import { computed, Injectable, Signal } from '@angular/core'; import { GrpcService } from './grpc.service'; import { AddHumanUserRequestSchema, AddHumanUserResponse, CreateInviteCodeRequestSchema, CreateInviteCodeResponse, CreatePasskeyRegistrationLinkRequestSchema, CreatePasskeyRegistrationLinkResponse, DeactivateUserRequestSchema, DeactivateUserResponse, DeleteUserRequestSchema, DeleteUserResponse, GetUserByIDResponse, ListAuthenticationFactorsRequestSchema, ListAuthenticationFactorsResponse, ListPasskeysRequestSchema, ListPasskeysResponse, ListUsersRequestSchema, ListUsersResponse, ReactivateUserRequestSchema, ReactivateUserResponse, RemoveOTPEmailRequestSchema, RemoveOTPEmailResponse, RemoveOTPSMSRequestSchema, RemoveOTPSMSResponse, RemovePasskeyRequestSchema, RemovePasskeyResponse, RemovePhoneRequestSchema, RemovePhoneResponse, RemoveTOTPRequestSchema, RemoveTOTPResponse, RemoveU2FRequestSchema, RemoveU2FResponse, ResendInviteCodeRequestSchema, ResendInviteCodeResponse, SetEmailRequestSchema, SetEmailResponse, SetPasswordRequestSchema, SetPasswordResponse, SetPhoneRequestSchema, SetPhoneResponse, UnlockUserRequestSchema, UnlockUserResponse, UpdateHumanUserRequestSchema, UpdateHumanUserResponse, } from '@zitadel/proto/zitadel/user/v2/user_service_pb'; import type { MessageInitShape } from '@bufbuild/protobuf'; import { create } from '@bufbuild/protobuf'; import { OAuthService } from 'angular-oauth2-oidc'; import { filter, map } from 'rxjs/operators'; import { toSignal } from '@angular/core/rxjs-interop'; import { injectQuery, queryOptions, skipToken } from '@tanstack/angular-query-experimental'; @Injectable({ providedIn: 'root', }) export class UserService { private readonly payload: Signal; public readonly userId: Signal; public readonly isExpired: Signal; public userQuery() { return injectQuery(() => this.userQueryOptions()); } public userQueryOptions() { const userId = this.userId(); return queryOptions({ queryKey: [userId, 'user'], queryFn: userId ? () => this.getUserById(userId).then((resp) => resp.user) : skipToken, }); } constructor( private readonly grpcService: GrpcService, private readonly oauthService: OAuthService, ) { this.payload = this.getPayload(); this.userId = this.getUserId(this.payload); this.isExpired = this.getIsExpired(this.payload); } private getPayload() { const idToken$ = this.oauthService.events.pipe( filter((event) => event.type === 'token_received'), // can actually return null // https://github.com/manfredsteyer/angular-oauth2-oidc/blob/c724ad73eadbb28338b084e3afa5ed49a0ea058c/projects/lib/src/oauth-service.ts#L2365 map(() => this.oauthService.getIdToken() as string | null), ); const idToken = toSignal(idToken$, { initialValue: this.oauthService.getIdToken() as string | null }); return computed(() => { try { // split jwt and get base64 encoded payload const unparsedPayload = atob((idToken() ?? '').split('.')[1]); // parse payload return JSON.parse(unparsedPayload) as unknown; } catch { return undefined; } }); } private getUserId(payloadSignal: Signal) { return computed(() => { const payload = payloadSignal(); if (payload && typeof payload === 'object' && 'sub' in payload && typeof payload.sub === 'string') { return payload.sub; } return undefined; }); } private getIsExpired(payloadSignal: Signal) { const expSignal = computed(() => { const payload = payloadSignal(); if (payload && typeof payload === 'object' && 'exp' in payload && typeof payload.exp === 'number') { return new Date(payload.exp * 1000); } return undefined; }); return computed(() => { const exp = expSignal(); return exp ? exp <= new Date() : true; }); } public addHumanUser(req: MessageInitShape): Promise { return this.grpcService.userNew.addHumanUser(create(AddHumanUserRequestSchema, req)); } public listUsers(req: MessageInitShape): Promise { return this.grpcService.userNew.listUsers(req); } public getUserById(userId: string): Promise { return this.grpcService.userNew.getUserByID({ userId }); } public deactivateUser(userId: string): Promise { return this.grpcService.userNew.deactivateUser(create(DeactivateUserRequestSchema, { userId })); } public reactivateUser(userId: string): Promise { return this.grpcService.userNew.reactivateUser(create(ReactivateUserRequestSchema, { userId })); } public deleteUser(userId: string): Promise { return this.grpcService.userNew.deleteUser(create(DeleteUserRequestSchema, { userId })); } public updateUser(req: MessageInitShape): Promise { return this.grpcService.userNew.updateHumanUser(create(UpdateHumanUserRequestSchema, req)); } public unlockUser(userId: string): Promise { return this.grpcService.userNew.unlockUser(create(UnlockUserRequestSchema, { userId })); } public listAuthenticationFactors( req: MessageInitShape, ): Promise { return this.grpcService.userNew.listAuthenticationFactors(create(ListAuthenticationFactorsRequestSchema, req)); } public listPasskeys(req: MessageInitShape): Promise { return this.grpcService.userNew.listPasskeys(create(ListPasskeysRequestSchema, req)); } public removePasskeys(req: MessageInitShape): Promise { return this.grpcService.userNew.removePasskey(create(RemovePasskeyRequestSchema, req)); } public createPasskeyRegistrationLink( req: MessageInitShape, ): Promise { return this.grpcService.userNew.createPasskeyRegistrationLink(create(CreatePasskeyRegistrationLinkRequestSchema, req)); } public removePhone(userId: string): Promise { return this.grpcService.userNew.removePhone(create(RemovePhoneRequestSchema, { userId })); } public setPhone(req: MessageInitShape): Promise { return this.grpcService.userNew.setPhone(create(SetPhoneRequestSchema, req)); } public setEmail(req: MessageInitShape): Promise { return this.grpcService.userNew.setEmail(create(SetEmailRequestSchema, req)); } public removeTOTP(userId: string): Promise { return this.grpcService.userNew.removeTOTP(create(RemoveTOTPRequestSchema, { userId })); } public removeU2F(userId: string, u2fId: string): Promise { return this.grpcService.userNew.removeU2F(create(RemoveU2FRequestSchema, { userId, u2fId })); } public removeOTPSMS(userId: string): Promise { return this.grpcService.userNew.removeOTPSMS(create(RemoveOTPSMSRequestSchema, { userId })); } public removeOTPEmail(userId: string): Promise { return this.grpcService.userNew.removeOTPEmail(create(RemoveOTPEmailRequestSchema, { userId })); } public resendInviteCode(userId: string): Promise { return this.grpcService.userNew.resendInviteCode(create(ResendInviteCodeRequestSchema, { userId })); } public createInviteCode(req: MessageInitShape): Promise { return this.grpcService.userNew.createInviteCode(create(CreateInviteCodeRequestSchema, req)); } public setPassword(req: MessageInitShape): Promise { return this.grpcService.userNew.setPassword(create(SetPasswordRequestSchema, req)); } }