1
0
mirror of https://github.com/Unleash/unleash.git synced 2024-12-22 19:07:54 +01:00

feat: update seen_at pat column (#2516)

https://linear.app/unleash/issue/2-451/update-last-seen-column-for-pats
This commit is contained in:
Nuno Góis 2022-11-30 06:10:31 +00:00 committed by GitHub
parent 4d8817698a
commit 7ce38ffe89
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 59 additions and 1 deletions

View File

@ -228,6 +228,17 @@ class UserStore implements IUserStore {
.first(); .first();
return rowToUser(row); return rowToUser(row);
} }
async markSeenAt(secrets: string[]): Promise<void> {
const now = new Date();
try {
await this.db('personal_access_tokens')
.whereIn('secret', secrets)
.update({ seen_at: now });
} catch (err) {
this.logger.error('Could not update lastSeen, error: ', err);
}
}
} }
module.exports = UserStore; module.exports = UserStore;

View File

@ -17,6 +17,7 @@ const patMiddleware = (
apiToken, apiToken,
); );
req.user = user; req.user = user;
userService.addPATSeen(apiToken);
} }
} catch (error) { } catch (error) {
logger.error(error); logger.error(error);

View File

@ -28,6 +28,8 @@ import PasswordMismatch from '../error/password-mismatch';
import BadDataError from '../error/bad-data-error'; import BadDataError from '../error/bad-data-error';
import { isDefined } from '../util/isDefined'; import { isDefined } from '../util/isDefined';
import { TokenUserSchema } from '../openapi/spec/token-user-schema'; import { TokenUserSchema } from '../openapi/spec/token-user-schema';
import { IFlagResolver } from 'lib/types/experimental';
import { minutesToMilliseconds } from 'date-fns';
const systemUser = new User({ id: -1, username: 'system' }); const systemUser = new User({ id: -1, username: 'system' });
@ -78,12 +80,22 @@ class UserService {
private passwordResetTimeouts: { [key: string]: NodeJS.Timeout } = {}; private passwordResetTimeouts: { [key: string]: NodeJS.Timeout } = {};
private seenTimer: NodeJS.Timeout;
private lastSeenSecrets: Set<string> = new Set<string>();
private flagResolver: IFlagResolver;
constructor( constructor(
stores: Pick<IUnleashStores, 'userStore' | 'eventStore'>, stores: Pick<IUnleashStores, 'userStore' | 'eventStore'>,
{ {
getLogger, getLogger,
authentication, authentication,
}: Pick<IUnleashConfig, 'getLogger' | 'authentication'>, flagResolver,
}: Pick<
IUnleashConfig,
'getLogger' | 'authentication' | 'flagResolver'
>,
services: { services: {
accessService: AccessService; accessService: AccessService;
resetTokenService: ResetTokenService; resetTokenService: ResetTokenService;
@ -92,6 +104,7 @@ class UserService {
settingService: SettingService; settingService: SettingService;
}, },
) { ) {
this.flagResolver = flagResolver;
this.logger = getLogger('service/user-service.js'); this.logger = getLogger('service/user-service.js');
this.store = stores.userStore; this.store = stores.userStore;
this.eventStore = stores.eventStore; this.eventStore = stores.eventStore;
@ -103,6 +116,9 @@ class UserService {
if (authentication && authentication.createAdminUser) { if (authentication && authentication.createAdminUser) {
process.nextTick(() => this.initAdminUser()); process.nextTick(() => this.initAdminUser());
} }
if (this.flagResolver.isEnabled('tokensLastSeen')) {
this.updateLastSeen();
}
} }
validatePassword(password: string): boolean { validatePassword(password: string): boolean {
@ -426,6 +442,30 @@ class UserService {
async getUserByPersonalAccessToken(secret: string): Promise<IUser> { async getUserByPersonalAccessToken(secret: string): Promise<IUser> {
return this.store.getUserByPersonalAccessToken(secret); return this.store.getUserByPersonalAccessToken(secret);
} }
async updateLastSeen(): Promise<void> {
if (this.lastSeenSecrets.size > 0) {
const toStore = [...this.lastSeenSecrets];
this.lastSeenSecrets = new Set<string>();
await this.store.markSeenAt(toStore);
}
this.seenTimer = setTimeout(
async () => this.updateLastSeen(),
minutesToMilliseconds(3),
).unref();
}
addPATSeen(secret: string): void {
if (this.flagResolver.isEnabled('tokensLastSeen')) {
this.lastSeenSecrets.add(secret);
}
}
destroy(): void {
clearTimeout(this.seenTimer);
this.seenTimer = null;
}
} }
module.exports = UserService; module.exports = UserService;

View File

@ -33,4 +33,5 @@ export interface IUserStore extends Store<IUser, number> {
successfullyLogin(user: IUser): Promise<void>; successfullyLogin(user: IUser): Promise<void>;
count(): Promise<number>; count(): Promise<number>;
getUserByPersonalAccessToken(secret: string): Promise<IUser>; getUserByPersonalAccessToken(secret: string): Promise<IUser>;
markSeenAt(secrets: string[]): Promise<void>;
} }

View File

@ -142,6 +142,11 @@ class UserStoreMock implements IUserStore {
getUserByPersonalAccessToken(secret: string): Promise<IUser> { getUserByPersonalAccessToken(secret: string): Promise<IUser> {
return Promise.resolve(undefined); return Promise.resolve(undefined);
} }
// eslint-disable-next-line @typescript-eslint/no-unused-vars
async markSeenAt(secrets: string[]): Promise<void> {
throw new Error('Not implemented');
}
} }
module.exports = UserStoreMock; module.exports = UserStoreMock;