import { CrossServiceTokenData, TokenEncoderInterface } from '@standardnotes/security' import { ErrorTag } from '@standardnotes/common' import { SettingName } from '@standardnotes/settings' import { Request, Response } from 'express' import { inject } from 'inversify' import { BaseHttpController, controller, httpPost, // eslint-disable-next-line @typescript-eslint/no-unused-vars results, } from 'inversify-express-utils' import TYPES from '../Bootstrap/Types' import { Role } from '../Domain/Role/Role' import { SettingServiceInterface } from '../Domain/Setting/SettingServiceInterface' import { AuthenticateSubscriptionToken } from '../Domain/UseCase/AuthenticateSubscriptionToken/AuthenticateSubscriptionToken' import { CreateSubscriptionToken } from '../Domain/UseCase/CreateSubscriptionToken/CreateSubscriptionToken' import { User } from '../Domain/User/User' import { ProjectorInterface } from '../Projection/ProjectorInterface' @controller('/subscription-tokens') export class SubscriptionTokensController extends BaseHttpController { constructor( @inject(TYPES.CreateSubscriptionToken) private createSubscriptionToken: CreateSubscriptionToken, @inject(TYPES.AuthenticateSubscriptionToken) private authenticateToken: AuthenticateSubscriptionToken, @inject(TYPES.SettingService) private settingService: SettingServiceInterface, @inject(TYPES.UserProjector) private userProjector: ProjectorInterface, @inject(TYPES.RoleProjector) private roleProjector: ProjectorInterface, @inject(TYPES.CrossServiceTokenEncoder) private tokenEncoder: TokenEncoderInterface, @inject(TYPES.AUTH_JWT_TTL) private jwtTTL: number, ) { super() } @httpPost('/', TYPES.ApiGatewayAuthMiddleware) async createToken(_request: Request, response: Response): Promise { if (response.locals.readOnlyAccess) { return this.json( { error: { tag: ErrorTag.ReadOnlyAccess, message: 'Session has read-only access.', }, }, 401, ) } const result = await this.createSubscriptionToken.execute({ userUuid: response.locals.user.uuid, }) return this.json({ token: result.subscriptionToken.token, }) } @httpPost('/:token/validate') async validate(request: Request): Promise { const authenticateTokenResponse = await this.authenticateToken.execute({ token: request.params.token, }) if (!authenticateTokenResponse.success) { return this.json( { error: { tag: 'invalid-auth', message: 'Invalid login credentials.', }, }, 401, ) } const user = authenticateTokenResponse.user as User let extensionKey = undefined const extensionKeySetting = await this.settingService.findSettingWithDecryptedValue({ settingName: SettingName.ExtensionKey, userUuid: user.uuid, }) if (extensionKeySetting !== null) { extensionKey = extensionKeySetting.value as string } const roles = await user.roles const authTokenData: CrossServiceTokenData = { user: await this.projectUser(user), roles: await this.projectRoles(roles), extensionKey, } const authToken = this.tokenEncoder.encodeExpirableToken(authTokenData, this.jwtTTL) return this.json({ authToken }) } private async projectUser(user: User): Promise<{ uuid: string; email: string }> { return <{ uuid: string; email: string }>await this.userProjector.projectSimple(user) } private async projectRoles(roles: Array): Promise> { const roleProjections = [] for (const role of roles) { roleProjections.push(<{ uuid: string; name: string }>await this.roleProjector.projectSimple(role)) } return roleProjections } }