first commit
This commit is contained in:
21
libs/api/identity/domain/README.md
Normal file
21
libs/api/identity/domain/README.md
Normal file
@@ -0,0 +1,21 @@
|
||||
# api-identity-domain
|
||||
|
||||
El usuario, tal como lo conoce TaskStream (sus datos vienen de Pireon).
|
||||
|
||||
- **Import**: `@task-stream/api/identity/domain`
|
||||
- **Capa**: `domain` · **Tags**: `scope:api-identity`, `type:domain`
|
||||
|
||||
## Contenido
|
||||
|
||||
- `User` (agregado): nombre, email, activo, roles globales y roles en el cliente; `hasAppRole()`, `deactivate()`.
|
||||
- `UserFactory`: construye usuarios a partir de Pireon o de la caché.
|
||||
- Tipos del inicio de sesión: `TokenGrant`, `TokenSet`, `AuthorizationConfig`.
|
||||
|
||||
## Comandos
|
||||
|
||||
```sh
|
||||
npx nx lint api-identity-domain
|
||||
npx nx test api-identity-domain # Jest
|
||||
```
|
||||
|
||||
Reglas de dependencia entre capas: [Arquitectura](../../../../docs/02-arquitectura.md#21-capas-hexagonales).
|
||||
3
libs/api/identity/domain/eslint.config.mjs
Normal file
3
libs/api/identity/domain/eslint.config.mjs
Normal file
@@ -0,0 +1,3 @@
|
||||
import baseConfig from '../../../../eslint.config.mjs';
|
||||
|
||||
export default [...baseConfig];
|
||||
11
libs/api/identity/domain/jest.config.cts
Normal file
11
libs/api/identity/domain/jest.config.cts
Normal file
@@ -0,0 +1,11 @@
|
||||
module.exports = {
|
||||
displayName: 'api-identity-domain',
|
||||
preset: '../../../../jest.preset.js',
|
||||
testEnvironment: 'node',
|
||||
passWithNoTests: true,
|
||||
transform: {
|
||||
'^.+\\.[tj]s$': ['ts-jest', { tsconfig: '<rootDir>/tsconfig.spec.json' }],
|
||||
},
|
||||
moduleFileExtensions: ['ts', 'js', 'html'],
|
||||
coverageDirectory: '../../../../coverage/libs/api/identity/domain',
|
||||
};
|
||||
12
libs/api/identity/domain/project.json
Normal file
12
libs/api/identity/domain/project.json
Normal file
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"name": "api-identity-domain",
|
||||
"$schema": "../../../../node_modules/nx/schemas/project-schema.json",
|
||||
"sourceRoot": "libs/api/identity/domain/src",
|
||||
"projectType": "library",
|
||||
"tags": [
|
||||
"scope:api-identity",
|
||||
"type:domain"
|
||||
],
|
||||
"// targets": "to see all targets run: nx show project api-identity-domain --web",
|
||||
"targets": {}
|
||||
}
|
||||
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],
|
||||
};
|
||||
}
|
||||
}
|
||||
23
libs/api/identity/domain/tsconfig.json
Normal file
23
libs/api/identity/domain/tsconfig.json
Normal file
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"extends": "../../../../tsconfig.base.json",
|
||||
"compilerOptions": {
|
||||
"module": "commonjs",
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"strict": true,
|
||||
"importHelpers": true,
|
||||
"noImplicitOverride": true,
|
||||
"noImplicitReturns": true,
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
"noPropertyAccessFromIndexSignature": true
|
||||
},
|
||||
"files": [],
|
||||
"include": [],
|
||||
"references": [
|
||||
{
|
||||
"path": "./tsconfig.lib.json"
|
||||
},
|
||||
{
|
||||
"path": "./tsconfig.spec.json"
|
||||
}
|
||||
]
|
||||
}
|
||||
15
libs/api/identity/domain/tsconfig.lib.json
Normal file
15
libs/api/identity/domain/tsconfig.lib.json
Normal file
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"extends": "./tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "../../../../dist/out-tsc",
|
||||
"declaration": true,
|
||||
"types": ["node"]
|
||||
},
|
||||
"include": ["src/**/*.ts"],
|
||||
"exclude": [
|
||||
"jest.config.ts",
|
||||
"jest.config.cts",
|
||||
"src/**/*.spec.ts",
|
||||
"src/**/*.test.ts"
|
||||
]
|
||||
}
|
||||
16
libs/api/identity/domain/tsconfig.spec.json
Normal file
16
libs/api/identity/domain/tsconfig.spec.json
Normal file
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"extends": "./tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "../../../../dist/out-tsc",
|
||||
"module": "commonjs",
|
||||
"moduleResolution": "bundler",
|
||||
"types": ["jest", "node"]
|
||||
},
|
||||
"include": [
|
||||
"jest.config.ts",
|
||||
"jest.config.cts",
|
||||
"src/**/*.test.ts",
|
||||
"src/**/*.spec.ts",
|
||||
"src/**/*.d.ts"
|
||||
]
|
||||
}
|
||||
Reference in New Issue
Block a user