first commit

This commit is contained in:
2026-10-03 13:40:26 +02:00
parent 732a5b5a16
commit 566f415fa7
555 changed files with 62704 additions and 0 deletions

View File

@@ -0,0 +1,57 @@
import { Logger } from '@nestjs/common';
import { Command, CommandHandler, ICommandHandler } from '@nestjs/cqrs';
import { SessionCache } from '../ports/session-cache.port';
import { UserRegistry } from '../ports/user-registry.port';
/** Reacts to Pireon webhooks: `user.deactivated` and `token.revoked`. */
export class HandleIdentityWebhookCommand extends Command<void> {
constructor(
public readonly event: string,
public readonly data: Record<string, unknown>,
) {
super();
}
}
@CommandHandler(HandleIdentityWebhookCommand)
export class HandleIdentityWebhookHandler
implements ICommandHandler<HandleIdentityWebhookCommand>
{
private readonly logger = new Logger(HandleIdentityWebhookHandler.name);
constructor(
private readonly cache: SessionCache,
private readonly registry: UserRegistry,
) {}
async execute({ event, data }: HandleIdentityWebhookCommand): Promise<void> {
const userId =
data['user_id'] ??
data['userId'] ??
(data['user'] as { id?: unknown } | undefined)?.id;
if (userId === undefined || userId === null) {
this.logger.warn(`Webhook ${event} without user id, ignored`);
return;
}
const id = String(userId);
switch (event) {
case 'user.deactivated':
await Promise.all([this.deactivate(id), this.cache.evictUser(id)]);
break;
case 'token.revoked':
// Only token hashes are cached, so drop every session of that user.
await this.cache.evictUser(id);
break;
default:
this.logger.debug(`Unhandled identity webhook ${event}`);
}
}
private async deactivate(userId: string): Promise<void> {
const user = await this.registry.findById(userId);
if (!user) return;
user.deactivate();
await this.registry.upsert(user);
}
}

View File

@@ -0,0 +1,19 @@
import { Command, CommandHandler, ICommandHandler } from '@nestjs/cqrs';
import { TokenGrant, TokenSet } from '@task-stream/api/identity/domain';
import { IdentityProvider } from '../ports/identity-provider.port';
/** Signs a user in (or refreshes the session) against the identity server. */
export class IssueTokenCommand extends Command<TokenSet> {
constructor(public readonly grant: TokenGrant) {
super();
}
}
@CommandHandler(IssueTokenCommand)
export class IssueTokenHandler implements ICommandHandler<IssueTokenCommand> {
constructor(private readonly provider: IdentityProvider) {}
execute({ grant }: IssueTokenCommand): Promise<TokenSet> {
return this.provider.issueToken(grant);
}
}