first commit
This commit is contained in:
4
libs/api/identity/domain/src/index.ts
Normal file
4
libs/api/identity/domain/src/index.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
export * from './lib/user';
|
||||
export * from './lib/events/user-deactivated.event';
|
||||
export * from './lib/factories/user.factory';
|
||||
export * from './lib/token';
|
||||
@@ -0,0 +1,9 @@
|
||||
import { AutowiredEvent, DomainEvent } from '@task-stream/api/shared/domain';
|
||||
|
||||
/** Pireon deactivated the user. */
|
||||
@AutowiredEvent
|
||||
export class UserDeactivatedEvent extends DomainEvent {
|
||||
constructor(readonly userId: string) {
|
||||
super('identity-server');
|
||||
}
|
||||
}
|
||||
25
libs/api/identity/domain/src/lib/factories/user.factory.ts
Normal file
25
libs/api/identity/domain/src/lib/factories/user.factory.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { assertThat } from '@task-stream/api/shared/domain';
|
||||
import { User, UserSnapshot } from '../user';
|
||||
|
||||
type UserProps = Omit<UserSnapshot, 'roles' | 'appRoles'> &
|
||||
Partial<Pick<UserSnapshot, 'roles' | 'appRoles'>>;
|
||||
|
||||
/** Users are never created here (Pireon owns them): they are only (re)built. */
|
||||
@Injectable()
|
||||
export class UserFactory {
|
||||
create(props: UserProps): User {
|
||||
assertThat(props.id.length > 0, 'User id is required');
|
||||
const user = new User(props.id);
|
||||
user.name = props.name;
|
||||
user.email = props.email;
|
||||
user.isActive = props.isActive;
|
||||
user.roles = [...(props.roles ?? [])];
|
||||
user.appRoles = [...(props.appRoles ?? [])];
|
||||
return user;
|
||||
}
|
||||
|
||||
restore(snapshot: UserProps): User {
|
||||
return this.create(snapshot);
|
||||
}
|
||||
}
|
||||
27
libs/api/identity/domain/src/lib/token.ts
Normal file
27
libs/api/identity/domain/src/lib/token.ts
Normal file
@@ -0,0 +1,27 @@
|
||||
/** How a client proves who it is to get tokens from the identity server. */
|
||||
export type TokenGrant =
|
||||
| {
|
||||
type: 'authorization_code';
|
||||
code: string;
|
||||
codeVerifier: string;
|
||||
redirectUri: string;
|
||||
}
|
||||
| { type: 'password'; username: string; password: string }
|
||||
| { type: 'refresh_token'; refreshToken: string };
|
||||
|
||||
export interface TokenSet {
|
||||
accessToken: string;
|
||||
refreshToken: string | null;
|
||||
/** Seconds until the access token expires. */
|
||||
expiresIn: number;
|
||||
tokenType: string;
|
||||
}
|
||||
|
||||
/** What a browser needs to start the Authorization Code + PKCE flow. */
|
||||
export interface AuthorizationConfig {
|
||||
authorizeUrl: string;
|
||||
clientId: string;
|
||||
scopes: string[];
|
||||
/** Password grant can be disabled on the identity server. */
|
||||
passwordGrant: boolean;
|
||||
}
|
||||
56
libs/api/identity/domain/src/lib/user.ts
Normal file
56
libs/api/identity/domain/src/lib/user.ts
Normal file
@@ -0,0 +1,56 @@
|
||||
import { VersionedAggregateRoot } from '@task-stream/api/shared/domain';
|
||||
import { UserDeactivatedEvent } from './events/user-deactivated.event';
|
||||
|
||||
export interface UserSnapshot {
|
||||
/** Pireon user id (string to stay provider-agnostic). */
|
||||
id: string;
|
||||
name: string;
|
||||
email: string;
|
||||
isActive: boolean;
|
||||
/** Global roles in the identity server. */
|
||||
roles: string[];
|
||||
/** Roles granted inside this OAuth client (task-stream). */
|
||||
appRoles: string[];
|
||||
}
|
||||
|
||||
/**
|
||||
* A person managed by the Pireon identity server. Not event-sourced here
|
||||
* (Pireon owns users): UserFactory rebuilds it from Pireon or the local cache.
|
||||
*/
|
||||
export class User extends VersionedAggregateRoot implements UserSnapshot {
|
||||
public name!: string;
|
||||
public email!: string;
|
||||
public isActive = true;
|
||||
public roles: string[] = [];
|
||||
public appRoles: string[] = [];
|
||||
|
||||
constructor(id: string) {
|
||||
super();
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
/** Global Pireon admins implicitly hold every app role. */
|
||||
hasAppRole(role: string): boolean {
|
||||
return this.appRoles.includes(role) || this.roles.includes('admin');
|
||||
}
|
||||
|
||||
deactivate(): void {
|
||||
if (!this.isActive) return;
|
||||
this.apply(new UserDeactivatedEvent(this.id));
|
||||
}
|
||||
|
||||
onUserDeactivatedEvent(): void {
|
||||
this.isActive = false;
|
||||
}
|
||||
|
||||
toSnapshot(): UserSnapshot {
|
||||
return {
|
||||
id: this.id,
|
||||
name: this.name,
|
||||
email: this.email,
|
||||
isActive: this.isActive,
|
||||
roles: [...this.roles],
|
||||
appRoles: [...this.appRoles],
|
||||
};
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user