| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128 |
- import crypto from 'crypto';
- import type { Ref, IUserHasId } from '@growi/core/dist/interfaces';
- import type {
- Document, Model, Types, HydratedDocument,
- } from 'mongoose';
- import { Schema } from 'mongoose';
- import mongoosePaginate from 'mongoose-paginate-v2';
- import uniqueValidator from 'mongoose-unique-validator';
- import type { Scope } from '~/interfaces/scope';
- import loggerFactory from '~/utils/logger';
- import { getOrCreateModel } from '../util/mongoose-utils';
- import { extractScopes } from '../util/scope-utils';
- const logger = loggerFactory('growi:models:access-token');
- const generateTokenHash = (token: string) => crypto.createHash('sha256').update(token).digest('hex');
- type GenerateTokenResult = {
- token: string,
- _id: Types.ObjectId,
- expiredAt: Date,
- scopes?: Scope[],
- description?: string,
- }
- export type IAccessToken = {
- user: Ref<IUserHasId>,
- tokenHash: string,
- expiredAt: Date,
- scopes?: Scope[],
- description?: string,
- }
- export interface IAccessTokenDocument extends IAccessToken, Document {
- isExpired: () => boolean
- }
- export interface IAccessTokenModel extends Model<IAccessTokenDocument> {
- generateToken: (userId: Types.ObjectId | string, expiredAt: Date, scopes?: Scope[], description?: string,) => Promise<GenerateTokenResult>
- deleteToken: (token: string) => Promise<void>
- deleteTokenById: (tokenId: Types.ObjectId | string) => Promise<void>
- deleteAllTokensByUserId: (userId: Types.ObjectId | string) => Promise<void>
- deleteExpiredToken: () => Promise<void>
- findUserIdByToken: (token: string, requiredScopes: Scope[]) => Promise<HydratedDocument<IAccessTokenDocument> | null>
- findTokenByUserId: (userId: Types.ObjectId | string) => Promise<HydratedDocument<IAccessTokenDocument>[] | null>
- validateTokenScopes: (token: string, requiredScopes: Scope[]) => Promise<boolean>
- }
- const accessTokenSchema = new Schema<IAccessTokenDocument, IAccessTokenModel>({
- user: {
- type: Schema.Types.ObjectId, ref: 'User', required: true,
- },
- tokenHash: { type: String, required: true, unique: true },
- expiredAt: { type: Date, required: true, index: true },
- scopes: [{ type: String, default: '' }],
- description: { type: String, default: '' },
- });
- accessTokenSchema.plugin(mongoosePaginate);
- accessTokenSchema.plugin(uniqueValidator);
- accessTokenSchema.statics.generateToken = async function(userId: Types.ObjectId | string, expiredAt: Date, scopes?: Scope[], description?: string) {
- const extractedScopes = extractScopes(scopes ?? []);
- const token = crypto.randomBytes(32).toString('hex');
- const tokenHash = generateTokenHash(token);
- try {
- const { _id } = await this.create({
- user: userId, tokenHash, expiredAt, scopes: extractedScopes, description,
- });
- logger.debug('Token generated');
- return {
- token, _id, expiredAt, scopes: extractedScopes, description,
- };
- }
- catch (err) {
- logger.debug('Failed to generate token');
- throw err;
- }
- };
- accessTokenSchema.statics.deleteToken = async function(token: string) {
- const tokenHash = generateTokenHash(token);
- await this.deleteOne({ tokenHash });
- };
- accessTokenSchema.statics.deleteTokenById = async function(tokenId: Types.ObjectId | string) {
- await this.deleteOne({ _id: tokenId });
- };
- accessTokenSchema.statics.deleteAllTokensByUserId = async function(userId: Types.ObjectId | string) {
- await this.deleteMany({ user: userId });
- };
- accessTokenSchema.statics.deleteExpiredToken = async function() {
- const now = new Date();
- await this.deleteMany({ expiredAt: { $lte: now } });
- };
- accessTokenSchema.statics.findUserIdByToken = async function(token: string, requiredScopes: Scope[]) {
- const tokenHash = generateTokenHash(token);
- const now = new Date();
- if (requiredScopes.length === 0) {
- return;
- }
- const extractedScopes = extractScopes(requiredScopes);
- return this.findOne({ tokenHash, expiredAt: { $gt: now }, scopes: { $all: extractedScopes } }).select('user');
- };
- accessTokenSchema.statics.findTokenByUserId = async function(userId: Types.ObjectId | string) {
- const now = new Date();
- return this.find({ user: userId, expiredAt: { $gt: now } }).select('_id expiredAt scopes description');
- };
- accessTokenSchema.statics.validateTokenScopes = async function(token: string, requiredScopes: Scope[]) {
- return this.findUserIdByToken(token, requiredScopes) != null;
- };
- accessTokenSchema.methods.isExpired = function() {
- return this.expiredAt < new Date();
- };
- export const AccessToken = getOrCreateModel<IAccessTokenDocument, IAccessTokenModel>('AccessToken', accessTokenSchema);
|