@datrix/api 0.1.1 → 0.1.2

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.
package/dist/index.d.mts CHANGED
@@ -1,4 +1,4 @@
1
- import { DefaultPermission, IAuthManager, AuthUser, LoginResult, AuthContext, IUpload, BasePlugin, IApiPlugin, Datrix, QueryContext, PluginContext, SchemaDefinition, DatrixEntry, QueryObject, PermissionAction, ParsedQuery, FallbackInput, ParserError, PermissionValue, PermissionCheckResult, FieldPermissionCheckResult, RawQueryParams, ParserOptions, FallbackWhereClause, PopulateClause, DatrixRecord, DatrixError } from '@datrix/core';
1
+ import { DatrixEntry, DefaultPermission, AuthenticatedUser, IAuthManager, AuthUser, LoginResult, AuthContext, IUpload, BasePlugin, IApiPlugin, Datrix, QueryContext, PluginContext, SchemaDefinition, QueryObject, PermissionAction, ParsedQuery, FallbackInput, ParserError, PermissionValue, PermissionCheckResult, FieldPermissionCheckResult, RawQueryParams, ParserOptions, FallbackWhereClause, PopulateClause, DatrixRecord, DatrixError } from '@datrix/core';
2
2
  export { PermissionAction } from '@datrix/core';
3
3
 
4
4
  interface PasswordHash {
@@ -52,7 +52,7 @@ interface SessionConfig {
52
52
  readonly checkPeriod?: number;
53
53
  readonly prefix?: string;
54
54
  }
55
- interface AuthConfig<TRole extends string = string> {
55
+ interface AuthConfig<TRole extends string = string, TUser extends DatrixEntry = DatrixEntry> {
56
56
  readonly roles: readonly TRole[];
57
57
  readonly defaultRole: TRole;
58
58
  readonly defaultPermission?: DefaultPermission<TRole>;
@@ -69,8 +69,14 @@ interface AuthConfig<TRole extends string = string> {
69
69
  readonly register?: string;
70
70
  readonly logout?: string;
71
71
  readonly me?: string;
72
+ readonly forgotPassword?: string;
73
+ readonly resetPassword?: string;
72
74
  readonly disableRegister?: boolean;
73
75
  };
76
+ readonly passwordReset?: {
77
+ readonly tokenExpirySeconds?: number;
78
+ readonly onForgotPassword?: (user: Omit<AuthenticatedUser<TRole, TUser>, "password" | "passwordSalt">, token: string) => Promise<void>;
79
+ };
74
80
  }
75
81
 
76
82
  declare class JwtStrategy {
@@ -122,13 +128,13 @@ declare class MemorySessionStore implements SessionStore {
122
128
  private getKey;
123
129
  }
124
130
 
125
- declare class AuthManager<TRole extends string = string> implements IAuthManager {
131
+ declare class AuthManager<TRole extends string = string, TUser extends DatrixEntry = DatrixEntry> implements IAuthManager {
126
132
  private readonly passwordManager;
127
133
  private readonly jwtStrategy;
128
134
  private readonly sessionStrategy;
129
135
  private readonly config;
130
- get authConfig(): AuthConfig<TRole>;
131
- constructor(config: AuthConfig<TRole>);
136
+ get authConfig(): AuthConfig<TRole, TUser>;
137
+ constructor(config: AuthConfig<TRole, TUser>);
132
138
  hashPassword(password: string): Promise<PasswordHash>;
133
139
  verifyPassword(password: string, hash: string, salt: string): Promise<boolean>;
134
140
  login(user: AuthUser, options?: {
@@ -158,7 +164,7 @@ interface ApiConfig<TRole extends string = string> extends Record<string, unknow
158
164
  declare class ApiPlugin<TRole extends string = string> extends BasePlugin<ApiConfig<TRole>> implements IApiPlugin<TRole> {
159
165
  readonly name = "api";
160
166
  readonly version = "1.0.0";
161
- authManager?: AuthManager;
167
+ authManager?: AuthManager<TRole>;
162
168
  user: AuthUser | null;
163
169
  private datrixInstance?;
164
170
  get datrix(): Datrix;
@@ -186,7 +192,7 @@ declare class ApiPlugin<TRole extends string = string> extends BasePlugin<ApiCon
186
192
  private handleAuthRequest;
187
193
  isEnabled(): boolean;
188
194
  isAuthEnabled(): boolean;
189
- getAuthManager(): AuthManager | undefined;
195
+ getAuthManager(): AuthManager<TRole> | undefined;
190
196
  }
191
197
 
192
198
  declare function handleRequest(datrix: Datrix, request: Request): Promise<Response>;
@@ -242,18 +248,20 @@ declare function serializeQuery<T extends DatrixEntry = DatrixEntry>(query: Pars
242
248
 
243
249
  declare function handleCrudRequest<TRole extends string = string>(request: Request, datrix: Datrix, api: IApiPlugin<TRole>, options?: ContextBuilderOptions): Promise<Response>;
244
250
 
245
- interface AuthHandlerConfig {
251
+ interface AuthHandlerConfig<TRole extends string = string, TUser extends DatrixEntry = DatrixEntry> {
246
252
  readonly datrix: Datrix;
247
- readonly authManager: AuthManager;
248
- readonly authConfig: AuthConfig;
253
+ readonly authManager: AuthManager<TRole, TUser>;
254
+ readonly authConfig: AuthConfig<TRole, TUser>;
249
255
  }
250
- declare function createAuthHandlers(config: AuthHandlerConfig): {
256
+ declare function createAuthHandlers<TRole extends string = string, TUser extends DatrixEntry = DatrixEntry>(config: AuthHandlerConfig<TRole, TUser>): {
251
257
  register: (request: Request) => Promise<Response>;
252
258
  login: (request: Request) => Promise<Response>;
253
259
  logout: (request: Request) => Promise<Response>;
254
260
  me: (request: Request) => Promise<Response>;
261
+ forgotPassword: (request: Request) => Promise<Response>;
262
+ resetPassword: (request: Request) => Promise<Response>;
255
263
  };
256
- declare function createUnifiedAuthHandler(config: AuthHandlerConfig, apiPrefix?: string): (request: Request) => Promise<Response>;
264
+ declare function createUnifiedAuthHandler<TRole extends string = string, TUser extends DatrixEntry = DatrixEntry>(config: AuthHandlerConfig<TRole, TUser>, apiPrefix?: string): (request: Request) => Promise<Response>;
257
265
 
258
266
  declare class DatrixApiError extends DatrixError {
259
267
  status: number;
package/dist/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { DefaultPermission, IAuthManager, AuthUser, LoginResult, AuthContext, IUpload, BasePlugin, IApiPlugin, Datrix, QueryContext, PluginContext, SchemaDefinition, DatrixEntry, QueryObject, PermissionAction, ParsedQuery, FallbackInput, ParserError, PermissionValue, PermissionCheckResult, FieldPermissionCheckResult, RawQueryParams, ParserOptions, FallbackWhereClause, PopulateClause, DatrixRecord, DatrixError } from '@datrix/core';
1
+ import { DatrixEntry, DefaultPermission, AuthenticatedUser, IAuthManager, AuthUser, LoginResult, AuthContext, IUpload, BasePlugin, IApiPlugin, Datrix, QueryContext, PluginContext, SchemaDefinition, QueryObject, PermissionAction, ParsedQuery, FallbackInput, ParserError, PermissionValue, PermissionCheckResult, FieldPermissionCheckResult, RawQueryParams, ParserOptions, FallbackWhereClause, PopulateClause, DatrixRecord, DatrixError } from '@datrix/core';
2
2
  export { PermissionAction } from '@datrix/core';
3
3
 
4
4
  interface PasswordHash {
@@ -52,7 +52,7 @@ interface SessionConfig {
52
52
  readonly checkPeriod?: number;
53
53
  readonly prefix?: string;
54
54
  }
55
- interface AuthConfig<TRole extends string = string> {
55
+ interface AuthConfig<TRole extends string = string, TUser extends DatrixEntry = DatrixEntry> {
56
56
  readonly roles: readonly TRole[];
57
57
  readonly defaultRole: TRole;
58
58
  readonly defaultPermission?: DefaultPermission<TRole>;
@@ -69,8 +69,14 @@ interface AuthConfig<TRole extends string = string> {
69
69
  readonly register?: string;
70
70
  readonly logout?: string;
71
71
  readonly me?: string;
72
+ readonly forgotPassword?: string;
73
+ readonly resetPassword?: string;
72
74
  readonly disableRegister?: boolean;
73
75
  };
76
+ readonly passwordReset?: {
77
+ readonly tokenExpirySeconds?: number;
78
+ readonly onForgotPassword?: (user: Omit<AuthenticatedUser<TRole, TUser>, "password" | "passwordSalt">, token: string) => Promise<void>;
79
+ };
74
80
  }
75
81
 
76
82
  declare class JwtStrategy {
@@ -122,13 +128,13 @@ declare class MemorySessionStore implements SessionStore {
122
128
  private getKey;
123
129
  }
124
130
 
125
- declare class AuthManager<TRole extends string = string> implements IAuthManager {
131
+ declare class AuthManager<TRole extends string = string, TUser extends DatrixEntry = DatrixEntry> implements IAuthManager {
126
132
  private readonly passwordManager;
127
133
  private readonly jwtStrategy;
128
134
  private readonly sessionStrategy;
129
135
  private readonly config;
130
- get authConfig(): AuthConfig<TRole>;
131
- constructor(config: AuthConfig<TRole>);
136
+ get authConfig(): AuthConfig<TRole, TUser>;
137
+ constructor(config: AuthConfig<TRole, TUser>);
132
138
  hashPassword(password: string): Promise<PasswordHash>;
133
139
  verifyPassword(password: string, hash: string, salt: string): Promise<boolean>;
134
140
  login(user: AuthUser, options?: {
@@ -158,7 +164,7 @@ interface ApiConfig<TRole extends string = string> extends Record<string, unknow
158
164
  declare class ApiPlugin<TRole extends string = string> extends BasePlugin<ApiConfig<TRole>> implements IApiPlugin<TRole> {
159
165
  readonly name = "api";
160
166
  readonly version = "1.0.0";
161
- authManager?: AuthManager;
167
+ authManager?: AuthManager<TRole>;
162
168
  user: AuthUser | null;
163
169
  private datrixInstance?;
164
170
  get datrix(): Datrix;
@@ -186,7 +192,7 @@ declare class ApiPlugin<TRole extends string = string> extends BasePlugin<ApiCon
186
192
  private handleAuthRequest;
187
193
  isEnabled(): boolean;
188
194
  isAuthEnabled(): boolean;
189
- getAuthManager(): AuthManager | undefined;
195
+ getAuthManager(): AuthManager<TRole> | undefined;
190
196
  }
191
197
 
192
198
  declare function handleRequest(datrix: Datrix, request: Request): Promise<Response>;
@@ -242,18 +248,20 @@ declare function serializeQuery<T extends DatrixEntry = DatrixEntry>(query: Pars
242
248
 
243
249
  declare function handleCrudRequest<TRole extends string = string>(request: Request, datrix: Datrix, api: IApiPlugin<TRole>, options?: ContextBuilderOptions): Promise<Response>;
244
250
 
245
- interface AuthHandlerConfig {
251
+ interface AuthHandlerConfig<TRole extends string = string, TUser extends DatrixEntry = DatrixEntry> {
246
252
  readonly datrix: Datrix;
247
- readonly authManager: AuthManager;
248
- readonly authConfig: AuthConfig;
253
+ readonly authManager: AuthManager<TRole, TUser>;
254
+ readonly authConfig: AuthConfig<TRole, TUser>;
249
255
  }
250
- declare function createAuthHandlers(config: AuthHandlerConfig): {
256
+ declare function createAuthHandlers<TRole extends string = string, TUser extends DatrixEntry = DatrixEntry>(config: AuthHandlerConfig<TRole, TUser>): {
251
257
  register: (request: Request) => Promise<Response>;
252
258
  login: (request: Request) => Promise<Response>;
253
259
  logout: (request: Request) => Promise<Response>;
254
260
  me: (request: Request) => Promise<Response>;
261
+ forgotPassword: (request: Request) => Promise<Response>;
262
+ resetPassword: (request: Request) => Promise<Response>;
255
263
  };
256
- declare function createUnifiedAuthHandler(config: AuthHandlerConfig, apiPrefix?: string): (request: Request) => Promise<Response>;
264
+ declare function createUnifiedAuthHandler<TRole extends string = string, TUser extends DatrixEntry = DatrixEntry>(config: AuthHandlerConfig<TRole, TUser>, apiPrefix?: string): (request: Request) => Promise<Response>;
257
265
 
258
266
  declare class DatrixApiError extends DatrixError {
259
267
  status: number;
package/dist/index.js CHANGED
@@ -1263,7 +1263,91 @@ function createAuthHandlers(config) {
1263
1263
  );
1264
1264
  }
1265
1265
  }
1266
- return { register, login, logout, me };
1266
+ async function forgotPassword(request) {
1267
+ try {
1268
+ const onForgotPassword = authConfig.passwordReset?.onForgotPassword;
1269
+ if (!onForgotPassword) {
1270
+ throw handlerError.permissionDenied("Password reset is not configured");
1271
+ }
1272
+ const body = await request.json();
1273
+ const { email } = body;
1274
+ if (!email || typeof email !== "string") {
1275
+ throw handlerError.invalidBody("Email is required");
1276
+ }
1277
+ const authRecord = await datrix.raw.findOne(
1278
+ authSchemaName,
1279
+ { email },
1280
+ {
1281
+ populate: true,
1282
+ select: ["email", "role", "resetToken", "resetTokenExpiry"]
1283
+ }
1284
+ );
1285
+ if (!authRecord) {
1286
+ return jsonResponse({ data: { success: true } });
1287
+ }
1288
+ const tokenBytes = new Uint8Array(32);
1289
+ crypto.getRandomValues(tokenBytes);
1290
+ const token = Array.from(tokenBytes).map((b) => b.toString(16).padStart(2, "0")).join("");
1291
+ const expirySeconds = authConfig.passwordReset?.tokenExpirySeconds ?? import_core7.DEFAULT_API_AUTH_CONFIG.passwordReset.tokenExpirySeconds;
1292
+ const expiry = new Date(Date.now() + expirySeconds * 1e3);
1293
+ await datrix.raw.update(authSchemaName, authRecord.id, {
1294
+ resetToken: token,
1295
+ resetTokenExpiry: expiry
1296
+ });
1297
+ await onForgotPassword(authRecord, token);
1298
+ return jsonResponse({ data: { success: true } });
1299
+ } catch (error) {
1300
+ if (error instanceof import_core8.DatrixError) {
1301
+ return datrixErrorResponse(error);
1302
+ }
1303
+ const message = error instanceof Error ? error.message : "Internal server error";
1304
+ return datrixErrorResponse(
1305
+ handlerError.internalError(
1306
+ message,
1307
+ error instanceof Error ? error : void 0
1308
+ )
1309
+ );
1310
+ }
1311
+ }
1312
+ async function resetPassword(request) {
1313
+ try {
1314
+ const body = await request.json();
1315
+ const { token, password } = body;
1316
+ if (!token || typeof token !== "string") {
1317
+ throw handlerError.invalidBody("Token is required");
1318
+ }
1319
+ if (!password || typeof password !== "string") {
1320
+ throw handlerError.invalidBody("Password is required");
1321
+ }
1322
+ const authRecord = await datrix.raw.findOne(
1323
+ authSchemaName,
1324
+ { resetToken: token }
1325
+ );
1326
+ if (!authRecord || !authRecord.resetTokenExpiry || new Date(authRecord.resetTokenExpiry) < /* @__PURE__ */ new Date()) {
1327
+ throw handlerError.invalidBody("Invalid or expired reset token");
1328
+ }
1329
+ const { hash, salt } = await authManager.hashPassword(password);
1330
+ await datrix.raw.update(authSchemaName, authRecord.id, {
1331
+ password: hash,
1332
+ passwordSalt: salt,
1333
+ resetToken: null,
1334
+ resetTokenExpiry: null
1335
+ });
1336
+ return jsonResponse({ data: { success: true } });
1337
+ } catch (error) {
1338
+ if (error instanceof import_core8.DatrixError) {
1339
+ return datrixErrorResponse(error);
1340
+ }
1341
+ const message = error instanceof Error ? error.message : "Internal server error";
1342
+ return datrixErrorResponse(
1343
+ handlerError.internalError(
1344
+ message,
1345
+ error instanceof Error ? error : void 0
1346
+ )
1347
+ );
1348
+ }
1349
+ }
1350
+ return { register, login, logout, me, forgotPassword, resetPassword };
1267
1351
  }
1268
1352
  function createUnifiedAuthHandler(config, apiPrefix = "/api") {
1269
1353
  const handlers = createAuthHandlers(config);
@@ -1272,7 +1356,9 @@ function createUnifiedAuthHandler(config, apiPrefix = "/api") {
1272
1356
  register: authConfig.endpoints?.register ?? import_core7.DEFAULT_API_AUTH_CONFIG.endpoints.register,
1273
1357
  login: authConfig.endpoints?.login ?? import_core7.DEFAULT_API_AUTH_CONFIG.endpoints.login,
1274
1358
  logout: authConfig.endpoints?.logout ?? import_core7.DEFAULT_API_AUTH_CONFIG.endpoints.logout,
1275
- me: authConfig.endpoints?.me ?? import_core7.DEFAULT_API_AUTH_CONFIG.endpoints.me
1359
+ me: authConfig.endpoints?.me ?? import_core7.DEFAULT_API_AUTH_CONFIG.endpoints.me,
1360
+ forgotPassword: authConfig.endpoints?.forgotPassword ?? import_core7.DEFAULT_API_AUTH_CONFIG.endpoints.forgotPassword,
1361
+ resetPassword: authConfig.endpoints?.resetPassword ?? import_core7.DEFAULT_API_AUTH_CONFIG.endpoints.resetPassword
1276
1362
  };
1277
1363
  return async function authHandler(request) {
1278
1364
  const url = new URL(request.url);
@@ -1290,6 +1376,12 @@ function createUnifiedAuthHandler(config, apiPrefix = "/api") {
1290
1376
  if (path === endpoints.me && method === "GET") {
1291
1377
  return handlers.me(request);
1292
1378
  }
1379
+ if (path === endpoints.forgotPassword && method === "POST") {
1380
+ return handlers.forgotPassword(request);
1381
+ }
1382
+ if (path === endpoints.resetPassword && method === "POST") {
1383
+ return handlers.resetPassword(request);
1384
+ }
1293
1385
  return datrixErrorResponse(
1294
1386
  handlerError.recordNotFound("Auth Route", url.pathname)
1295
1387
  );