YANG QIA bd942342b0
fix(auth): should set user token as invalid when changing password (#5212)
* fix(auth): should log user out when changing password

* fix: add passwordChangeTZ

* fix: clear local token when token is invalid

* fix: test

* fix: field name
2024-09-06 14:43:14 +08:00

132 lines
2.8 KiB
TypeScript

/**
* This file is part of the NocoBase (R) project.
* Copyright (c) 2020-2024 NocoBase Co., Ltd.
* Authors: NocoBase Team.
*
* This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
* For more information, please refer to: https://www.nocobase.com/agreement.
*/
import { Collection, Model } from '@nocobase/database';
import { Auth, AuthConfig } from '../auth';
import { JwtService } from './jwt-service';
import { Cache } from '@nocobase/cache';
/**
* BaseAuth
* @description A base class with jwt provide some common methods.
*/
export class BaseAuth extends Auth {
protected userCollection: Collection;
constructor(
config: AuthConfig & {
userCollection: Collection;
},
) {
const { userCollection } = config;
super(config);
this.userCollection = userCollection;
}
get userRepository() {
return this.userCollection.repository;
}
/**
* @internal
*/
get jwt(): JwtService {
return this.ctx.app.authManager.jwt;
}
set user(user: Model) {
this.ctx.state.currentUser = user;
}
get user() {
return this.ctx.state.currentUser;
}
/**
* @internal
*/
getCacheKey(userId: number) {
return `auth:${userId}`;
}
/**
* @internal
*/
validateUsername(username: string) {
return /^[^@.<>"'/]{1,50}$/.test(username);
}
async check() {
const token = this.ctx.getBearerToken();
if (!token) {
return null;
}
try {
const { userId, roleName, iat, temp } = await this.jwt.decode(token);
if (roleName) {
this.ctx.headers['x-role'] = roleName;
}
const cache = this.ctx.cache as Cache;
const user = await cache.wrap(this.getCacheKey(userId), () =>
this.userRepository.findOne({
filter: {
id: userId,
},
raw: true,
}),
);
if (temp && user.passwordChangeTz && iat * 1000 < user.passwordChangeTz) {
throw new Error('Token is invalid');
}
return user;
} catch (err) {
this.ctx.logger.error(err, { method: 'check' });
return null;
}
}
async validate(): Promise<Model> {
return null;
}
async signIn() {
let user: Model;
try {
user = await this.validate();
} catch (err) {
this.ctx.throw(err.status || 401, err.message);
}
if (!user) {
this.ctx.throw(401, 'Unauthorized');
}
const token = this.jwt.sign({
userId: user.id,
temp: true,
});
return {
user,
token,
};
}
async signOut(): Promise<any> {
const token = this.ctx.getBearerToken();
if (!token) {
return;
}
const { userId } = await this.jwt.decode(token);
await this.ctx.app.emitAsync('cache:del:roles', { userId });
await this.ctx.cache.del(this.getCacheKey(userId));
return await this.jwt.block(token);
}
}