|
@@ -1,5 +1,6 @@
|
|
|
import crypto from 'crypto';
|
|
import crypto from 'crypto';
|
|
|
|
|
|
|
|
|
|
+import type { Ref, IUserHasId } from '@growi/core/dist/interfaces';
|
|
|
import type { Document, Model, Types } from 'mongoose';
|
|
import type { Document, Model, Types } from 'mongoose';
|
|
|
import { Schema } from 'mongoose';
|
|
import { Schema } from 'mongoose';
|
|
|
import mongoosePaginate from 'mongoose-paginate-v2';
|
|
import mongoosePaginate from 'mongoose-paginate-v2';
|
|
@@ -9,25 +10,19 @@ import loggerFactory from '~/utils/logger';
|
|
|
|
|
|
|
|
import { getOrCreateModel } from '../util/mongoose-utils';
|
|
import { getOrCreateModel } from '../util/mongoose-utils';
|
|
|
|
|
|
|
|
-const logger = loggerFactory('growi:models:comment');
|
|
|
|
|
|
|
+const logger = loggerFactory('growi:models:access-token');
|
|
|
|
|
|
|
|
const generateTokenHash = (token: string) => crypto.createHash('sha256').update(token).digest('hex');
|
|
const generateTokenHash = (token: string) => crypto.createHash('sha256').update(token).digest('hex');
|
|
|
|
|
|
|
|
export type IAccessToken = {
|
|
export type IAccessToken = {
|
|
|
- userId: Types.ObjectId,
|
|
|
|
|
|
|
+ user: Ref<IUserHasId>,
|
|
|
tokenHash: string,
|
|
tokenHash: string,
|
|
|
expiredAt: Date,
|
|
expiredAt: Date,
|
|
|
- scope: string[],
|
|
|
|
|
- description: string,
|
|
|
|
|
|
|
+ scope?: string[],
|
|
|
|
|
+ description?: string,
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
export interface IAccessTokenDocument extends IAccessToken, Document {
|
|
export interface IAccessTokenDocument extends IAccessToken, Document {
|
|
|
- userId: Types.ObjectId,
|
|
|
|
|
- tokenHash: string,
|
|
|
|
|
- expiredAt: Date,
|
|
|
|
|
- scope: string[],
|
|
|
|
|
- description: string,
|
|
|
|
|
-
|
|
|
|
|
isExpired: () => boolean
|
|
isExpired: () => boolean
|
|
|
}
|
|
}
|
|
|
|
|
|
|
@@ -37,13 +32,13 @@ export interface IAccessTokenModel extends Model<IAccessTokenDocument> {
|
|
|
deleteTokenById: (tokenId: Types.ObjectId) => Promise<void>
|
|
deleteTokenById: (tokenId: Types.ObjectId) => Promise<void>
|
|
|
deleteAllTokensByUserId: (userId: Types.ObjectId) => Promise<void>
|
|
deleteAllTokensByUserId: (userId: Types.ObjectId) => Promise<void>
|
|
|
deleteExpiredToken: () => Promise<void>
|
|
deleteExpiredToken: () => Promise<void>
|
|
|
- findUserIdByToken: (token: string) => Promise<IAccessTokenDocument>
|
|
|
|
|
|
|
+ findUserIdByToken: (token: string) => Promise<Types.ObjectId>
|
|
|
findTokenByUserId: (userId: Types.ObjectId) => Promise<IAccessTokenDocument[]>
|
|
findTokenByUserId: (userId: Types.ObjectId) => Promise<IAccessTokenDocument[]>
|
|
|
validateTokenScopes: (token: string, requiredScope: string[]) => Promise<boolean>
|
|
validateTokenScopes: (token: string, requiredScope: string[]) => Promise<boolean>
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-const IAccessTokenSchema = new Schema<IAccessTokenDocument, IAccessTokenModel>({
|
|
|
|
|
- userId: {
|
|
|
|
|
|
|
+const accessTokenSchema = new Schema<IAccessTokenDocument, IAccessTokenModel>({
|
|
|
|
|
+ user: {
|
|
|
type: Schema.Types.ObjectId, ref: 'User', required: true,
|
|
type: Schema.Types.ObjectId, ref: 'User', required: true,
|
|
|
},
|
|
},
|
|
|
tokenHash: { type: String, required: true, unique: true },
|
|
tokenHash: { type: String, required: true, unique: true },
|
|
@@ -52,17 +47,17 @@ const IAccessTokenSchema = new Schema<IAccessTokenDocument, IAccessTokenModel>({
|
|
|
description: { type: String, default: '' },
|
|
description: { type: String, default: '' },
|
|
|
});
|
|
});
|
|
|
|
|
|
|
|
-IAccessTokenSchema.plugin(mongoosePaginate);
|
|
|
|
|
-IAccessTokenSchema.plugin(uniqueValidator);
|
|
|
|
|
|
|
+accessTokenSchema.plugin(mongoosePaginate);
|
|
|
|
|
+accessTokenSchema.plugin(uniqueValidator);
|
|
|
|
|
|
|
|
-IAccessTokenSchema.statics.generateToken = async function(userId: Types.ObjectId, expiredAt: Date, scope?: string[], description?: string) {
|
|
|
|
|
|
|
+accessTokenSchema.statics.generateToken = async function(userId: Types.ObjectId, expiredAt: Date, scope?: string[], description?: string) {
|
|
|
|
|
|
|
|
const token = crypto.randomBytes(32).toString('hex');
|
|
const token = crypto.randomBytes(32).toString('hex');
|
|
|
const tokenHash = generateTokenHash(token);
|
|
const tokenHash = generateTokenHash(token);
|
|
|
|
|
|
|
|
try {
|
|
try {
|
|
|
const { _id } = await this.create({
|
|
const { _id } = await this.create({
|
|
|
- userId, tokenHash, expiredAt, scope, description,
|
|
|
|
|
|
|
+ user: userId, tokenHash, expiredAt, scope, description,
|
|
|
});
|
|
});
|
|
|
|
|
|
|
|
logger.debug('Token generated');
|
|
logger.debug('Token generated');
|
|
@@ -76,44 +71,44 @@ IAccessTokenSchema.statics.generateToken = async function(userId: Types.ObjectId
|
|
|
}
|
|
}
|
|
|
};
|
|
};
|
|
|
|
|
|
|
|
-IAccessTokenSchema.statics.deleteToken = async function(token: string) {
|
|
|
|
|
|
|
+accessTokenSchema.statics.deleteToken = async function(token: string) {
|
|
|
const tokenHash = generateTokenHash(token);
|
|
const tokenHash = generateTokenHash(token);
|
|
|
return this.deleteOne({ tokenHash });
|
|
return this.deleteOne({ tokenHash });
|
|
|
};
|
|
};
|
|
|
|
|
|
|
|
-IAccessTokenSchema.statics.deleteTokenById = async function(tokenId: Types.ObjectId) {
|
|
|
|
|
|
|
+accessTokenSchema.statics.deleteTokenById = async function(tokenId: Types.ObjectId) {
|
|
|
return this.deleteOne({ _id: tokenId });
|
|
return this.deleteOne({ _id: tokenId });
|
|
|
};
|
|
};
|
|
|
|
|
|
|
|
-IAccessTokenSchema.statics.deleteAllTokensByUserId = async function(userId: Types.ObjectId) {
|
|
|
|
|
- return this.deleteMany({ userId });
|
|
|
|
|
|
|
+accessTokenSchema.statics.deleteAllTokensByUserId = async function(userId: Types.ObjectId) {
|
|
|
|
|
+ return this.deleteMany({ user: userId });
|
|
|
};
|
|
};
|
|
|
|
|
|
|
|
-IAccessTokenSchema.statics.deleteExpiredToken = async function() {
|
|
|
|
|
|
|
+accessTokenSchema.statics.deleteExpiredToken = async function() {
|
|
|
const now = new Date();
|
|
const now = new Date();
|
|
|
return this.deleteMany({ expiredAt: { $lte: now } });
|
|
return this.deleteMany({ expiredAt: { $lte: now } });
|
|
|
};
|
|
};
|
|
|
|
|
|
|
|
-IAccessTokenSchema.statics.findUserIdByToken = async function(token: string) {
|
|
|
|
|
|
|
+accessTokenSchema.statics.findUserIdByToken = async function(token: string) {
|
|
|
const tokenHash = generateTokenHash(token);
|
|
const tokenHash = generateTokenHash(token);
|
|
|
const now = new Date();
|
|
const now = new Date();
|
|
|
- return this.findOne({ tokenHash, expiredAt: { $gt: now } }).select('userId');
|
|
|
|
|
|
|
+ return this.findOne({ tokenHash, expiredAt: { $gt: now } }).select('user');
|
|
|
};
|
|
};
|
|
|
|
|
|
|
|
-IAccessTokenSchema.statics.findTokenByUserId = async function(userId: Types.ObjectId) {
|
|
|
|
|
|
|
+accessTokenSchema.statics.findTokenByUserId = async function(userId: Types.ObjectId) {
|
|
|
const now = new Date();
|
|
const now = new Date();
|
|
|
- return this.find({ userId, expiredAt: { $gt: now } }).select('_id expiredAt scope description');
|
|
|
|
|
|
|
+ return this.find({ user: userId, expiredAt: { $gt: now } }).select('_id expiredAt scope description');
|
|
|
};
|
|
};
|
|
|
|
|
|
|
|
-IAccessTokenSchema.statics.validateTokenScopes = async function(token: string, requiredScopes: string[]) {
|
|
|
|
|
|
|
+accessTokenSchema.statics.validateTokenScopes = async function(token: string, requiredScopes: string[]) {
|
|
|
const tokenHash = generateTokenHash(token);
|
|
const tokenHash = generateTokenHash(token);
|
|
|
const now = new Date();
|
|
const now = new Date();
|
|
|
const tokenData = await this.findOne({ tokenHash, expiredAt: { $gt: now }, scope: { $all: requiredScopes } });
|
|
const tokenData = await this.findOne({ tokenHash, expiredAt: { $gt: now }, scope: { $all: requiredScopes } });
|
|
|
return tokenData != null;
|
|
return tokenData != null;
|
|
|
};
|
|
};
|
|
|
|
|
|
|
|
-IAccessTokenSchema.methods.isExpired = function() {
|
|
|
|
|
|
|
+accessTokenSchema.methods.isExpired = function() {
|
|
|
return this.expiredAt < new Date();
|
|
return this.expiredAt < new Date();
|
|
|
};
|
|
};
|
|
|
|
|
|
|
|
-export const AccessToken = getOrCreateModel<IAccessTokenDocument, IAccessToken>('AccessToken', IAccessTokenSchema);
|
|
|
|
|
|
|
+export const AccessToken = getOrCreateModel<IAccessTokenDocument, IAccessToken>('AccessToken', accessTokenSchema);
|