@veap/auth 0.0.0-stage → 0.1.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 (125) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +167 -2
  3. package/dist/actions/auth.actions.d.ts +23 -0
  4. package/dist/actions/auth.actions.js +40 -0
  5. package/dist/actions/index.d.ts +1 -0
  6. package/dist/actions/index.js +1 -0
  7. package/dist/client/auth.context.d.ts +29 -0
  8. package/dist/client/auth.context.js +64 -0
  9. package/dist/client/index.d.ts +1 -0
  10. package/dist/client/index.js +1 -0
  11. package/dist/common/augment.d.ts +26 -0
  12. package/dist/common/augment.js +63 -0
  13. package/dist/common/context.d.ts +30 -0
  14. package/dist/common/context.js +36 -0
  15. package/dist/common/index.d.ts +7 -0
  16. package/dist/common/index.js +7 -0
  17. package/dist/common/logic.d.ts +64 -0
  18. package/dist/common/logic.js +133 -0
  19. package/dist/common/registry.d.ts +28 -0
  20. package/dist/common/registry.js +76 -0
  21. package/dist/common/routes.d.ts +3 -0
  22. package/dist/common/routes.js +2 -0
  23. package/dist/common/tokens.d.ts +10 -0
  24. package/dist/common/tokens.js +8 -0
  25. package/dist/common/types.d.ts +178 -0
  26. package/dist/common/types.js +9 -0
  27. package/dist/crypto/aes-cipher.d.ts +8 -0
  28. package/dist/crypto/aes-cipher.js +12 -0
  29. package/dist/crypto/bcrypt-hasher.d.ts +12 -0
  30. package/dist/crypto/bcrypt-hasher.js +37 -0
  31. package/dist/crypto/contracts.d.ts +36 -0
  32. package/dist/crypto/contracts.js +3 -0
  33. package/dist/crypto/encryption.d.ts +18 -0
  34. package/dist/crypto/encryption.js +69 -0
  35. package/dist/crypto/index.d.ts +6 -0
  36. package/dist/crypto/index.js +6 -0
  37. package/dist/crypto/password.d.ts +19 -0
  38. package/dist/crypto/password.js +27 -0
  39. package/dist/crypto/token-generator.d.ts +10 -0
  40. package/dist/crypto/token-generator.js +25 -0
  41. package/dist/index.d.ts +11 -0
  42. package/dist/index.js +11 -0
  43. package/dist/migrations/0001_create_auth_tables.d.ts +4 -0
  44. package/dist/migrations/0001_create_auth_tables.js +122 -0
  45. package/dist/migrations/index.d.ts +4 -0
  46. package/dist/migrations/index.js +7 -0
  47. package/dist/models/EmailVerification.d.ts +23 -0
  48. package/dist/models/EmailVerification.js +19 -0
  49. package/dist/models/PasswordResetSession.d.ts +25 -0
  50. package/dist/models/PasswordResetSession.js +21 -0
  51. package/dist/models/Permission.d.ts +20 -0
  52. package/dist/models/Permission.js +19 -0
  53. package/dist/models/Role.d.ts +20 -0
  54. package/dist/models/Role.js +19 -0
  55. package/dist/models/Session.d.ts +22 -0
  56. package/dist/models/Session.js +21 -0
  57. package/dist/models/User.d.ts +32 -0
  58. package/dist/models/User.js +31 -0
  59. package/dist/models/index.d.ts +6 -0
  60. package/dist/models/index.js +6 -0
  61. package/dist/models.d.ts +1 -0
  62. package/dist/models.js +1 -0
  63. package/dist/passwords/contracts.d.ts +28 -0
  64. package/dist/passwords/contracts.js +2 -0
  65. package/dist/passwords/index.d.ts +4 -0
  66. package/dist/passwords/index.js +4 -0
  67. package/dist/passwords/password-reset.facade.d.ts +14 -0
  68. package/dist/passwords/password-reset.facade.js +57 -0
  69. package/dist/passwords/password-reset.repository.d.ts +10 -0
  70. package/dist/passwords/password-reset.repository.js +39 -0
  71. package/dist/passwords/password-reset.service.d.ts +38 -0
  72. package/dist/passwords/password-reset.service.js +242 -0
  73. package/dist/providers/auth.provider.d.ts +9 -0
  74. package/dist/providers/auth.provider.js +199 -0
  75. package/dist/providers/index.d.ts +2 -0
  76. package/dist/providers/index.js +2 -0
  77. package/dist/providers/setup.d.ts +7 -0
  78. package/dist/providers/setup.js +29 -0
  79. package/dist/rbac/contracts.d.ts +75 -0
  80. package/dist/rbac/contracts.js +4 -0
  81. package/dist/rbac/index.d.ts +4 -0
  82. package/dist/rbac/index.js +4 -0
  83. package/dist/rbac/rbac.facade.d.ts +23 -0
  84. package/dist/rbac/rbac.facade.js +85 -0
  85. package/dist/rbac/rbac.repository.d.ts +30 -0
  86. package/dist/rbac/rbac.repository.js +137 -0
  87. package/dist/rbac/rbac.service.d.ts +35 -0
  88. package/dist/rbac/rbac.service.js +143 -0
  89. package/dist/server.d.ts +3 -0
  90. package/dist/server.js +3 -0
  91. package/dist/session/auth.service.d.ts +35 -0
  92. package/dist/session/auth.service.js +207 -0
  93. package/dist/session/contracts.d.ts +33 -0
  94. package/dist/session/contracts.js +2 -0
  95. package/dist/session/index.d.ts +5 -0
  96. package/dist/session/index.js +5 -0
  97. package/dist/session/session.facade.d.ts +14 -0
  98. package/dist/session/session.facade.js +75 -0
  99. package/dist/session/session.repository.d.ts +15 -0
  100. package/dist/session/session.repository.js +55 -0
  101. package/dist/session/session.service.d.ts +63 -0
  102. package/dist/session/session.service.js +221 -0
  103. package/dist/users/contracts.d.ts +33 -0
  104. package/dist/users/contracts.js +2 -0
  105. package/dist/users/index.d.ts +4 -0
  106. package/dist/users/index.js +4 -0
  107. package/dist/users/user.facade.d.ts +28 -0
  108. package/dist/users/user.facade.js +92 -0
  109. package/dist/users/user.repository.d.ts +16 -0
  110. package/dist/users/user.repository.js +60 -0
  111. package/dist/users/user.service.d.ts +80 -0
  112. package/dist/users/user.service.js +201 -0
  113. package/dist/validation/index.d.ts +90 -0
  114. package/dist/validation/index.js +46 -0
  115. package/dist/verification/contracts.d.ts +25 -0
  116. package/dist/verification/contracts.js +2 -0
  117. package/dist/verification/index.d.ts +4 -0
  118. package/dist/verification/index.js +4 -0
  119. package/dist/verification/verification.facade.d.ts +10 -0
  120. package/dist/verification/verification.facade.js +49 -0
  121. package/dist/verification/verification.repository.d.ts +7 -0
  122. package/dist/verification/verification.repository.js +23 -0
  123. package/dist/verification/verification.service.d.ts +44 -0
  124. package/dist/verification/verification.service.js +122 -0
  125. package/package.json +84 -4
@@ -0,0 +1,221 @@
1
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
2
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
3
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
4
+ 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;
5
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
6
+ };
7
+ var __metadata = (this && this.__metadata) || function (k, v) {
8
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
9
+ };
10
+ var __param = (this && this.__param) || function (paramIndex, decorator) {
11
+ return function (target, key) { decorator(target, key, paramIndex); }
12
+ };
13
+ import { isIP } from "node:net";
14
+ import { COOKIE_STORE, Inject, Injectable, REQUEST_CONTEXT, } from "@veap/kernel";
15
+ import { addDays } from "date-fns";
16
+ import { augmentSession } from "../common/augment.js";
17
+ import { getAuthRoutes } from "../common/context.js";
18
+ import { performFullUserAugmentation } from "../common/logic.js";
19
+ import { TOKEN_GENERATOR } from "../crypto/contracts.js";
20
+ import { SESSION_REPOSITORY } from "./contracts.js";
21
+ let SessionService = class SessionService {
22
+ sessions;
23
+ tokens;
24
+ cookieStore;
25
+ requestContext;
26
+ constructor(sessions, tokens, cookieStore, requestContext) {
27
+ this.sessions = sessions;
28
+ this.tokens = tokens;
29
+ this.cookieStore = cookieStore;
30
+ this.requestContext = requestContext;
31
+ }
32
+ /**
33
+ * Returns the user's validated IP address, checking trusted proxy headers first.
34
+ */
35
+ async getIPAddress() {
36
+ const directHeaders = ["cf-connecting-ip", "x-real-ip", "x-client-ip"];
37
+ for (const header of directHeaders) {
38
+ const val = await this.requestContext.getHeader(header);
39
+ if (val && isIP(val.trim())) {
40
+ return val.trim();
41
+ }
42
+ }
43
+ const forwarded = await this.requestContext.getHeader("x-forwarded-for");
44
+ if (forwarded) {
45
+ const ips = forwarded.split(",").map((ip) => ip.trim());
46
+ for (const ip of ips) {
47
+ if (isIP(ip)) {
48
+ return ip;
49
+ }
50
+ }
51
+ }
52
+ return null;
53
+ }
54
+ /**
55
+ * Validates the session token.
56
+ */
57
+ async validateSessionToken(token) {
58
+ const sessionId = this.tokens.hashToken(token);
59
+ const record = await this.sessions.findWithUser(sessionId);
60
+ if (!record) {
61
+ return { session: null, user: null };
62
+ }
63
+ const baseUserAttributes = record.user;
64
+ const baseSessionAttributes = record.session;
65
+ // STRICTLY remove non-serializable and sensitive fields (including camelCase recoveryCode)
66
+ const { password, recovery_code, recoveryCode, ...safeUserRest } = baseUserAttributes;
67
+ const safeUser = {
68
+ ...safeUserRest,
69
+ id: baseUserAttributes.id || safeUserRest.id,
70
+ };
71
+ const expiresAt = baseSessionAttributes.expiresAt ?? baseSessionAttributes.expires_at;
72
+ // Check if session is expired
73
+ if (new Date() > new Date(expiresAt)) {
74
+ await this.sessions.remove(sessionId);
75
+ return { session: null, user: null };
76
+ }
77
+ const metadata = baseSessionAttributes.metadata || {};
78
+ // Restore metadata into session object
79
+ const sessionWithFlags = {
80
+ ...baseSessionAttributes,
81
+ id: baseSessionAttributes.id,
82
+ userId: baseSessionAttributes.userId ?? baseSessionAttributes.user_id,
83
+ expiresAt,
84
+ metadata,
85
+ ...metadata,
86
+ };
87
+ // AUGMENT (EXTENSIBILITY POINTS)
88
+ const augmentedUser = await performFullUserAugmentation(safeUser, sessionWithFlags);
89
+ const augmentedSession = await augmentSession(sessionWithFlags);
90
+ // ENSURE PLAIN OBJECTS for Client Components
91
+ return {
92
+ session: augmentedSession ? { ...augmentedSession } : null,
93
+ user: augmentedUser ? { ...augmentedUser } : null,
94
+ };
95
+ }
96
+ /**
97
+ * Returns the current user session from cookies.
98
+ */
99
+ async getCurrentSession() {
100
+ const token = await this.cookieStore.get("session");
101
+ if (token === null) {
102
+ return { session: null, user: null };
103
+ }
104
+ return await this.validateSessionToken(token);
105
+ }
106
+ /**
107
+ * Invalidates a single session.
108
+ */
109
+ async invalidateSession(sessionId) {
110
+ await this.sessions.remove(sessionId);
111
+ }
112
+ /**
113
+ * Invalidates all user sessions.
114
+ */
115
+ async invalidateUserSessions(userId) {
116
+ await this.sessions.removeByUserId(userId);
117
+ }
118
+ /**
119
+ * Sets the session token in a cookie.
120
+ */
121
+ async setSessionTokenCookie(token, expiresAt) {
122
+ await this.cookieStore.set("session", token, {
123
+ httpOnly: true,
124
+ path: "/",
125
+ secure: process.env.NODE_ENV === "production",
126
+ sameSite: "lax",
127
+ expires: expiresAt,
128
+ });
129
+ }
130
+ /**
131
+ * Removes the session token cookie.
132
+ */
133
+ async deleteSessionTokenCookie() {
134
+ await this.cookieStore.delete("session");
135
+ }
136
+ /**
137
+ * Generates a new random session token.
138
+ */
139
+ async generateSessionToken() {
140
+ return this.tokens.generateSessionToken();
141
+ }
142
+ /**
143
+ * Creates a new session in the database.
144
+ */
145
+ async createSession(token, userId, flags) {
146
+ const sessionId = this.tokens.hashToken(token);
147
+ return await this.sessions.create({
148
+ id: sessionId,
149
+ expiresAt: new Date(addDays(new Date(), 7)),
150
+ userId,
151
+ metadata: flags,
152
+ });
153
+ }
154
+ /**
155
+ * Updates the current session metadata.
156
+ */
157
+ async updateSessionMetadata(flags) {
158
+ const { session } = await this.getCurrentSession();
159
+ if (!session)
160
+ return;
161
+ const current = await this.sessions.findById(session.id);
162
+ if (!current)
163
+ return;
164
+ const newMetadata = {
165
+ ...(current.metadata || {}),
166
+ ...flags,
167
+ };
168
+ await this.sessions.updateMetadata(session.id, newMetadata);
169
+ }
170
+ /**
171
+ * Signs the user out and redirects to the sign-in page.
172
+ */
173
+ async sessionSignOut() {
174
+ const { session } = await this.getCurrentSession();
175
+ if (session) {
176
+ await this.invalidateSession(session.id);
177
+ await this.deleteSessionTokenCookie();
178
+ }
179
+ const routes = getAuthRoutes();
180
+ await this.requestContext.redirect(routes.signIn);
181
+ }
182
+ /**
183
+ * Get all active sessions for a user.
184
+ */
185
+ async getUserSessions(userId, currentSessionId) {
186
+ const sessions = await this.sessions.findByUserId(userId);
187
+ const augmentedSessions = await Promise.all(sessions.map(async (session) => {
188
+ const metadata = session.metadata || {};
189
+ const sessionWithFlags = {
190
+ ...session,
191
+ id: session.id,
192
+ userId: session.userId ?? session.user_id,
193
+ expiresAt: session.expiresAt ?? session.expires_at,
194
+ metadata,
195
+ ...metadata,
196
+ };
197
+ // Run augmenters
198
+ const augmented = await augmentSession(sessionWithFlags);
199
+ return {
200
+ ...augmented,
201
+ isCurrent: augmented.id === currentSessionId,
202
+ };
203
+ }));
204
+ return augmentedSessions;
205
+ }
206
+ /**
207
+ * Invalidate all sessions for a user except the specified current one.
208
+ */
209
+ async invalidateOtherSessions(userId, currentSessionId) {
210
+ await this.sessions.removeOtherUserSessions(userId, currentSessionId);
211
+ }
212
+ };
213
+ SessionService = __decorate([
214
+ Injectable(),
215
+ __param(0, Inject(SESSION_REPOSITORY)),
216
+ __param(1, Inject(TOKEN_GENERATOR)),
217
+ __param(2, Inject(COOKIE_STORE)),
218
+ __param(3, Inject(REQUEST_CONTEXT)),
219
+ __metadata("design:paramtypes", [Object, Object, Object, Object])
220
+ ], SessionService);
221
+ export { SessionService };
@@ -0,0 +1,33 @@
1
+ import type { FullUser, User } from "../common/index.js";
2
+ export type { FullUser, User };
3
+ export interface CreateUserRecord {
4
+ email: string;
5
+ name: string;
6
+ password?: string | null;
7
+ image?: string | null;
8
+ emailVerifiedAt?: Date | null;
9
+ recoveryCode: Uint8Array;
10
+ }
11
+ export interface UpdateUserRecord {
12
+ name?: string;
13
+ email?: string;
14
+ image?: string | null;
15
+ password?: string;
16
+ emailVerifiedAt?: Date | null;
17
+ }
18
+ /**
19
+ * User persistence port.
20
+ */
21
+ export interface IUserRepository {
22
+ create(record: CreateUserRecord): Promise<User>;
23
+ findById(id: string): Promise<User | null>;
24
+ findByEmail(email: string): Promise<User | null>;
25
+ findPasswordHash(id: string): Promise<string | null | undefined>;
26
+ findRecoveryCode(id: string): Promise<Uint8Array | null | undefined>;
27
+ update(id: string, changes: UpdateUserRecord): Promise<void>;
28
+ setRecoveryCode(id: string, encrypted: Uint8Array): Promise<void>;
29
+ setEmailVerifiedIfEmailMatches(id: string, email: string): Promise<boolean>;
30
+ isEmailAvailable(email: string): Promise<boolean>;
31
+ }
32
+ /** Injection token for the user repository port. */
33
+ export declare const USER_REPOSITORY: unique symbol;
@@ -0,0 +1,2 @@
1
+ /** Injection token for the user repository port. */
2
+ export const USER_REPOSITORY = Symbol.for("veap:auth:user-repository");
@@ -0,0 +1,4 @@
1
+ export * from "./contracts.js";
2
+ export * from "./user.facade.js";
3
+ export * from "./user.repository.js";
4
+ export * from "./user.service.js";
@@ -0,0 +1,4 @@
1
+ export * from "./contracts.js";
2
+ export * from "./user.facade.js";
3
+ export * from "./user.repository.js";
4
+ export * from "./user.service.js";
@@ -0,0 +1,28 @@
1
+ import type { User } from "../common/index.js";
2
+ export declare function verifyUsernameInput(username: string): Promise<boolean>;
3
+ export declare function isEmailAvailable(email: string): Promise<boolean>;
4
+ /**
5
+ * Checks if an email address is available for registration.
6
+ * Compatible with legacy imports.
7
+ */
8
+ export declare function checkEmailAvailability(email: string): Promise<boolean>;
9
+ export declare function createUser(email: string, username: string, password: string): Promise<User>;
10
+ export declare function createOAuthUser(email: string, name: string, image?: string): Promise<User>;
11
+ export declare function getUserById(userId: string): Promise<User | null>;
12
+ export declare function getUserRecoveryCode(userId: string): Promise<string>;
13
+ /**
14
+ * @deprecated Use `getUserRecoveryCode` instead.
15
+ */
16
+ export declare function getUserRecoverCode(userId: string): Promise<string>;
17
+ export declare function resetUserRecoveryCode(userId: string): Promise<string>;
18
+ export declare function updateUserPassword(userId: string, password: string): Promise<void>;
19
+ export declare function updateUserName(userId: string, name: string): Promise<void>;
20
+ export declare function updateUserAvatar(userId: string, image: string): Promise<void>;
21
+ /**
22
+ * @deprecated Use `updateUserAvatar` instead.
23
+ */
24
+ export declare function updateUserAwatar(userId: string, image: string): Promise<void>;
25
+ export declare function updateUserEmailAndSetEmailAsVerified(userId: string, email: string): Promise<void>;
26
+ export declare function setUserAsEmailVerifiedIfEmailMatches(userId: string, email: string): Promise<boolean>;
27
+ export declare function getUserPasswordHash(userId: string): Promise<string | null>;
28
+ export declare function getUserFromEmail(email: string): Promise<User | null>;
@@ -0,0 +1,92 @@
1
+ "use server";
2
+ import { app } from "@veap/kernel";
3
+ import { authContext, isAuthContextBound } from "../common/context.js";
4
+ import { UserService } from "./user.service.js";
5
+ async function resolveUserService() {
6
+ const g = globalThis;
7
+ if (g.__VEAP_BOOTSTRAPPING_PROMISE__) {
8
+ try {
9
+ await g.__VEAP_BOOTSTRAPPING_PROMISE__;
10
+ }
11
+ catch { }
12
+ }
13
+ if (isAuthContextBound()) {
14
+ return authContext().user;
15
+ }
16
+ return await app(UserService);
17
+ }
18
+ export async function verifyUsernameInput(username) {
19
+ const service = await resolveUserService();
20
+ return service.verifyUsernameInput(username);
21
+ }
22
+ export async function isEmailAvailable(email) {
23
+ const service = await resolveUserService();
24
+ return service.isEmailAvailable(email);
25
+ }
26
+ /**
27
+ * Checks if an email address is available for registration.
28
+ * Compatible with legacy imports.
29
+ */
30
+ export async function checkEmailAvailability(email) {
31
+ return isEmailAvailable(email);
32
+ }
33
+ export async function createUser(email, username, password) {
34
+ const service = await resolveUserService();
35
+ return service.createUser(email, username, password);
36
+ }
37
+ export async function createOAuthUser(email, name, image) {
38
+ const service = await resolveUserService();
39
+ return service.createOAuthUser(email, name, image);
40
+ }
41
+ export async function getUserById(userId) {
42
+ const service = await resolveUserService();
43
+ return service.getUserById(userId);
44
+ }
45
+ export async function getUserRecoveryCode(userId) {
46
+ const service = await resolveUserService();
47
+ return service.getUserRecoveryCode(userId);
48
+ }
49
+ /**
50
+ * @deprecated Use `getUserRecoveryCode` instead.
51
+ */
52
+ export async function getUserRecoverCode(userId) {
53
+ return getUserRecoveryCode(userId);
54
+ }
55
+ export async function resetUserRecoveryCode(userId) {
56
+ const service = await resolveUserService();
57
+ return service.resetUserRecoveryCode(userId);
58
+ }
59
+ export async function updateUserPassword(userId, password) {
60
+ const service = await resolveUserService();
61
+ return service.updateUserPassword(userId, password);
62
+ }
63
+ export async function updateUserName(userId, name) {
64
+ const service = await resolveUserService();
65
+ return service.updateUserName(userId, name);
66
+ }
67
+ export async function updateUserAvatar(userId, image) {
68
+ const service = await resolveUserService();
69
+ return service.updateUserAvatar(userId, image);
70
+ }
71
+ /**
72
+ * @deprecated Use `updateUserAvatar` instead.
73
+ */
74
+ export async function updateUserAwatar(userId, image) {
75
+ return updateUserAvatar(userId, image);
76
+ }
77
+ export async function updateUserEmailAndSetEmailAsVerified(userId, email) {
78
+ const service = await resolveUserService();
79
+ return service.updateUserEmailAndSetEmailAsVerified(userId, email);
80
+ }
81
+ export async function setUserAsEmailVerifiedIfEmailMatches(userId, email) {
82
+ const service = await resolveUserService();
83
+ return service.setUserAsEmailVerifiedIfEmailMatches(userId, email);
84
+ }
85
+ export async function getUserPasswordHash(userId) {
86
+ const service = await resolveUserService();
87
+ return service.getUserPasswordHash(userId);
88
+ }
89
+ export async function getUserFromEmail(email) {
90
+ const service = await resolveUserService();
91
+ return service.getUserFromEmail(email);
92
+ }
@@ -0,0 +1,16 @@
1
+ import type { User as UserEntity } from "../common/index.js";
2
+ import type { CreateUserRecord, IUserRepository, UpdateUserRecord } from "./contracts.js";
3
+ /**
4
+ * `IUserRepository` adapter backed by the `User` ActiveRecord model.
5
+ */
6
+ export declare class ActiveRecordUserRepository implements IUserRepository {
7
+ create(record: CreateUserRecord): Promise<UserEntity>;
8
+ findById(id: string): Promise<UserEntity | null>;
9
+ findByEmail(email: string): Promise<UserEntity | null>;
10
+ findPasswordHash(id: string): Promise<string | null | undefined>;
11
+ findRecoveryCode(id: string): Promise<Uint8Array | null | undefined>;
12
+ update(id: string, changes: UpdateUserRecord): Promise<void>;
13
+ setRecoveryCode(id: string, encrypted: Uint8Array): Promise<void>;
14
+ setEmailVerifiedIfEmailMatches(id: string, email: string): Promise<boolean>;
15
+ isEmailAvailable(email: string): Promise<boolean>;
16
+ }
@@ -0,0 +1,60 @@
1
+ import { User } from "../models/User.js";
2
+ /**
3
+ * `IUserRepository` adapter backed by the `User` ActiveRecord model.
4
+ */
5
+ export class ActiveRecordUserRepository {
6
+ async create(record) {
7
+ const user = await User.create({
8
+ email: record.email,
9
+ name: record.name,
10
+ password: record.password ?? null,
11
+ image: record.image ?? null,
12
+ emailVerifiedAt: record.emailVerifiedAt ?? null,
13
+ recovery_code: record.recoveryCode,
14
+ });
15
+ return user.toJSON();
16
+ }
17
+ async findById(id) {
18
+ const user = await User.find(id);
19
+ return user ? user.toJSON() : null;
20
+ }
21
+ async findByEmail(email) {
22
+ const user = await User.where("email", email).first();
23
+ return user ? user.toJSON() : null;
24
+ }
25
+ async findPasswordHash(id) {
26
+ const user = await User.find(id);
27
+ if (!user)
28
+ return undefined;
29
+ return user.password ?? null;
30
+ }
31
+ async findRecoveryCode(id) {
32
+ const user = await User.find(id);
33
+ if (!user)
34
+ return undefined;
35
+ return user.recovery_code ?? null;
36
+ }
37
+ async update(id, changes) {
38
+ if (Object.keys(changes).length === 0)
39
+ return;
40
+ await User.where("id", id).update(changes);
41
+ }
42
+ async setRecoveryCode(id, encrypted) {
43
+ const user = await User.find(id);
44
+ if (!user)
45
+ return;
46
+ user.setAttribute("recovery_code", Buffer.from(encrypted));
47
+ await user.save();
48
+ }
49
+ async setEmailVerifiedIfEmailMatches(id, email) {
50
+ const updated = await User.where({ id, email }).update({
51
+ emailVerifiedAt: new Date(),
52
+ });
53
+ return updated > 0;
54
+ }
55
+ async isEmailAvailable(email) {
56
+ const normalized = email.toLowerCase().trim();
57
+ const exists = await User.where("email", normalized).exists();
58
+ return !exists;
59
+ }
60
+ }
@@ -0,0 +1,80 @@
1
+ import type { User } from "../common/index.js";
2
+ import { type IPasswordHasher, type ISecretCipher, type ITokenGenerator } from "../crypto/contracts.js";
3
+ import { type IRoleRepository } from "../rbac/contracts.js";
4
+ import { type IUserRepository } from "./contracts.js";
5
+ export declare class UserService {
6
+ private readonly users;
7
+ private readonly roles;
8
+ private readonly hasher;
9
+ private readonly tokens;
10
+ private readonly cipher;
11
+ constructor(users: IUserRepository, roles: IRoleRepository, hasher: IPasswordHasher, tokens: ITokenGenerator, cipher: ISecretCipher);
12
+ /**
13
+ * Validates the username input.
14
+ */
15
+ verifyUsernameInput(username: string): Promise<boolean>;
16
+ /**
17
+ * Checks whether the specified email is available for registration.
18
+ */
19
+ isEmailAvailable(email: string): Promise<boolean>;
20
+ /**
21
+ * Creates a new user with an initial recovery code and default 'user' role.
22
+ */
23
+ createUser(email: string, username: string, password: string): Promise<User>;
24
+ /**
25
+ * Creates a new user from an OAuth provider.
26
+ */
27
+ createOAuthUser(email: string, name: string, image?: string): Promise<User>;
28
+ /**
29
+ * Returns a user by ID.
30
+ */
31
+ getUserById(userId: string): Promise<User | null>;
32
+ /**
33
+ * Decrypts and returns the user's recovery code.
34
+ */
35
+ getUserRecoveryCode(userId: string): Promise<string>;
36
+ /**
37
+ * @deprecated Use `getUserRecoveryCode` instead.
38
+ */
39
+ getUserRecoverCode(userId: string): Promise<string>;
40
+ /**
41
+ * Generates and sets a new recovery code for the user.
42
+ */
43
+ resetUserRecoveryCode(userId: string): Promise<string>;
44
+ /**
45
+ * Updates the user's password.
46
+ */
47
+ updateUserPassword(userId: string, password: string): Promise<void>;
48
+ /**
49
+ * Updates the user's name.
50
+ */
51
+ updateUserName(userId: string, name: string): Promise<void>;
52
+ /**
53
+ * Updates the user's avatar image.
54
+ */
55
+ updateUserAvatar(userId: string, image: string): Promise<void>;
56
+ /**
57
+ * @deprecated Use `updateUserAvatar` instead.
58
+ */
59
+ updateUserAwatar(userId: string, image: string): Promise<void>;
60
+ /**
61
+ * Updates the user's email and marks it as verified.
62
+ */
63
+ updateUserEmailAndSetEmailAsVerified(userId: string, email: string): Promise<void>;
64
+ /**
65
+ * Sets the user as email verified if the provided email matches.
66
+ */
67
+ setUserAsEmailVerifiedIfEmailMatches(userId: string, email: string): Promise<boolean>;
68
+ /**
69
+ * Returns the user's password hash.
70
+ */
71
+ getUserPasswordHash(userId: string): Promise<string | null>;
72
+ /**
73
+ * Returns a user by email.
74
+ */
75
+ getUserFromEmail(email: string): Promise<User | null>;
76
+ /**
77
+ * Ensures the default 'user' role exists and assigns it.
78
+ */
79
+ private assignDefaultRole;
80
+ }