reiji-h 1 год назад
Родитель
Сommit
1c42dabfd0

+ 25 - 5
apps/app/src/server/models/access-token.ts

@@ -1,6 +1,5 @@
 import crypto from 'crypto';
 import crypto from 'crypto';
 
 
-import type { IAccessToken } from '@growi/core';
 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';
@@ -12,6 +11,16 @@ import { getOrCreateModel } from '../util/mongoose-utils';
 
 
 const logger = loggerFactory('growi:models:comment');
 const logger = loggerFactory('growi:models:comment');
 
 
+const generateTokenHash = (token: string) => crypto.createHash('sha256').update(token).digest('hex');
+
+export type IAccessToken = {
+  userId: Types.ObjectId,
+  tokenHash: string,
+  expiredAt: Date,
+  scope: string[],
+  description: string,
+}
+
 export interface IAccessTokenDocument extends IAccessToken, Document {
 export interface IAccessTokenDocument extends IAccessToken, Document {
   userId: Types.ObjectId,
   userId: Types.ObjectId,
   tokenHash: string,
   tokenHash: string,
@@ -25,8 +34,11 @@ export interface IAccessTokenDocument extends IAccessToken, Document {
 export interface IAccessTokenModel extends Model<IAccessTokenDocument> {
 export interface IAccessTokenModel extends Model<IAccessTokenDocument> {
   generateToken: (userId: Types.ObjectId, expiredAt: Date, scope: string[], description?: string,) => Promise<string>
   generateToken: (userId: Types.ObjectId, expiredAt: Date, scope: string[], description?: string,) => Promise<string>
   deleteToken: (model: IAccessTokenModel) => Promise<void>
   deleteToken: (model: IAccessTokenModel) => Promise<void>
+  deleteAllTokensByUserId: (userId: Types.ObjectId) => Promise<void>
+  deleteExpiredToken: () => Promise<void>
   findUserIdByToken: (token: string) => Promise<IAccessTokenDocument>
   findUserIdByToken: (token: string) => Promise<IAccessTokenDocument>
   findTokenByUserId: (userId: Types.ObjectId) => Promise<IAccessTokenDocument[]>
   findTokenByUserId: (userId: Types.ObjectId) => Promise<IAccessTokenDocument[]>
+  validateTokenScopes: (token: string, requiredScope: string[]) => Promise<boolean>
 }
 }
 
 
 const IAccessTokenSchema = new Schema<IAccessTokenDocument, IAccessTokenModel>({
 const IAccessTokenSchema = new Schema<IAccessTokenDocument, IAccessTokenModel>({
@@ -45,7 +57,7 @@ IAccessTokenSchema.plugin(uniqueValidator);
 IAccessTokenSchema.statics.generateToken = async function(userId: Types.ObjectId, expiredAt: Date, scope?: string[], description?: string) {
 IAccessTokenSchema.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 = crypto.createHash('sha256').update(token).digest('hex');
+  const tokenHash = generateTokenHash(token);
 
 
   // TODO: scope validation
   // TODO: scope validation
   try {
   try {
@@ -63,7 +75,7 @@ IAccessTokenSchema.statics.generateToken = async function(userId: Types.ObjectId
 };
 };
 
 
 IAccessTokenSchema.statics.deleteToken = async function(token: string) {
 IAccessTokenSchema.statics.deleteToken = async function(token: string) {
-  const tokenHash = crypto.createHash('sha256').update(token).digest('hex');
+  const tokenHash = generateTokenHash(token);
   return this.deleteOne({ tokenHash });
   return this.deleteOne({ tokenHash });
 };
 };
 
 
@@ -77,7 +89,7 @@ IAccessTokenSchema.statics.deleteExpiredToken = async function() {
 };
 };
 
 
 IAccessTokenSchema.statics.findUserIdByToken = async function(token: string) {
 IAccessTokenSchema.statics.findUserIdByToken = async function(token: string) {
-  const tokenHash = crypto.createHash('sha256').update(token).digest('hex');
+  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('userId');
 };
 };
@@ -87,9 +99,17 @@ IAccessTokenSchema.statics.findTokenByUserId = async function(userId: Types.Obje
   return this.find({ userId, expiredAt: { $gt: now } }).select('expiredAt scope description');
   return this.find({ userId, expiredAt: { $gt: now } }).select('expiredAt scope description');
 };
 };
 
 
+// check token's scope is satisfied
+IAccessTokenSchema.statics.validateTokenScopes = async function(token: string, requiredScopes: string[]) {
+  const tokenHash = generateTokenHash(token);
+  const now = new Date();
+  // TODO: scope validation
+  const tokenData = await this.findOne({ tokenHash, expiredAt: { $gt: now }, scope: { $all: requiredScopes } });
+  return tokenData != null;
+};
+
 IAccessTokenSchema.methods.isExpired = function() {
 IAccessTokenSchema.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', IAccessTokenSchema);

+ 0 - 9
packages/core/src/interfaces/access-token.ts

@@ -1,9 +0,0 @@
-import type { Types } from 'mongoose';
-
-export type IAccessToken = {
-  userId: Types.ObjectId,
-  tokenHash: string,
-  expiredAt: Date,
-  scope: string[],
-  description: string,
-}

+ 0 - 2
packages/core/src/interfaces/user.ts

@@ -1,4 +1,3 @@
-import type { IAccessToken } from './access-token';
 import type { IAttachment } from './attachment';
 import type { IAttachment } from './attachment';
 import type { Ref } from './common';
 import type { Ref } from './common';
 import type { HasObjectId } from './has-object-id';
 import type { HasObjectId } from './has-object-id';
@@ -16,7 +15,6 @@ export type IUser = {
   admin: boolean,
   admin: boolean,
   readOnly: boolean,
   readOnly: boolean,
   apiToken?: string,
   apiToken?: string,
-  accessToken?: Ref<IAccessToken>,
   isEmailPublished: boolean,
   isEmailPublished: boolean,
   isInvitationEmailSended: boolean,
   isInvitationEmailSended: boolean,
   lang: Lang,
   lang: Lang,