@deplois/api 0.0.0-stage → 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (56) hide show
  1. package/README.md +176 -2
  2. package/dist/api.module.d.ts +10 -0
  3. package/dist/api.module.js +58 -0
  4. package/dist/controller/session.controller.d.ts +30 -0
  5. package/dist/controller/session.controller.js +115 -0
  6. package/dist/create-api.d.ts +16 -0
  7. package/dist/create-api.js +113 -0
  8. package/dist/database/client.d.ts +6 -0
  9. package/dist/database/client.js +29 -0
  10. package/dist/database/identity.store.d.ts +12 -0
  11. package/dist/database/identity.store.js +91 -0
  12. package/dist/database/index.d.ts +5 -0
  13. package/dist/database/index.js +21 -0
  14. package/dist/database/metadata.d.ts +1 -0
  15. package/dist/database/metadata.js +21 -0
  16. package/dist/database/session.entity.d.ts +11 -0
  17. package/dist/database/session.entity.js +62 -0
  18. package/dist/database/user.entity.d.ts +10 -0
  19. package/dist/database/user.entity.js +54 -0
  20. package/dist/identity/identity.plugin.d.ts +31 -0
  21. package/dist/identity/identity.plugin.js +161 -0
  22. package/dist/identity/identity.service.d.ts +33 -0
  23. package/dist/identity/identity.service.js +36 -0
  24. package/dist/identity/interface.d.ts +49 -0
  25. package/dist/identity/interface.js +2 -0
  26. package/dist/index.d.ts +19 -0
  27. package/dist/index.js +40 -0
  28. package/dist/interface.d.ts +20 -0
  29. package/dist/interface.js +2 -0
  30. package/dist/options.d.ts +23 -0
  31. package/dist/options.js +4 -0
  32. package/dist/session.module.d.ts +2 -0
  33. package/dist/session.module.js +18 -0
  34. package/dist/system/decorator/raw-response.decorator.d.ts +2 -0
  35. package/dist/system/decorator/raw-response.decorator.js +7 -0
  36. package/dist/system/error/app.exception.d.ts +6 -0
  37. package/dist/system/error/app.exception.js +12 -0
  38. package/dist/system/error/error-code.enum.d.ts +11 -0
  39. package/dist/system/error/error-code.enum.js +15 -0
  40. package/dist/system/error/error.plugin.d.ts +14 -0
  41. package/dist/system/error/error.plugin.js +108 -0
  42. package/dist/system/filter/http-exception.filter.d.ts +9 -0
  43. package/dist/system/filter/http-exception.filter.js +49 -0
  44. package/dist/system/guard/session.guard.d.ts +13 -0
  45. package/dist/system/guard/session.guard.js +64 -0
  46. package/dist/system/guard/user.guard.d.ts +13 -0
  47. package/dist/system/guard/user.guard.js +46 -0
  48. package/dist/system/interceptor/response.interceptor.d.ts +9 -0
  49. package/dist/system/interceptor/response.interceptor.js +44 -0
  50. package/dist/system/logger/file.logger.d.ts +38 -0
  51. package/dist/system/logger/file.logger.js +116 -0
  52. package/dist/system/logger/request.logger.d.ts +25 -0
  53. package/dist/system/logger/request.logger.js +99 -0
  54. package/dist/system/pipe/body-validation.pipe.d.ts +7 -0
  55. package/dist/system/pipe/body-validation.pipe.js +71 -0
  56. package/package.json +45 -4
@@ -0,0 +1,91 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.TypeORMIdentityStore = void 0;
4
+ const user_entity_1 = require("./user.entity");
5
+ const session_entity_1 = require("./session.entity");
6
+ const metadata_1 = require("./metadata");
7
+ class TypeORMIdentityStore {
8
+ database;
9
+ constructor(database) {
10
+ this.database = database;
11
+ }
12
+ async createUser(user) {
13
+ await this.database.getRepository(user_entity_1.UserEntity).save(this.database.getRepository(user_entity_1.UserEntity).create(user));
14
+ }
15
+ async getUser(id) {
16
+ return this.database.getRepository(user_entity_1.UserEntity).findOneBy({ id, isActive: true });
17
+ }
18
+ async updateUser(id, patch, now) {
19
+ return this.database.transaction(async (manager) => {
20
+ const repository = manager.getRepository(user_entity_1.UserEntity);
21
+ const user = await repository.findOne({ where: { id, isActive: true }, lock: { mode: 'pessimistic_write' } });
22
+ if (!user)
23
+ return null;
24
+ if (patch.profile)
25
+ user.profile = (0, metadata_1.mergeMetadata)(user.profile, patch.profile);
26
+ if (patch.metadata)
27
+ user.metadata = (0, metadata_1.mergeMetadata)(user.metadata, patch.metadata);
28
+ if (patch.isActive === false)
29
+ Object.assign(user, { isActive: false, deletedDate: now });
30
+ await repository.save(user);
31
+ return user;
32
+ });
33
+ }
34
+ async createSession(session) {
35
+ const { userIds, ...data } = session;
36
+ // Newly issued sessions are anonymous. User attachment requires the locked mutation below.
37
+ if (userIds.length)
38
+ throw new Error('Create an anonymous session before attaching users.');
39
+ await this.database.getRepository(session_entity_1.SessionEntity).save(this.database.getRepository(session_entity_1.SessionEntity).create(data));
40
+ }
41
+ async getSession(tokenHash) {
42
+ const session = await this.database.getRepository(session_entity_1.SessionEntity).findOne({ where: { tokenHash }, relations: { users: true } });
43
+ if (!session)
44
+ return null;
45
+ const { users, ...data } = session;
46
+ return { ...data, tokenHash, userIds: users.filter(user => user.isActive).map(user => user.id) };
47
+ }
48
+ async updateSession(id, update, now, tokenHash) {
49
+ return this.database.transaction(async (manager) => {
50
+ const repository = manager.getRepository(session_entity_1.SessionEntity);
51
+ // Lock only the session row; outer-joined nullable relations cannot be FOR UPDATE locked in PostgreSQL.
52
+ const session = await repository.createQueryBuilder('session').addSelect('session.tokenHash').where('session.id = :id', { id }).setLock('pessimistic_write').getOne();
53
+ if (!session || (tokenHash !== undefined && tokenHash !== session.tokenHash))
54
+ return null;
55
+ if (update.type === 'revoke') {
56
+ if (!session.revokedDate) {
57
+ session.revokedDate = now;
58
+ await repository.save(session);
59
+ }
60
+ }
61
+ else {
62
+ if (session.revokedDate || session.expiresDate.getTime() <= now.getTime())
63
+ return null;
64
+ switch (update.type) {
65
+ case 'touch':
66
+ session.lastUsedDate = new Date(Math.max(session.lastUsedDate.getTime(), now.getTime()));
67
+ session.expiresDate = new Date(Math.max(session.expiresDate.getTime(), update.expiresDate.getTime()));
68
+ await repository.save(session);
69
+ break;
70
+ case 'addUser': {
71
+ const user = await manager.getRepository(user_entity_1.UserEntity).findOne({ where: { id: update.userId, isActive: true }, lock: { mode: 'pessimistic_read' } });
72
+ if (!user)
73
+ return null;
74
+ const existing = await manager.createQueryBuilder().relation(session_entity_1.SessionEntity, 'users').of(id).loadMany();
75
+ if (!existing.some(item => item.id === user.id))
76
+ await manager.createQueryBuilder().relation(session_entity_1.SessionEntity, 'users').of(id).add(user.id);
77
+ Object.assign(session, { tokenHash: update.tokenHash });
78
+ await repository.save(session);
79
+ break;
80
+ }
81
+ case 'removeUser':
82
+ await manager.createQueryBuilder().relation(session_entity_1.SessionEntity, 'users').of(id).remove(update.userId);
83
+ break;
84
+ }
85
+ }
86
+ const users = await manager.createQueryBuilder().relation(session_entity_1.SessionEntity, 'users').of(id).loadMany();
87
+ return { ...session, userIds: users.filter(user => user.isActive && !user.deletedDate).map(user => user.id) };
88
+ });
89
+ }
90
+ }
91
+ exports.TypeORMIdentityStore = TypeORMIdentityStore;
@@ -0,0 +1,5 @@
1
+ export * from './user.entity';
2
+ export * from './session.entity';
3
+ export * from './identity.store';
4
+ export * from './client';
5
+ export * from './metadata';
@@ -0,0 +1,21 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
+ };
16
+ Object.defineProperty(exports, "__esModule", { value: true });
17
+ __exportStar(require("./user.entity"), exports);
18
+ __exportStar(require("./session.entity"), exports);
19
+ __exportStar(require("./identity.store"), exports);
20
+ __exportStar(require("./client"), exports);
21
+ __exportStar(require("./metadata"), exports);
@@ -0,0 +1 @@
1
+ export declare function mergeMetadata(current: Record<string, unknown>, patch: Record<string, unknown>): Record<string, unknown>;
@@ -0,0 +1,21 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.mergeMetadata = mergeMetadata;
4
+ // Merge object patches. An explicit undefined removes a key; null is a stored value.
5
+ function mergeMetadata(current, patch) {
6
+ const result = { ...current };
7
+ for (const [key, value] of Object.entries(patch)) {
8
+ if (value === undefined)
9
+ delete result[key];
10
+ else {
11
+ const previous = result[key];
12
+ Object.defineProperty(result, key, {
13
+ value: value != null && Object.getPrototypeOf(value) === Object.prototype
14
+ ? mergeMetadata(previous != null && Object.getPrototypeOf(previous) === Object.prototype ? previous : {}, value)
15
+ : value,
16
+ writable: true, configurable: true, enumerable: true
17
+ });
18
+ }
19
+ }
20
+ return result;
21
+ }
@@ -0,0 +1,11 @@
1
+ import { UserEntity } from './user.entity';
2
+ export declare class SessionEntity {
3
+ readonly id: string;
4
+ readonly tokenHash: string;
5
+ users: UserEntity[];
6
+ metadata: Record<string, unknown>;
7
+ readonly createdDate: Date;
8
+ lastUsedDate: Date;
9
+ expiresDate: Date;
10
+ revokedDate: Date | null;
11
+ }
@@ -0,0 +1,62 @@
1
+ "use strict";
2
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
3
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
4
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
5
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
6
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
7
+ };
8
+ var __metadata = (this && this.__metadata) || function (k, v) {
9
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
10
+ };
11
+ Object.defineProperty(exports, "__esModule", { value: true });
12
+ exports.SessionEntity = void 0;
13
+ const typeorm_1 = require("typeorm");
14
+ const user_entity_1 = require("./user.entity");
15
+ let SessionEntity = class SessionEntity {
16
+ id;
17
+ tokenHash;
18
+ users;
19
+ metadata;
20
+ createdDate;
21
+ lastUsedDate;
22
+ expiresDate;
23
+ revokedDate;
24
+ };
25
+ exports.SessionEntity = SessionEntity;
26
+ __decorate([
27
+ (0, typeorm_1.PrimaryGeneratedColumn)('uuid'),
28
+ __metadata("design:type", String)
29
+ ], SessionEntity.prototype, "id", void 0);
30
+ __decorate([
31
+ (0, typeorm_1.Column)({ name: 'token_hash', type: 'varchar', length: 64, unique: true, select: false }),
32
+ __metadata("design:type", String)
33
+ ], SessionEntity.prototype, "tokenHash", void 0);
34
+ __decorate([
35
+ (0, typeorm_1.ManyToMany)(() => user_entity_1.UserEntity),
36
+ (0, typeorm_1.JoinTable)({ name: 'api_session_users', joinColumn: { name: 'session_id' }, inverseJoinColumn: { name: 'user_id' } }),
37
+ __metadata("design:type", Array)
38
+ ], SessionEntity.prototype, "users", void 0);
39
+ __decorate([
40
+ (0, typeorm_1.Column)({ type: 'jsonb', default: {} }),
41
+ __metadata("design:type", Object)
42
+ ], SessionEntity.prototype, "metadata", void 0);
43
+ __decorate([
44
+ (0, typeorm_1.CreateDateColumn)({ name: 'created_date', type: 'timestamptz' }),
45
+ __metadata("design:type", Date)
46
+ ], SessionEntity.prototype, "createdDate", void 0);
47
+ __decorate([
48
+ (0, typeorm_1.Column)({ name: 'last_used_date', type: 'timestamptz' }),
49
+ __metadata("design:type", Date)
50
+ ], SessionEntity.prototype, "lastUsedDate", void 0);
51
+ __decorate([
52
+ (0, typeorm_1.Index)(),
53
+ (0, typeorm_1.Column)({ name: 'expires_date', type: 'timestamptz' }),
54
+ __metadata("design:type", Date)
55
+ ], SessionEntity.prototype, "expiresDate", void 0);
56
+ __decorate([
57
+ (0, typeorm_1.Column)({ name: 'revoked_date', type: 'timestamptz', nullable: true, default: null }),
58
+ __metadata("design:type", Object)
59
+ ], SessionEntity.prototype, "revokedDate", void 0);
60
+ exports.SessionEntity = SessionEntity = __decorate([
61
+ (0, typeorm_1.Entity)('api_session')
62
+ ], SessionEntity);
@@ -0,0 +1,10 @@
1
+ import type { User } from '../identity/interface';
2
+ export declare class UserEntity {
3
+ readonly id: string;
4
+ profile: User['profile'];
5
+ metadata: Record<string, unknown>;
6
+ isActive: boolean;
7
+ readonly createdDate: Date;
8
+ readonly updatedDate: Date;
9
+ readonly deletedDate: Date | null;
10
+ }
@@ -0,0 +1,54 @@
1
+ "use strict";
2
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
3
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
4
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
5
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
6
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
7
+ };
8
+ var __metadata = (this && this.__metadata) || function (k, v) {
9
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
10
+ };
11
+ Object.defineProperty(exports, "__esModule", { value: true });
12
+ exports.UserEntity = void 0;
13
+ const typeorm_1 = require("typeorm");
14
+ let UserEntity = class UserEntity {
15
+ id;
16
+ profile;
17
+ metadata;
18
+ isActive;
19
+ createdDate;
20
+ updatedDate;
21
+ deletedDate;
22
+ };
23
+ exports.UserEntity = UserEntity;
24
+ __decorate([
25
+ (0, typeorm_1.PrimaryGeneratedColumn)('uuid'),
26
+ __metadata("design:type", String)
27
+ ], UserEntity.prototype, "id", void 0);
28
+ __decorate([
29
+ (0, typeorm_1.Column)({ type: 'jsonb', default: {} }),
30
+ __metadata("design:type", Object)
31
+ ], UserEntity.prototype, "profile", void 0);
32
+ __decorate([
33
+ (0, typeorm_1.Column)({ type: 'jsonb', default: {} }),
34
+ __metadata("design:type", Object)
35
+ ], UserEntity.prototype, "metadata", void 0);
36
+ __decorate([
37
+ (0, typeorm_1.Column)({ name: 'is_active', type: 'boolean', default: true }),
38
+ __metadata("design:type", Boolean)
39
+ ], UserEntity.prototype, "isActive", void 0);
40
+ __decorate([
41
+ (0, typeorm_1.CreateDateColumn)({ name: 'created_date', type: 'timestamptz' }),
42
+ __metadata("design:type", Date)
43
+ ], UserEntity.prototype, "createdDate", void 0);
44
+ __decorate([
45
+ (0, typeorm_1.UpdateDateColumn)({ name: 'updated_date', type: 'timestamptz' }),
46
+ __metadata("design:type", Date)
47
+ ], UserEntity.prototype, "updatedDate", void 0);
48
+ __decorate([
49
+ (0, typeorm_1.DeleteDateColumn)({ name: 'deleted_date', type: 'timestamptz', nullable: true }),
50
+ __metadata("design:type", Object)
51
+ ], UserEntity.prototype, "deletedDate", void 0);
52
+ exports.UserEntity = UserEntity = __decorate([
53
+ (0, typeorm_1.Entity)('api_user')
54
+ ], UserEntity);
@@ -0,0 +1,31 @@
1
+ import type { PluginResult } from '../interface';
2
+ import type { IdentityOptions, IdentityStore, Session, User } from './interface';
3
+ export declare function createIdentityPlugins(store: IdentityStore, options?: IdentityOptions): {
4
+ userPlugin: {
5
+ pluginName: string;
6
+ create(input?: {
7
+ profile?: User["profile"];
8
+ metadata?: User["metadata"];
9
+ }): Promise<PluginResult<User>>;
10
+ get(userId: string): Promise<PluginResult<User>>;
11
+ update(userId: string, patch: Pick<Partial<User>, "profile" | "metadata">): Promise<PluginResult<User>>;
12
+ disable(userId: string): Promise<PluginResult<null>>;
13
+ };
14
+ sessionPlugin: {
15
+ pluginName: string;
16
+ create(input?: {
17
+ userId?: string;
18
+ metadata?: Record<string, unknown>;
19
+ }): Promise<PluginResult<{
20
+ token: string;
21
+ session: Omit<Session, "tokenHash">;
22
+ }>>;
23
+ get(token: string): Promise<PluginResult<Omit<Session, "tokenHash">>>;
24
+ addUser(token: string, userId: string): Promise<PluginResult<{
25
+ token: string;
26
+ session: Omit<Session, "tokenHash">;
27
+ }>>;
28
+ removeUser(token: string, userId: string): Promise<PluginResult<null>>;
29
+ revoke(token: string): Promise<PluginResult<null>>;
30
+ };
31
+ };
@@ -0,0 +1,161 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.createIdentityPlugins = createIdentityPlugins;
7
+ const node_crypto_1 = require("node:crypto");
8
+ const app_exception_1 = require("../system/error/app.exception");
9
+ const error_plugin_1 = __importDefault(require("../system/error/error.plugin"));
10
+ const hash = (token) => (0, node_crypto_1.createHash)('sha256').update(token).digest('hex');
11
+ function createIdentityPlugins(store, options = {}) {
12
+ const sessionDuration = options.sessionDuration ?? 60 * 60 * 24;
13
+ const now = options.now ?? (() => new Date());
14
+ if (!Number.isSafeInteger(sessionDuration) || sessionDuration <= 0) {
15
+ throw new Error('Durations must be positive integers in seconds.');
16
+ }
17
+ const userPlugin = {
18
+ pluginName: 'user.plugin',
19
+ async create(input = {}) {
20
+ try {
21
+ const date = now();
22
+ const user = {
23
+ id: (0, node_crypto_1.randomUUID)(), profile: input.profile ?? {}, metadata: input.metadata ?? {},
24
+ isActive: true, createdDate: date, updatedDate: date, deletedDate: null
25
+ };
26
+ await store.createUser(user);
27
+ return { success: true, data: user };
28
+ }
29
+ catch (error) {
30
+ return { success: false, error: toError(error) };
31
+ }
32
+ },
33
+ async get(userId) {
34
+ try {
35
+ const user = await store.getUser(userId);
36
+ if (!user?.isActive)
37
+ throw error_plugin_1.default.createException('USER_NOT_FOUND', 'User not found.', 404);
38
+ return { success: true, data: user };
39
+ }
40
+ catch (error) {
41
+ return { success: false, error: toError(error) };
42
+ }
43
+ },
44
+ async update(userId, patch) {
45
+ try {
46
+ const user = await store.updateUser(userId, patch, now());
47
+ if (!user?.isActive)
48
+ throw error_plugin_1.default.createException('USER_NOT_FOUND', 'User not found.', 404);
49
+ return { success: true, data: user };
50
+ }
51
+ catch (error) {
52
+ return { success: false, error: toError(error) };
53
+ }
54
+ },
55
+ async disable(userId) {
56
+ try {
57
+ const date = now();
58
+ const user = await store.updateUser(userId, { isActive: false }, date);
59
+ if (!user)
60
+ throw error_plugin_1.default.createException('USER_NOT_FOUND', 'User not found.', 404);
61
+ return { success: true, data: null };
62
+ }
63
+ catch (error) {
64
+ return { success: false, error: toError(error) };
65
+ }
66
+ }
67
+ };
68
+ const sessionPlugin = {
69
+ pluginName: 'session.plugin',
70
+ async create(input = {}) {
71
+ try {
72
+ const token = (0, node_crypto_1.randomBytes)(96).toString('base64url');
73
+ const date = now();
74
+ const session = {
75
+ id: (0, node_crypto_1.randomUUID)(), tokenHash: hash(token), userIds: [], metadata: input.metadata ?? {},
76
+ createdDate: date, lastUsedDate: date, expiresDate: new Date(date.getTime() + sessionDuration * 1000), revokedDate: null
77
+ };
78
+ await store.createSession(session);
79
+ if (input.userId) {
80
+ const result = await sessionPlugin.addUser(token, input.userId);
81
+ if (!result.success) {
82
+ await store.updateSession(session.id, { type: 'revoke' }, now(), hash(token));
83
+ throw result.error;
84
+ }
85
+ return result;
86
+ }
87
+ const { tokenHash, ...data } = session;
88
+ return { success: true, data: { token, session: data } };
89
+ }
90
+ catch (error) {
91
+ return { success: false, error: toError(error) };
92
+ }
93
+ },
94
+ async get(token) {
95
+ try {
96
+ const session = await store.getSession(hash(token));
97
+ const date = now();
98
+ if (!session || session.revokedDate)
99
+ throw error_plugin_1.default.createException('WRONG_SESSION', 'Wrong session.');
100
+ if (session.expiresDate.getTime() <= date.getTime())
101
+ throw error_plugin_1.default.createException('EXPIRED_SESSION', 'Session expired.', 401);
102
+ const updated = await store.updateSession(session.id, { type: 'touch', expiresDate: new Date(date.getTime() + sessionDuration * 1000) }, date, hash(token));
103
+ if (!updated)
104
+ throw error_plugin_1.default.createException('WRONG_SESSION', 'Session is no longer active.');
105
+ const { tokenHash, ...data } = updated;
106
+ return { success: true, data };
107
+ }
108
+ catch (error) {
109
+ return { success: false, error: toError(error) };
110
+ }
111
+ },
112
+ // Call only after another module has authenticated this user.
113
+ async addUser(token, userId) {
114
+ try {
115
+ const user = await userPlugin.get(userId);
116
+ if (!user.success)
117
+ throw user.error;
118
+ const session = await sessionPlugin.get(token);
119
+ if (!session.success)
120
+ throw session.error;
121
+ const newToken = (0, node_crypto_1.randomBytes)(96).toString('base64url');
122
+ const updated = await store.updateSession(session.data.id, { type: 'addUser', userId, tokenHash: hash(newToken) }, now(), hash(token));
123
+ if (!updated)
124
+ throw error_plugin_1.default.createException('WRONG_SESSION', 'Session is no longer active.');
125
+ const { tokenHash, ...data } = updated;
126
+ return { success: true, data: { token: newToken, session: data } };
127
+ }
128
+ catch (error) {
129
+ return { success: false, error: toError(error) };
130
+ }
131
+ },
132
+ async removeUser(token, userId) {
133
+ try {
134
+ const session = await sessionPlugin.get(token);
135
+ if (!session.success)
136
+ throw session.error;
137
+ if (!await store.updateSession(session.data.id, { type: 'removeUser', userId }, now(), hash(token)))
138
+ throw error_plugin_1.default.createException('WRONG_SESSION', 'Session is no longer active.');
139
+ return { success: true, data: null };
140
+ }
141
+ catch (error) {
142
+ return { success: false, error: toError(error) };
143
+ }
144
+ },
145
+ async revoke(token) {
146
+ try {
147
+ const session = await store.getSession(hash(token));
148
+ if (session)
149
+ await store.updateSession(session.id, { type: 'revoke' }, now(), hash(token));
150
+ return { success: true, data: null };
151
+ }
152
+ catch (error) {
153
+ return { success: false, error: toError(error) };
154
+ }
155
+ }
156
+ };
157
+ return { userPlugin, sessionPlugin };
158
+ }
159
+ function toError(error) {
160
+ return error instanceof Error ? error : new app_exception_1.AppException({ code: 'INTERNAL_SERVER_ERROR', message: 'An internal server error occurred.' }, 500, { cause: error });
161
+ }
@@ -0,0 +1,33 @@
1
+ import { APIOptions } from '../options';
2
+ export declare class IdentityService {
3
+ private readonly options;
4
+ constructor(options: APIOptions);
5
+ get userPlugin(): {
6
+ pluginName: string;
7
+ create(input?: {
8
+ profile?: import("./interface").User["profile"];
9
+ metadata?: import("./interface").User["metadata"];
10
+ }): Promise<import("..").PluginResult<import("./interface").User>>;
11
+ get(userId: string): Promise<import("..").PluginResult<import("./interface").User>>;
12
+ update(userId: string, patch: Pick<Partial<import("./interface").User>, "profile" | "metadata">): Promise<import("..").PluginResult<import("./interface").User>>;
13
+ disable(userId: string): Promise<import("..").PluginResult<null>>;
14
+ };
15
+ get sessionPlugin(): {
16
+ pluginName: string;
17
+ create(input?: {
18
+ userId?: string;
19
+ metadata?: Record<string, unknown>;
20
+ }): Promise<import("..").PluginResult<{
21
+ token: string;
22
+ session: Omit<import("./interface").Session, "tokenHash">;
23
+ }>>;
24
+ get(token: string): Promise<import("..").PluginResult<Omit<import("./interface").Session, "tokenHash">>>;
25
+ addUser(token: string, userId: string): Promise<import("..").PluginResult<{
26
+ token: string;
27
+ session: Omit<import("./interface").Session, "tokenHash">;
28
+ }>>;
29
+ removeUser(token: string, userId: string): Promise<import("..").PluginResult<null>>;
30
+ revoke(token: string): Promise<import("..").PluginResult<null>>;
31
+ };
32
+ private get plugins();
33
+ }
@@ -0,0 +1,36 @@
1
+ "use strict";
2
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
3
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
4
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
5
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
6
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
7
+ };
8
+ var __metadata = (this && this.__metadata) || function (k, v) {
9
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
10
+ };
11
+ var __param = (this && this.__param) || function (paramIndex, decorator) {
12
+ return function (target, key) { decorator(target, key, paramIndex); }
13
+ };
14
+ Object.defineProperty(exports, "__esModule", { value: true });
15
+ exports.IdentityService = void 0;
16
+ const common_1 = require("@nestjs/common");
17
+ const options_1 = require("../options");
18
+ let IdentityService = class IdentityService {
19
+ options;
20
+ constructor(options) {
21
+ this.options = options;
22
+ }
23
+ get userPlugin() { return this.plugins.userPlugin; }
24
+ get sessionPlugin() { return this.plugins.sessionPlugin; }
25
+ get plugins() {
26
+ if (!this.options.identity)
27
+ throw new Error('Identity store is not configured.');
28
+ return this.options.identity;
29
+ }
30
+ };
31
+ exports.IdentityService = IdentityService;
32
+ exports.IdentityService = IdentityService = __decorate([
33
+ (0, common_1.Injectable)(),
34
+ __param(0, (0, common_1.Inject)(options_1.API_OPTIONS)),
35
+ __metadata("design:paramtypes", [Object])
36
+ ], IdentityService);
@@ -0,0 +1,49 @@
1
+ export interface User {
2
+ id: string;
3
+ profile: {
4
+ fullName?: string;
5
+ emailAddress?: string;
6
+ phoneNumber?: string;
7
+ imageUrl?: string;
8
+ };
9
+ metadata: Record<string, unknown>;
10
+ isActive: boolean;
11
+ createdDate: Date;
12
+ updatedDate: Date;
13
+ deletedDate: Date | null;
14
+ }
15
+ export interface Session {
16
+ id: string;
17
+ tokenHash: string;
18
+ userIds: string[];
19
+ metadata: Record<string, unknown>;
20
+ createdDate: Date;
21
+ lastUsedDate: Date;
22
+ expiresDate: Date;
23
+ revokedDate: Date | null;
24
+ }
25
+ export type SessionUpdate = {
26
+ type: 'touch';
27
+ expiresDate: Date;
28
+ } | {
29
+ type: 'addUser';
30
+ userId: string;
31
+ tokenHash: string;
32
+ } | {
33
+ type: 'removeUser';
34
+ userId: string;
35
+ } | {
36
+ type: 'revoke';
37
+ };
38
+ export interface IdentityStore {
39
+ createUser(user: User): Promise<void>;
40
+ getUser(id: string): Promise<User | null>;
41
+ updateUser(id: string, patch: Pick<Partial<User>, 'profile' | 'metadata' | 'isActive'>, now: Date): Promise<User | null>;
42
+ createSession(session: Session): Promise<void>;
43
+ getSession(tokenHash: string): Promise<Session | null>;
44
+ updateSession(id: string, update: SessionUpdate, now: Date, tokenHash?: string): Promise<Session | null>;
45
+ }
46
+ export interface IdentityOptions {
47
+ sessionDuration?: number;
48
+ now?: () => Date;
49
+ }
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,19 @@
1
+ export * from './api.module';
2
+ export * from './options';
3
+ export * from './interface';
4
+ export * from './system/error/app.exception';
5
+ export * from './system/error/error-code.enum';
6
+ export { default as errorPlugin } from './system/error/error.plugin';
7
+ export * from './system/filter/http-exception.filter';
8
+ export * from './system/interceptor/response.interceptor';
9
+ export * from './system/pipe/body-validation.pipe';
10
+ export * from './system/logger/request.logger';
11
+ export * from './system/guard/session.guard';
12
+ export * from './system/decorator/raw-response.decorator';
13
+ export * from './identity/interface';
14
+ export * from './identity/identity.plugin';
15
+ export * from './identity/identity.service';
16
+ export * from './system/guard/user.guard';
17
+ export * from './session.module';
18
+ export * from './create-api';
19
+ export * from './system/logger/file.logger';