This repository has no description
Something went wrong. Try again.
4.4 kB · 114 lines
TypeScript
at main
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115// SPDX-License-Identifier: AGPL-3.0-or-later
import {createHmac} from 'node:crypto';import { CONNECTION_INITIATION_TOKEN_EXPIRY_MS, CONNECTION_VERIFICATION_TOKEN_LENGTH, type ConnectionType, ConnectionTypes,} from '@fluxer/constants/src/ConnectionConstants';import type { ConnectionResponse, ConnectionVerificationResponse, CreateConnectionRequest, UpdateConnectionRequest, VerifyAndCreateConnectionRequest,} from '@fluxer/schema/src/domains/connection/ConnectionSchemas';import type {UserID} from '../BrandedTypes';import {signInitiationToken, verifyInitiationToken} from './ConnectionInitiationToken';import {mapConnectionToResponse} from './ConnectionMappers';import {ConnectionInitiationTokenInvalidError} from './errors/ConnectionInitiationTokenInvalidError';import type {IConnectionService} from './IConnectionService';
export class ConnectionRequestService { constructor( private readonly connectionService: IConnectionService, private readonly connectionInitiationSecret: string, ) {}
async listConnections(userId: UserID): Promise<Array<ConnectionResponse>> { const rows = await this.connectionService.getConnectionsForUser(userId); return rows.sort((a, b) => a.sort_order - b.sort_order).map((row) => mapConnectionToResponse(row)); }
async initiateConnection(userId: UserID, body: CreateConnectionRequest): Promise<ConnectionVerificationResponse> { await this.connectionService.initiateConnection(userId, body.type, body.identifier); const verificationCode = this.generateVerificationCode(userId, body.type, body.identifier); const instructions = this.generateVerificationInstructions(body.type, body.identifier); const initiationToken = signInitiationToken( { userId: String(userId), type: body.type, identifier: body.identifier, verificationCode, expiresAt: Date.now() + CONNECTION_INITIATION_TOKEN_EXPIRY_MS, }, this.connectionInitiationSecret, ); return { token: verificationCode, type: body.type, id: body.identifier, instructions, initiation_token: initiationToken, }; }
async verifyAndCreateConnection(userId: UserID, body: VerifyAndCreateConnectionRequest): Promise<ConnectionResponse> { const payload = verifyInitiationToken(body.initiation_token, this.connectionInitiationSecret); if (!payload || payload.userId !== String(userId)) { throw new ConnectionInitiationTokenInvalidError(); } const row = await this.connectionService.verifyAndCreateConnection( userId, payload.type, payload.identifier, payload.verificationCode, body.visibility_flags ?? 1, ); return mapConnectionToResponse(row); }
async updateConnection( userId: UserID, connectionType: ConnectionType, connectionId: string, body: UpdateConnectionRequest, ): Promise<void> { await this.connectionService.updateConnection(userId, connectionType, connectionId, body); }
async deleteConnection(userId: UserID, connectionType: ConnectionType, connectionId: string): Promise<void> { await this.connectionService.deleteConnection(userId, connectionType, connectionId); }
async verifyConnection( userId: UserID, connectionType: ConnectionType, connectionId: string, ): Promise<ConnectionResponse> { const row = await this.connectionService.verifyConnection(userId, connectionType, connectionId); return mapConnectionToResponse(row); }
async reorderConnections(userId: UserID, connectionIds: Array<string>): Promise<void> { await this.connectionService.reorderConnections(userId, connectionIds); }
private generateVerificationInstructions(connectionType: ConnectionType, identifier: string): string { switch (connectionType) { case ConnectionTypes.DOMAIN: return `Add a DNS TXT record at _fluxer.${identifier} with the value fluxer-verification=<token>, or serve the token at https://${identifier}/.well-known/fluxer-verification`; default: return 'Follow the platform-specific verification instructions'; } }
private generateVerificationCode(userId: UserID, connectionType: ConnectionType, identifier: string): string { const normalizedIdentifier = identifier.toLowerCase(); return createHmac('sha256', this.connectionInitiationSecret) .update(`connection-verification:v1:${String(userId)}:${connectionType}:${normalizedIdentifier}`) .digest('hex') .slice(0, CONNECTION_VERIFICATION_TOKEN_LENGTH * 2); }}