feat: support token blacklist (#2168)
* feat: support token blacklist, Close T-799 * feat: clean * fix: possible token does not exist * fix: update * feat: update * feat: add node-cron to delete expired token * fix: findOrCreate not work and add test case * test: add token-blacklist tests * feat: add test cases for blacklist in authManager * test: update better * fix: should hidden token field * test: clean * test: clean * fix: should stop cron in afterStop * refactor: move delete expired token in token blacklist service * feat: remove plugin disable/enable logic * fix: clean * test: revert * fix: cron typo
This commit is contained in:
parent
207ad61c63
commit
25a3a8affa
@ -1,6 +1,7 @@
|
|||||||
import { Context } from '@nocobase/actions';
|
import { Context } from '@nocobase/actions';
|
||||||
import { Auth, AuthManager } from '@nocobase/auth';
|
import { Auth, AuthManager } from '@nocobase/auth';
|
||||||
import { Model } from '@nocobase/database';
|
import Database, { Model } from '@nocobase/database';
|
||||||
|
import { MockServer, mockServer } from '@nocobase/test';
|
||||||
|
|
||||||
class MockStorer {
|
class MockStorer {
|
||||||
elements: Map<string, any> = new Map();
|
elements: Map<string, any> = new Map();
|
||||||
@ -47,4 +48,69 @@ describe('auth-manager', () => {
|
|||||||
const authenticator = await authManager.get('basic-test', {} as Context);
|
const authenticator = await authManager.get('basic-test', {} as Context);
|
||||||
expect(authenticator).toBeInstanceOf(BasicAuth);
|
expect(authenticator).toBeInstanceOf(BasicAuth);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('middleware', () => {
|
||||||
|
let app: MockServer;
|
||||||
|
let db: Database;
|
||||||
|
let agent;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
app = mockServer({
|
||||||
|
registerActions: true,
|
||||||
|
acl: true,
|
||||||
|
plugins: ['users', 'auth', 'acl'],
|
||||||
|
});
|
||||||
|
|
||||||
|
// app.plugin(ApiKeysPlugin);
|
||||||
|
await app.loadAndInstall({ clean: true });
|
||||||
|
db = app.db;
|
||||||
|
agent = app.agent();
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(async () => {
|
||||||
|
await db.close();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('blacklist', () => {
|
||||||
|
const hasFn = jest.fn();
|
||||||
|
const addFn = jest.fn();
|
||||||
|
beforeEach(async () => {
|
||||||
|
await agent.login(1);
|
||||||
|
app.authManager.setTokenBlacklistService({
|
||||||
|
has: hasFn,
|
||||||
|
add: addFn,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
hasFn.mockReset();
|
||||||
|
addFn.mockReset();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('basic', async () => {
|
||||||
|
const res = await agent.resource('auth').check();
|
||||||
|
const token = res.request.header['Authorization'].replace('Bearer ', '');
|
||||||
|
expect(res.status).toBe(200);
|
||||||
|
expect(hasFn).toHaveBeenCalledWith(token);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('signOut should add token to blacklist', async () => {
|
||||||
|
// signOut will add token
|
||||||
|
const res = await agent.resource('auth').signOut();
|
||||||
|
const token = res.request.header['Authorization'].replace('Bearer ', '');
|
||||||
|
expect(addFn).toHaveBeenCalledWith({
|
||||||
|
token,
|
||||||
|
// Date or String is ok
|
||||||
|
expiration: expect.any(String),
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should throw 401 when token in blacklist', async () => {
|
||||||
|
hasFn.mockImplementation(() => true);
|
||||||
|
const res = await agent.resource('auth').check();
|
||||||
|
expect(res.status).toBe(401);
|
||||||
|
expect(res.text).toContain('token is not available');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
@ -3,6 +3,7 @@ import { Model } from '@nocobase/database';
|
|||||||
import { Registry } from '@nocobase/utils';
|
import { Registry } from '@nocobase/utils';
|
||||||
import { Auth, AuthExtend } from './auth';
|
import { Auth, AuthExtend } from './auth';
|
||||||
import { JwtOptions, JwtService } from './base/jwt-service';
|
import { JwtOptions, JwtService } from './base/jwt-service';
|
||||||
|
import { ITokenBlacklistService } from './base/token-blacklist-service';
|
||||||
|
|
||||||
type Storer = {
|
type Storer = {
|
||||||
get: (name: string) => Promise<Model>;
|
get: (name: string) => Promise<Model>;
|
||||||
@ -34,6 +35,10 @@ export class AuthManager {
|
|||||||
this.storer = storer;
|
this.storer = storer;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
setTokenBlacklistService(service: ITokenBlacklistService) {
|
||||||
|
this.jwt.blacklist = service;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* registerTypes
|
* registerTypes
|
||||||
* @description Add a new authenticate type and the corresponding authenticator.
|
* @description Add a new authenticate type and the corresponding authenticator.
|
||||||
@ -81,6 +86,11 @@ export class AuthManager {
|
|||||||
*/
|
*/
|
||||||
middleware() {
|
middleware() {
|
||||||
return async (ctx: Context & { auth: Auth }, next: Next) => {
|
return async (ctx: Context & { auth: Auth }, next: Next) => {
|
||||||
|
const token = ctx.getBearerToken();
|
||||||
|
if (token && (await ctx.app.authManager.jwt.blacklist.has(token))) {
|
||||||
|
return ctx.throw(401, ctx.t('token is not available'));
|
||||||
|
}
|
||||||
|
|
||||||
const name = ctx.get(this.options.authKey) || this.options.default;
|
const name = ctx.get(this.options.authKey) || this.options.default;
|
||||||
let authenticator: Auth;
|
let authenticator: Auth;
|
||||||
try {
|
try {
|
||||||
|
@ -81,4 +81,12 @@ export class BaseAuth extends Auth {
|
|||||||
token,
|
token,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async signOut(): Promise<any> {
|
||||||
|
const token = this.ctx.getBearerToken();
|
||||||
|
if (!token) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
return await this.jwt.block(token);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import jwt, { SignOptions } from 'jsonwebtoken';
|
import jwt, { SignOptions } from 'jsonwebtoken';
|
||||||
|
import { ITokenBlacklistService } from './token-blacklist-service';
|
||||||
|
|
||||||
export interface JwtOptions {
|
export interface JwtOptions {
|
||||||
secret: string;
|
secret: string;
|
||||||
@ -20,6 +21,8 @@ export class JwtService {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public blacklist: ITokenBlacklistService;
|
||||||
|
|
||||||
private expiresIn() {
|
private expiresIn() {
|
||||||
return this.options.expiresIn;
|
return this.options.expiresIn;
|
||||||
}
|
}
|
||||||
@ -43,4 +46,19 @@ export class JwtService {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description Block a token so that this token can no longer be used
|
||||||
|
*/
|
||||||
|
async block(token: string) {
|
||||||
|
if (!this.blacklist) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
const { exp } = await this.decode(token);
|
||||||
|
|
||||||
|
return this.blacklist.add({
|
||||||
|
token,
|
||||||
|
expiration: new Date(exp * 1000).toString(),
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
4
packages/core/auth/src/base/token-blacklist-service.ts
Normal file
4
packages/core/auth/src/base/token-blacklist-service.ts
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
export interface ITokenBlacklistService {
|
||||||
|
has(token: string): Promise<boolean>;
|
||||||
|
add(values: { token: string; expiration: string | Date }): Promise<any>;
|
||||||
|
}
|
@ -1,4 +1,5 @@
|
|||||||
export * from './auth-manager';
|
|
||||||
export * from './auth';
|
|
||||||
export * from './base/auth';
|
|
||||||
export * from './actions';
|
export * from './actions';
|
||||||
|
export * from './auth';
|
||||||
|
export * from './auth-manager';
|
||||||
|
export * from './base/auth';
|
||||||
|
export * from './base/token-blacklist-service';
|
||||||
|
@ -99,6 +99,9 @@ export class MockServer extends Application {
|
|||||||
userId: typeof userOrId === 'number' ? userOrId : userOrId?.id,
|
userId: typeof userOrId === 'number' ? userOrId : userOrId?.id,
|
||||||
},
|
},
|
||||||
process.env.APP_KEY,
|
process.env.APP_KEY,
|
||||||
|
{
|
||||||
|
expiresIn: '1d',
|
||||||
|
},
|
||||||
),
|
),
|
||||||
{ type: 'bearer' },
|
{ type: 'bearer' },
|
||||||
)
|
)
|
||||||
|
@ -78,5 +78,10 @@ export default {
|
|||||||
],
|
],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: 'token',
|
||||||
|
type: 'string',
|
||||||
|
hidden: true,
|
||||||
|
},
|
||||||
],
|
],
|
||||||
} as CollectionOptions;
|
} as CollectionOptions;
|
||||||
|
@ -15,7 +15,6 @@ describe('actions', () => {
|
|||||||
plugins: ['users', 'auth', 'api-keys', 'acl'],
|
plugins: ['users', 'auth', 'api-keys', 'acl'],
|
||||||
});
|
});
|
||||||
|
|
||||||
// app.plugin(ApiKeysPlugin);
|
|
||||||
await app.loadAndInstall({ clean: true });
|
await app.loadAndInstall({ clean: true });
|
||||||
db = app.db;
|
db = app.db;
|
||||||
repo = db.getRepository('apiKeys');
|
repo = db.getRepository('apiKeys');
|
||||||
@ -151,7 +150,7 @@ describe('actions', () => {
|
|||||||
expect(res.body.data.length).toBe(1);
|
expect(res.body.data.length).toBe(1);
|
||||||
const data = res.body.data[0];
|
const data = res.body.data[0];
|
||||||
await resource.destroy({
|
await resource.destroy({
|
||||||
id: data.id,
|
filterByTk: data.id,
|
||||||
});
|
});
|
||||||
expect((await resource.list()).body.data.length).toBe(0);
|
expect((await resource.list()).body.data.length).toBe(0);
|
||||||
});
|
});
|
||||||
@ -162,10 +161,21 @@ describe('actions', () => {
|
|||||||
const data = res.body.data[0];
|
const data = res.body.data[0];
|
||||||
await agent.login(testUser);
|
await agent.login(testUser);
|
||||||
await resource.destroy({
|
await resource.destroy({
|
||||||
id: data.id,
|
filterByTk: data.id,
|
||||||
});
|
});
|
||||||
await agent.login(user);
|
await agent.login(user);
|
||||||
expect((await resource.list()).body.data.length).toBe(1);
|
expect((await resource.list()).body.data.length).toBe(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('The token should not work after removing the api key', async () => {
|
||||||
|
const res = await resource.list();
|
||||||
|
expect(res.body.data.length).toBe(1);
|
||||||
|
const data = res.body.data[0];
|
||||||
|
await resource.destroy({
|
||||||
|
filterByTk: data.id,
|
||||||
|
});
|
||||||
|
const response = await agent.set('Authorization', `Bearer ${result.token}`).resource('auth').check();
|
||||||
|
expect(response.status).toBe(401);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -18,15 +18,32 @@ export async function create(ctx: Context, next: Next) {
|
|||||||
throw ctx.throw(400, ctx.t('Role not found'));
|
throw ctx.throw(400, ctx.t('Role not found'));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const token = ctx.app.authManager.jwt.sign(
|
||||||
|
{ userId: ctx.auth.user.id, roleName: role.name },
|
||||||
|
{ expiresIn: values.expiresIn },
|
||||||
|
);
|
||||||
|
ctx.action.mergeParams({
|
||||||
|
values: {
|
||||||
|
token,
|
||||||
|
},
|
||||||
|
});
|
||||||
return actions.create(ctx, async () => {
|
return actions.create(ctx, async () => {
|
||||||
const token = ctx.app.authManager.jwt.sign(
|
|
||||||
{ userId: ctx.auth.user.id, roleName: role.name },
|
|
||||||
{ expiresIn: values.expiresIn },
|
|
||||||
);
|
|
||||||
|
|
||||||
ctx.body = {
|
ctx.body = {
|
||||||
token,
|
token,
|
||||||
};
|
};
|
||||||
await next();
|
await next();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function destroy(ctx: Context, next: Next) {
|
||||||
|
const repo = ctx.db.getRepository(ctx.action.resourceName);
|
||||||
|
const { filterByTk } = ctx.action.params;
|
||||||
|
|
||||||
|
const data = await repo.findById(filterByTk);
|
||||||
|
const token = data?.get('token');
|
||||||
|
if (token) {
|
||||||
|
await ctx.app.authManager.jwt.block(token);
|
||||||
|
}
|
||||||
|
|
||||||
|
return actions.destroy(ctx, next);
|
||||||
|
}
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import { Plugin } from '@nocobase/server';
|
import { Plugin } from '@nocobase/server';
|
||||||
import { resolve } from 'path';
|
import { resolve } from 'path';
|
||||||
import { NAMESPACE } from '../constants';
|
import { NAMESPACE } from '../constants';
|
||||||
import { create } from './actions/api-keys';
|
import { create, destroy } from './actions/api-keys';
|
||||||
import { enUS, zhCN } from './locale';
|
import { enUS, zhCN } from './locale';
|
||||||
|
|
||||||
export interface ApiKeysPluginConfig {
|
export interface ApiKeysPluginConfig {
|
||||||
@ -22,6 +22,7 @@ export default class ApiKeysPlugin extends Plugin<ApiKeysPluginConfig> {
|
|||||||
name: this.resourceName,
|
name: this.resourceName,
|
||||||
actions: {
|
actions: {
|
||||||
create,
|
create,
|
||||||
|
destroy,
|
||||||
},
|
},
|
||||||
only: ['list', 'create', 'destroy'],
|
only: ['list', 'create', 'destroy'],
|
||||||
});
|
});
|
||||||
|
@ -3,11 +3,15 @@
|
|||||||
"version": "0.10.0-alpha.5",
|
"version": "0.10.0-alpha.5",
|
||||||
"main": "lib/server/index.js",
|
"main": "lib/server/index.js",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@nocobase/test": "0.10.0-alpha.5",
|
||||||
|
"@types/cron": "^2.0.1"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
"@nocobase/actions": "0.10.0-alpha.5",
|
"@nocobase/actions": "0.10.0-alpha.5",
|
||||||
"@nocobase/client": "0.10.0-alpha.5",
|
"@nocobase/client": "0.10.0-alpha.5",
|
||||||
"@nocobase/database": "0.10.0-alpha.5",
|
"@nocobase/database": "0.10.0-alpha.5",
|
||||||
"@nocobase/server": "0.10.0-alpha.5",
|
"@nocobase/server": "0.10.0-alpha.5",
|
||||||
"@nocobase/test": "0.10.0-alpha.5"
|
"cron": "^2.3.1"
|
||||||
},
|
},
|
||||||
"displayName": "Authentication",
|
"displayName": "Authentication",
|
||||||
"displayName.zh-CN": "用户认证",
|
"displayName.zh-CN": "用户认证",
|
||||||
|
@ -0,0 +1,73 @@
|
|||||||
|
import Database, { Repository } from '@nocobase/database';
|
||||||
|
import { MockServer, mockServer } from '@nocobase/test';
|
||||||
|
import { TokenBlacklistService } from '../token-blacklist';
|
||||||
|
|
||||||
|
describe('token-blacklist', () => {
|
||||||
|
let app: MockServer;
|
||||||
|
let db: Database;
|
||||||
|
let repo: Repository;
|
||||||
|
let tokenBlacklist: TokenBlacklistService;
|
||||||
|
|
||||||
|
beforeAll(async () => {
|
||||||
|
app = mockServer({
|
||||||
|
plugins: ['auth'],
|
||||||
|
});
|
||||||
|
await app.loadAndInstall({ clean: true });
|
||||||
|
db = app.db;
|
||||||
|
repo = db.getRepository('tokenBlacklist');
|
||||||
|
tokenBlacklist = new TokenBlacklistService(app.getPlugin('auth'));
|
||||||
|
});
|
||||||
|
|
||||||
|
afterAll(async () => {
|
||||||
|
await db.close();
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(async () => {
|
||||||
|
await repo.destroy({
|
||||||
|
truncate: true,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('add and has correctly', async () => {
|
||||||
|
await tokenBlacklist.add({
|
||||||
|
token: 'test',
|
||||||
|
expiration: new Date(),
|
||||||
|
});
|
||||||
|
|
||||||
|
await tokenBlacklist.add({
|
||||||
|
token: 'test1',
|
||||||
|
expiration: new Date(),
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(tokenBlacklist.has('test')).toBeTruthy();
|
||||||
|
expect(tokenBlacklist.has('test1')).toBeTruthy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('add same token correctly', async () => {
|
||||||
|
await tokenBlacklist.add({
|
||||||
|
token: 'test',
|
||||||
|
expiration: new Date(),
|
||||||
|
});
|
||||||
|
|
||||||
|
await tokenBlacklist.add({
|
||||||
|
token: 'test',
|
||||||
|
expiration: new Date(),
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(tokenBlacklist.has('test')).toBeTruthy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('delete expired token correctly', async () => {
|
||||||
|
await tokenBlacklist.add({
|
||||||
|
token: 'should be deleted',
|
||||||
|
expiration: new Date('2020-01-01'),
|
||||||
|
});
|
||||||
|
await tokenBlacklist.add({
|
||||||
|
token: 'should not be deleted',
|
||||||
|
expiration: new Date('2100-01-01'),
|
||||||
|
});
|
||||||
|
await tokenBlacklist.deleteByExpiration();
|
||||||
|
expect(await tokenBlacklist.has('should be deleted')).not.toBeTruthy();
|
||||||
|
expect(await tokenBlacklist.has('should not be deleted')).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
@ -0,0 +1,19 @@
|
|||||||
|
import { CollectionOptions } from '@nocobase/client';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
namespace: 'auth.token-black',
|
||||||
|
duplicator: 'optional',
|
||||||
|
name: 'tokenBlacklist',
|
||||||
|
model: 'TokenBlacklistModel',
|
||||||
|
fields: [
|
||||||
|
{
|
||||||
|
type: 'string',
|
||||||
|
name: 'token',
|
||||||
|
index: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'date',
|
||||||
|
name: 'expiration',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
} as CollectionOptions;
|
@ -1,17 +1,16 @@
|
|||||||
|
import { Model } from '@nocobase/database';
|
||||||
import { InstallOptions, Plugin } from '@nocobase/server';
|
import { InstallOptions, Plugin } from '@nocobase/server';
|
||||||
import { resolve } from 'path';
|
import { resolve } from 'path';
|
||||||
import { BasicAuth } from './basic-auth';
|
import { namespace, presetAuthenticator, presetAuthType } from '../preset';
|
||||||
import { presetAuthType, presetAuthenticator } from '../preset';
|
|
||||||
import authActions from './actions/auth';
|
import authActions from './actions/auth';
|
||||||
import authenticatorsActions from './actions/authenticators';
|
import authenticatorsActions from './actions/authenticators';
|
||||||
|
import { BasicAuth } from './basic-auth';
|
||||||
import { enUS, zhCN } from './locale';
|
import { enUS, zhCN } from './locale';
|
||||||
import { namespace } from '../preset';
|
|
||||||
import { AuthModel } from './model/authenticator';
|
import { AuthModel } from './model/authenticator';
|
||||||
import { Model } from '@nocobase/database';
|
import { TokenBlacklistService } from './token-blacklist';
|
||||||
|
|
||||||
export class AuthPlugin extends Plugin {
|
export class AuthPlugin extends Plugin {
|
||||||
afterAdd() {}
|
afterAdd() {}
|
||||||
|
|
||||||
async beforeLoad() {
|
async beforeLoad() {
|
||||||
this.app.i18n.addResources('zh-CN', namespace, zhCN);
|
this.app.i18n.addResources('zh-CN', namespace, zhCN);
|
||||||
this.app.i18n.addResources('en-US', namespace, enUS);
|
this.app.i18n.addResources('en-US', namespace, enUS);
|
||||||
@ -40,6 +39,12 @@ export class AuthPlugin extends Plugin {
|
|||||||
return authenticator || authenticators[0];
|
return authenticator || authenticators[0];
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (!this.app.authManager.jwt.blacklist) {
|
||||||
|
// If blacklist service is not set, should configure default blacklist service
|
||||||
|
this.app.authManager.setTokenBlacklistService(new TokenBlacklistService(this));
|
||||||
|
}
|
||||||
|
|
||||||
this.app.authManager.registerTypes(presetAuthType, {
|
this.app.authManager.registerTypes(presetAuthType, {
|
||||||
auth: BasicAuth,
|
auth: BasicAuth,
|
||||||
});
|
});
|
||||||
@ -81,11 +86,6 @@ export class AuthPlugin extends Plugin {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async afterEnable() {}
|
|
||||||
|
|
||||||
async afterDisable() {}
|
|
||||||
|
|
||||||
async remove() {}
|
async remove() {}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
66
packages/plugins/auth/src/server/token-blacklist.ts
Normal file
66
packages/plugins/auth/src/server/token-blacklist.ts
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
import { ITokenBlacklistService } from '@nocobase/auth';
|
||||||
|
import { Repository } from '@nocobase/database';
|
||||||
|
import { CronJob } from 'cron';
|
||||||
|
import AuthPlugin from './plugin';
|
||||||
|
|
||||||
|
export class TokenBlacklistService implements ITokenBlacklistService {
|
||||||
|
repo: Repository;
|
||||||
|
cronJob: CronJob;
|
||||||
|
|
||||||
|
constructor(protected plugin: AuthPlugin) {
|
||||||
|
this.repo = plugin.db.getRepository('tokenBlacklist');
|
||||||
|
this.cronJob = this.createCronJob();
|
||||||
|
}
|
||||||
|
|
||||||
|
get app() {
|
||||||
|
return this.plugin.app;
|
||||||
|
}
|
||||||
|
|
||||||
|
createCronJob() {
|
||||||
|
const cronJob = new CronJob(
|
||||||
|
// every day at 03:00
|
||||||
|
'0 3 * * *', //
|
||||||
|
async () => {
|
||||||
|
this.app.logger.info(`${this.plugin.name}: Start delete expired blacklist token`);
|
||||||
|
await this.deleteByExpiration();
|
||||||
|
this.app.logger.info(`${this.plugin.name}: End delete expired blacklist token`);
|
||||||
|
},
|
||||||
|
null,
|
||||||
|
);
|
||||||
|
|
||||||
|
this.app.once('beforeStart', () => {
|
||||||
|
cronJob.start();
|
||||||
|
});
|
||||||
|
|
||||||
|
this.app.once('beforeStop', () => {
|
||||||
|
cronJob.stop();
|
||||||
|
});
|
||||||
|
|
||||||
|
return cronJob;
|
||||||
|
}
|
||||||
|
|
||||||
|
async has(token: string) {
|
||||||
|
return !!(await this.repo.findOne({
|
||||||
|
where: {
|
||||||
|
token,
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
async add(values) {
|
||||||
|
return this.repo.model.findOrCreate({
|
||||||
|
defaults: values,
|
||||||
|
where: {
|
||||||
|
token: values.token,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
async deleteByExpiration() {
|
||||||
|
return this.repo.destroy({
|
||||||
|
filter: {
|
||||||
|
expiration: {
|
||||||
|
$dateNotAfter: new Date(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
34
yarn.lock
34
yarn.lock
@ -5467,9 +5467,10 @@
|
|||||||
version "1.6.2"
|
version "1.6.2"
|
||||||
resolved "https://registry.yarnpkg.com/@remix-run/router/-/router-1.6.2.tgz#bbe75f8c59e0b7077584920ce2cc76f8f354934d"
|
resolved "https://registry.yarnpkg.com/@remix-run/router/-/router-1.6.2.tgz#bbe75f8c59e0b7077584920ce2cc76f8f354934d"
|
||||||
|
|
||||||
"@remix-run/router@1.6.3":
|
"@remix-run/router@1.7.1":
|
||||||
version "1.6.3"
|
version "1.7.1"
|
||||||
resolved "https://registry.npmmirror.com/@remix-run/router/-/router-1.6.3.tgz#8205baf6e17ef93be35bf62c37d2d594e9be0dad"
|
resolved "https://registry.yarnpkg.com/@remix-run/router/-/router-1.7.1.tgz#fea7ac35ae4014637c130011f59428f618730498"
|
||||||
|
integrity sha512-bgVQM4ZJ2u2CM8k1ey70o1ePFXsEzYVZoWghh6WjM8p59jQ7HxzbHW4SbnWFG7V9ig9chLawQxDTZ3xzOF8MkQ==
|
||||||
|
|
||||||
"@restart/hooks@^0.3.25":
|
"@restart/hooks@^0.3.25":
|
||||||
version "0.3.27"
|
version "0.3.27"
|
||||||
@ -6013,6 +6014,14 @@
|
|||||||
"@types/keygrip" "*"
|
"@types/keygrip" "*"
|
||||||
"@types/node" "*"
|
"@types/node" "*"
|
||||||
|
|
||||||
|
"@types/cron@^2.0.1":
|
||||||
|
version "2.0.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/@types/cron/-/cron-2.0.1.tgz#d8bf7a24475f64197c7ac868c362b41be596e5f8"
|
||||||
|
integrity sha512-WHa/1rtNtD2Q/H0+YTTZoty+/5rcE66iAFX2IY+JuUoOACsevYyFkSYu/2vdw+G5LrmO7Lxowrqm0av4k3qWNQ==
|
||||||
|
dependencies:
|
||||||
|
"@types/luxon" "*"
|
||||||
|
"@types/node" "*"
|
||||||
|
|
||||||
"@types/d3-array@*":
|
"@types/d3-array@*":
|
||||||
version "3.0.5"
|
version "3.0.5"
|
||||||
resolved "https://registry.yarnpkg.com/@types/d3-array/-/d3-array-3.0.5.tgz#857c1afffd3f51319bbc5b301956aca68acaa7b8"
|
resolved "https://registry.yarnpkg.com/@types/d3-array/-/d3-array-3.0.5.tgz#857c1afffd3f51319bbc5b301956aca68acaa7b8"
|
||||||
@ -6405,6 +6414,11 @@
|
|||||||
version "4.14.195"
|
version "4.14.195"
|
||||||
resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.195.tgz#bafc975b252eb6cea78882ce8a7b6bf22a6de632"
|
resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.195.tgz#bafc975b252eb6cea78882ce8a7b6bf22a6de632"
|
||||||
|
|
||||||
|
"@types/luxon@*":
|
||||||
|
version "3.3.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/@types/luxon/-/luxon-3.3.0.tgz#a61043a62c0a72696c73a0a305c544c96501e006"
|
||||||
|
integrity sha512-uKRI5QORDnrGFYgcdAVnHvEIvEZ8noTpP/Bg+HeUzZghwinDlIS87DEenV5r1YoOF9G4x600YsUXLWZ19rmTmg==
|
||||||
|
|
||||||
"@types/markdown-it-highlightjs@3.3.1":
|
"@types/markdown-it-highlightjs@3.3.1":
|
||||||
version "3.3.1"
|
version "3.3.1"
|
||||||
resolved "https://registry.yarnpkg.com/@types/markdown-it-highlightjs/-/markdown-it-highlightjs-3.3.1.tgz#1e051211e7754ba478449fea7faeab3d177ca892"
|
resolved "https://registry.yarnpkg.com/@types/markdown-it-highlightjs/-/markdown-it-highlightjs-3.3.1.tgz#1e051211e7754ba478449fea7faeab3d177ca892"
|
||||||
@ -10116,6 +10130,13 @@ cron-parser@^4.6.0:
|
|||||||
dependencies:
|
dependencies:
|
||||||
luxon "^3.2.1"
|
luxon "^3.2.1"
|
||||||
|
|
||||||
|
cron@^2.3.1:
|
||||||
|
version "2.3.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/cron/-/cron-2.3.1.tgz#71caa566ffef60ada9183b8677df4afa9a214ad7"
|
||||||
|
integrity sha512-1eRRlIT0UfIqauwbG9pkg3J6CX9A6My2ytJWqAXoK0T9oJnUZTzGBNPxao0zjodIbPgf8UQWjE62BMb9eVllSQ==
|
||||||
|
dependencies:
|
||||||
|
luxon "^3.2.1"
|
||||||
|
|
||||||
croner@~4.1.92:
|
croner@~4.1.92:
|
||||||
version "4.1.97"
|
version "4.1.97"
|
||||||
resolved "https://registry.yarnpkg.com/croner/-/croner-4.1.97.tgz#6e373dc7bb3026fab2deb0d82685feef20796766"
|
resolved "https://registry.yarnpkg.com/croner/-/croner-4.1.97.tgz#6e373dc7bb3026fab2deb0d82685feef20796766"
|
||||||
@ -21521,10 +21542,11 @@ react-router-dom@6.3.0, react-router-dom@^6.11.2:
|
|||||||
react-router "6.11.2"
|
react-router "6.11.2"
|
||||||
|
|
||||||
react-router@6.11.2, react-router@6.3.0, react-router@^6.11.2:
|
react-router@6.11.2, react-router@6.3.0, react-router@^6.11.2:
|
||||||
version "6.12.1"
|
version "6.14.1"
|
||||||
resolved "https://registry.npmmirror.com/react-router/-/react-router-6.12.1.tgz#9e4126aa1139ec6b5d347e19576d5e940cd46362"
|
resolved "https://registry.yarnpkg.com/react-router/-/react-router-6.14.1.tgz#5e82bcdabf21add859dc04b1859f91066b3a5810"
|
||||||
|
integrity sha512-U4PfgvG55LdvbQjg5Y9QRWyVxIdO1LlpYT7x+tMAxd9/vmiPuJhIwdxZuIQLN/9e3O4KFDHYfR9gzGeYMasW8g==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@remix-run/router" "1.6.3"
|
"@remix-run/router" "1.7.1"
|
||||||
|
|
||||||
react-side-effect@^2.1.0:
|
react-side-effect@^2.1.0:
|
||||||
version "2.1.2"
|
version "2.1.2"
|
||||||
|
Loading…
Reference in New Issue
Block a user