2022-09-14 14:29:12 +02:00
|
|
|
import { EventEmitter } from 'events';
|
|
|
|
import { Knex } from 'knex';
|
|
|
|
import metricsHelper from '../util/metrics-helper';
|
|
|
|
import { DB_TIME } from '../metric-events';
|
|
|
|
import { Logger, LogProvider } from '../logger';
|
|
|
|
import NotFoundError from '../error/notfound-error';
|
|
|
|
import { PublicSignupTokenSchema } from '../openapi/spec/public-signup-token-schema';
|
|
|
|
import { IPublicSignupTokenStore } from '../types/stores/public-signup-token-store';
|
|
|
|
import { UserSchema } from '../openapi/spec/user-schema';
|
|
|
|
import { IPublicSignupTokenCreate } from '../types/models/public-signup-token';
|
|
|
|
|
|
|
|
const TABLE = 'public_signup_tokens';
|
|
|
|
const TOKEN_USERS_TABLE = 'public_signup_tokens_user';
|
|
|
|
|
|
|
|
interface ITokenInsert {
|
|
|
|
secret: string;
|
|
|
|
name: string;
|
|
|
|
expires_at: Date;
|
|
|
|
created_at: Date;
|
|
|
|
created_by?: string;
|
|
|
|
role_id: number;
|
|
|
|
}
|
|
|
|
|
|
|
|
interface ITokenRow extends ITokenInsert {
|
|
|
|
users: UserSchema[];
|
|
|
|
}
|
|
|
|
|
|
|
|
interface ITokenUserRow {
|
|
|
|
secret: string;
|
|
|
|
user_id: number;
|
|
|
|
created_at: Date;
|
|
|
|
}
|
2022-09-26 12:06:30 +02:00
|
|
|
|
2022-09-14 14:29:12 +02:00
|
|
|
const tokenRowReducer = (acc, tokenRow) => {
|
2022-09-30 13:01:32 +02:00
|
|
|
const {
|
|
|
|
userId,
|
|
|
|
userName,
|
|
|
|
userUsername,
|
|
|
|
roleId,
|
|
|
|
roleName,
|
|
|
|
roleType,
|
|
|
|
...token
|
|
|
|
} = tokenRow;
|
2022-09-14 14:29:12 +02:00
|
|
|
if (!acc[tokenRow.secret]) {
|
|
|
|
acc[tokenRow.secret] = {
|
|
|
|
secret: token.secret,
|
|
|
|
name: token.name,
|
2022-09-26 12:06:30 +02:00
|
|
|
url: token.url,
|
2022-09-14 14:29:12 +02:00
|
|
|
expiresAt: token.expires_at,
|
2022-09-30 13:01:32 +02:00
|
|
|
enabled: token.enabled,
|
2022-09-14 14:29:12 +02:00
|
|
|
createdAt: token.created_at,
|
|
|
|
createdBy: token.created_by,
|
2022-09-26 12:06:30 +02:00
|
|
|
role: {
|
|
|
|
id: roleId,
|
|
|
|
name: roleName,
|
2022-09-30 13:01:32 +02:00
|
|
|
type: roleType,
|
2022-09-26 12:06:30 +02:00
|
|
|
},
|
2022-09-14 14:29:12 +02:00
|
|
|
users: [],
|
|
|
|
};
|
|
|
|
}
|
|
|
|
const currentToken = acc[tokenRow.secret];
|
|
|
|
if (userId) {
|
2022-09-26 12:06:30 +02:00
|
|
|
currentToken.users.push({
|
|
|
|
id: userId,
|
|
|
|
name: userName,
|
|
|
|
username: userUsername,
|
|
|
|
});
|
2022-09-14 14:29:12 +02:00
|
|
|
}
|
|
|
|
return acc;
|
|
|
|
};
|
|
|
|
|
|
|
|
const toRow = (newToken: IPublicSignupTokenCreate) => {
|
|
|
|
if (!newToken) return;
|
|
|
|
return {
|
|
|
|
secret: newToken.secret,
|
|
|
|
name: newToken.name,
|
|
|
|
expires_at: newToken.expiresAt,
|
|
|
|
created_by: newToken.createdBy || null,
|
|
|
|
role_id: newToken.roleId,
|
2022-09-26 12:06:30 +02:00
|
|
|
url: newToken.url,
|
2022-09-14 14:29:12 +02:00
|
|
|
};
|
|
|
|
};
|
|
|
|
|
|
|
|
const toTokens = (rows: any[]): PublicSignupTokenSchema[] => {
|
|
|
|
const tokens = rows.reduce(tokenRowReducer, {});
|
|
|
|
return Object.values(tokens);
|
|
|
|
};
|
|
|
|
|
|
|
|
export class PublicSignupTokenStore implements IPublicSignupTokenStore {
|
|
|
|
private logger: Logger;
|
|
|
|
|
|
|
|
private timer: Function;
|
|
|
|
|
|
|
|
private db: Knex;
|
|
|
|
|
|
|
|
constructor(db: Knex, eventBus: EventEmitter, getLogger: LogProvider) {
|
|
|
|
this.db = db;
|
|
|
|
this.logger = getLogger('public-signup-tokens.js');
|
|
|
|
this.timer = (action: string) =>
|
|
|
|
metricsHelper.wrapTimer(eventBus, DB_TIME, {
|
|
|
|
store: 'public-signup-tokens',
|
|
|
|
action,
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
count(): Promise<number> {
|
|
|
|
return this.db(TABLE)
|
|
|
|
.count('*')
|
|
|
|
.then((res) => Number(res[0].count));
|
|
|
|
}
|
|
|
|
|
|
|
|
private makeTokenUsersQuery() {
|
|
|
|
return this.db<ITokenRow>(`${TABLE} as tokens`)
|
|
|
|
.leftJoin(
|
|
|
|
`${TOKEN_USERS_TABLE} as token_project_users`,
|
|
|
|
'tokens.secret',
|
|
|
|
'token_project_users.secret',
|
|
|
|
)
|
|
|
|
.leftJoin(`users`, 'token_project_users.user_id', 'users.id')
|
2022-09-26 12:06:30 +02:00
|
|
|
.leftJoin(`roles`, 'tokens.role_id', 'roles.id')
|
2022-09-14 14:29:12 +02:00
|
|
|
.select(
|
|
|
|
'tokens.secret',
|
|
|
|
'tokens.name',
|
|
|
|
'tokens.expires_at',
|
2022-09-30 13:01:32 +02:00
|
|
|
'tokens.enabled',
|
2022-09-14 14:29:12 +02:00
|
|
|
'tokens.created_at',
|
|
|
|
'tokens.created_by',
|
2022-09-26 12:06:30 +02:00
|
|
|
'tokens.url',
|
|
|
|
'token_project_users.user_id as userId',
|
|
|
|
'users.name as userName',
|
|
|
|
'users.username as userUsername',
|
|
|
|
'roles.id as roleId',
|
|
|
|
'roles.name as roleName',
|
2022-09-30 13:01:32 +02:00
|
|
|
'roles.type as roleType',
|
2022-09-14 14:29:12 +02:00
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
async getAll(): Promise<PublicSignupTokenSchema[]> {
|
|
|
|
const stopTimer = this.timer('getAll');
|
|
|
|
const rows = await this.makeTokenUsersQuery();
|
|
|
|
stopTimer();
|
|
|
|
return toTokens(rows);
|
|
|
|
}
|
|
|
|
|
|
|
|
async getAllActive(): Promise<PublicSignupTokenSchema[]> {
|
|
|
|
const stopTimer = this.timer('getAllActive');
|
|
|
|
const rows = await this.makeTokenUsersQuery()
|
|
|
|
.where('expires_at', 'IS', null)
|
|
|
|
.orWhere('expires_at', '>', 'now()');
|
|
|
|
stopTimer();
|
|
|
|
return toTokens(rows);
|
|
|
|
}
|
|
|
|
|
|
|
|
async addTokenUser(secret: string, userId: number): Promise<void> {
|
|
|
|
await this.db<ITokenUserRow>(TOKEN_USERS_TABLE).insert(
|
|
|
|
{ user_id: userId, secret },
|
|
|
|
['created_at'],
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
async insert(
|
|
|
|
newToken: IPublicSignupTokenCreate,
|
|
|
|
): Promise<PublicSignupTokenSchema> {
|
|
|
|
const response = await this.db<ITokenRow>(TABLE).insert(
|
|
|
|
toRow(newToken),
|
2022-09-26 15:50:54 +02:00
|
|
|
['secret'],
|
2022-09-14 14:29:12 +02:00
|
|
|
);
|
2022-09-26 15:50:54 +02:00
|
|
|
return this.get(response[0].secret);
|
2022-09-14 14:29:12 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
async isValid(secret: string): Promise<boolean> {
|
|
|
|
const result = await this.db.raw(
|
2022-09-30 13:01:32 +02:00
|
|
|
`SELECT EXISTS (SELECT 1 FROM ${TABLE} WHERE secret = ? AND expires_at::date > ? AND enabled = true) AS valid`,
|
2022-09-14 14:29:12 +02:00
|
|
|
[secret, new Date()],
|
|
|
|
);
|
|
|
|
const { valid } = result.rows[0];
|
|
|
|
return valid;
|
|
|
|
}
|
|
|
|
|
|
|
|
destroy(): void {}
|
|
|
|
|
|
|
|
async exists(secret: string): Promise<boolean> {
|
|
|
|
const result = await this.db.raw(
|
|
|
|
`SELECT EXISTS (SELECT 1 FROM ${TABLE} WHERE secret = ?) AS present`,
|
|
|
|
[secret],
|
|
|
|
);
|
|
|
|
const { present } = result.rows[0];
|
|
|
|
return present;
|
|
|
|
}
|
|
|
|
|
|
|
|
async get(key: string): Promise<PublicSignupTokenSchema> {
|
2022-09-26 12:06:30 +02:00
|
|
|
const rows = await this.makeTokenUsersQuery().where(
|
|
|
|
'tokens.secret',
|
|
|
|
key,
|
|
|
|
);
|
2022-09-14 14:29:12 +02:00
|
|
|
|
2022-09-26 12:06:30 +02:00
|
|
|
if (rows.length > 0) {
|
|
|
|
return toTokens(rows)[0];
|
|
|
|
}
|
|
|
|
throw new NotFoundError('Could not find public signup token.');
|
2022-09-14 14:29:12 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
async delete(secret: string): Promise<void> {
|
|
|
|
return this.db<ITokenInsert>(TABLE).where({ secret }).del();
|
|
|
|
}
|
|
|
|
|
|
|
|
async deleteAll(): Promise<void> {
|
|
|
|
return this.db<ITokenInsert>(TABLE).del();
|
|
|
|
}
|
|
|
|
|
2022-09-30 13:01:32 +02:00
|
|
|
async update(
|
2022-09-14 14:29:12 +02:00
|
|
|
secret: string,
|
2022-09-30 13:01:32 +02:00
|
|
|
{ expiresAt, enabled }: { expiresAt?: Date; enabled?: boolean },
|
2022-09-14 14:29:12 +02:00
|
|
|
): Promise<PublicSignupTokenSchema> {
|
|
|
|
const rows = await this.makeTokenUsersQuery()
|
2022-09-30 13:01:32 +02:00
|
|
|
.update({ expires_at: expiresAt, enabled })
|
2022-09-14 14:29:12 +02:00
|
|
|
.where('secret', secret)
|
|
|
|
.returning('*');
|
|
|
|
if (rows.length > 0) {
|
|
|
|
return toTokens(rows)[0];
|
|
|
|
}
|
|
|
|
throw new NotFoundError('Could not find public signup token.');
|
|
|
|
}
|
|
|
|
}
|