1
0
mirror of https://github.com/Unleash/unleash.git synced 2025-01-06 00:07:44 +01:00
unleash.unleash/src/lib/services/public-signup-token-service.ts
Nuno Góis 87d9497be9
refactor: prefer eventService.storeEvent methods (#4830)
https://linear.app/unleash/issue/2-1403/consider-refactoring-the-way-tags-are-fetched-for-the-events

This adds 2 methods to `EventService`:
 - `storeEvent`;
 - `storeEvents`;

This allows us to run event-specific logic inside these methods. In the
case of this PR, this means fetching the feature tags in case the event
contains a `featureName` and there are no tags specified in the event.

This prevents us from having to remember to fetch the tags in order to
store feature-related events except for very specific cases, like the
deletion of a feature - You can't fetch tags for a feature that no
longer exists, so in that case we need to pre-fetch the tags before
deleting the feature.

This also allows us to do any event-specific post-processing to the
event before reaching the DB layer.
In general I think it's also nicer that we reference the event service
instead of the event store directly.

There's a lot of changes and a lot of files touched, but most of it is
boilerplate to inject the `eventService` where needed instead of using
the `eventStore` directly.

Hopefully this will be a better approach than
https://github.com/Unleash/unleash/pull/4729

---------

Co-authored-by: Gastón Fournier <gaston@getunleash.io>
2023-09-27 14:23:05 +01:00

155 lines
4.9 KiB
TypeScript

import crypto from 'crypto';
import { Logger } from '../logger';
import { IUnleashConfig, IUnleashStores } from '../types';
import { IPublicSignupTokenStore } from '../types/stores/public-signup-token-store';
import { PublicSignupTokenSchema } from '../openapi/spec/public-signup-token-schema';
import { IRoleStore } from '../types/stores/role-store';
import { IPublicSignupTokenCreate } from '../types/models/public-signup-token';
import { PublicSignupTokenCreateSchema } from '../openapi/spec/public-signup-token-create-schema';
import { CreateInvitedUserSchema } from 'lib/openapi/spec/create-invited-user-schema';
import { RoleName } from '../types/model';
import {
PublicSignupTokenCreatedEvent,
PublicSignupTokenUpdatedEvent,
PublicSignupTokenUserAddedEvent,
} from '../types/events';
import UserService from './user-service';
import { IUser } from '../types/user';
import { URL } from 'url';
import { add } from 'date-fns';
import EventService from './event-service';
export class PublicSignupTokenService {
private store: IPublicSignupTokenStore;
private roleStore: IRoleStore;
private userService: UserService;
private eventService: EventService;
private logger: Logger;
private timer: NodeJS.Timeout;
private readonly unleashBase: string;
constructor(
{
publicSignupTokenStore,
roleStore,
}: Pick<IUnleashStores, 'publicSignupTokenStore' | 'roleStore'>,
config: Pick<IUnleashConfig, 'getLogger' | 'authentication' | 'server'>,
userService: UserService,
eventService: EventService,
) {
this.store = publicSignupTokenStore;
this.userService = userService;
this.eventService = eventService;
this.roleStore = roleStore;
this.logger = config.getLogger(
'/services/public-signup-token-service.ts',
);
this.unleashBase = config.server.unleashUrl;
}
private getUrl(secret: string): string {
return new URL(
`${this.unleashBase}/new-user?invite=${secret}`,
).toString();
}
public async get(secret: string): Promise<PublicSignupTokenSchema> {
return this.store.get(secret);
}
public async getAllTokens(): Promise<PublicSignupTokenSchema[]> {
return this.store.getAll();
}
public async getAllActiveTokens(): Promise<PublicSignupTokenSchema[]> {
return this.store.getAllActive();
}
public async validate(secret: string): Promise<boolean> {
return this.store.isValid(secret);
}
public async update(
secret: string,
{ expiresAt, enabled }: { expiresAt?: Date; enabled?: boolean },
createdBy: string,
): Promise<PublicSignupTokenSchema> {
const result = await this.store.update(secret, { expiresAt, enabled });
await this.eventService.storeEvent(
new PublicSignupTokenUpdatedEvent({
createdBy,
data: { secret, enabled, expiresAt },
}),
);
return result;
}
public async addTokenUser(
secret: string,
createUser: CreateInvitedUserSchema,
): Promise<IUser> {
const token = await this.get(secret);
const user = await this.userService.createUser({
...createUser,
rootRole: token.role.id,
});
await this.store.addTokenUser(secret, user.id);
await this.eventService.storeEvent(
new PublicSignupTokenUserAddedEvent({
createdBy: 'System',
data: { secret, userId: user.id },
}),
);
return user;
}
public async createNewPublicSignupToken(
tokenCreate: PublicSignupTokenCreateSchema,
createdBy: string,
): Promise<PublicSignupTokenSchema> {
const viewerRole = await this.roleStore.getRoleByName(RoleName.VIEWER);
const secret = this.generateSecretKey();
const url = this.getUrl(secret);
const cappedDate = this.getMinimumDate(
new Date(tokenCreate.expiresAt),
add(new Date(), { months: 1 }),
);
const newToken: IPublicSignupTokenCreate = {
name: tokenCreate.name,
expiresAt: cappedDate,
secret: secret,
roleId: viewerRole ? viewerRole.id : -1,
createdBy: createdBy,
url: url,
};
const token = await this.store.insert(newToken);
await this.eventService.storeEvent(
new PublicSignupTokenCreatedEvent({
createdBy: createdBy,
data: token,
}),
);
return token;
}
private generateSecretKey(): string {
return crypto.randomBytes(16).toString('hex');
}
private getMinimumDate(date1: Date, date2: Date): Date {
return date1 < date2 ? date1 : date2;
}
destroy(): void {
clearInterval(this.timer);
this.timer = null;
}
}