@fonderie/auth 7.7.4 → 7.8.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.
package/dist/index.d.cts CHANGED
@@ -1,10 +1,12 @@
1
- export { IMfaChallenge, ISession, IUser } from './types.cjs';
2
- import { IFonderieModule, IReadinessProblem, IFonderieApp } from '@fonderie/core';
1
+ import { IUser } from './types.cjs';
2
+ export { IMfaChallenge, ISession } from './types.cjs';
3
+ import { IFonderieModule, IAdminDescription, IReadinessProblem, IFonderieApp, IAdminRoute } from '@fonderie/core';
3
4
  import { IStoreAdapter } from '@fonderie/store';
4
5
  import { EventBus } from '@fonderie/events';
5
6
  import { I as IAuthConfig } from './session-Bv4qu9xV.cjs';
6
7
  export { A as AUTH_CONFIG_KEYS, a as AuthLimitedRoute, b as AuthMessageKey, c as IAuthRateLimitConfig, d as IAuthRuntimeConfig, e as IAuthSecrets, f as IDataExportContributor, M as MESSAGE_KEYS, g as buildAuthAccountLimiter, h as buildAuthIpLimiter, w as withSession } from './session-Bv4qu9xV.cjs';
7
- export { IUserDTO, toUserDTO } from './dtos/user.cjs';
8
+ import { IUserDTO } from './dtos/user.cjs';
9
+ export { toUserDTO } from './dtos/user.cjs';
8
10
  export { requireAuth, validate } from '@fonderie/core/middlewares';
9
11
  import { z } from 'zod';
10
12
  import '@fonderie/rate-limit';
@@ -15,6 +17,7 @@ declare class AuthModule implements IFonderieModule {
15
17
  private bus?;
16
18
  readonly name = "@fonderie/auth";
17
19
  constructor(store: IStoreAdapter, config: IAuthConfig, bus?: EventBus | undefined);
20
+ describeAdmin(): IAdminDescription;
18
21
  checkReadiness(): IReadinessProblem[];
19
22
  install(app: IFonderieApp): void;
20
23
  }
@@ -233,4 +236,12 @@ declare function startUserRetention(store: IStoreAdapter, options: IUserRetentio
233
236
 
234
237
  declare function validateAuthConfig(config: IAuthConfig): void;
235
238
 
236
- export { AuthModule, type ChangePasswordInput, DEFAULT_TEMPLATES, IAuthConfig, type IImportUser, type ILoginEventDTO, type ILoginHistoryPageDTO, type IPurgeOptions, type ISessionDTO, type IUserRetentionScheduleOptions, type LoginInput, type RegisterInput, type ResetPasswordInput, importUser, normalizeEmail, normalizeEmailSafe, purgeSoftDeletedUsers, schemas, startUserRetention, validateAuthConfig };
239
+ interface IAdminUserDTO extends IUserDTO {
240
+ suspended: boolean;
241
+ deletedAt: string | null;
242
+ createdAt: string;
243
+ }
244
+ declare function toAdminUserDTO(user: IUser): IAdminUserDTO;
245
+ declare function describeAuthAdminRoutes(store: IStoreAdapter): IAdminRoute[];
246
+
247
+ export { AuthModule, type ChangePasswordInput, DEFAULT_TEMPLATES, type IAdminUserDTO, IAuthConfig, type IImportUser, type ILoginEventDTO, type ILoginHistoryPageDTO, type IPurgeOptions, type ISessionDTO, IUser, IUserDTO, type IUserRetentionScheduleOptions, type LoginInput, type RegisterInput, type ResetPasswordInput, describeAuthAdminRoutes, importUser, normalizeEmail, normalizeEmailSafe, purgeSoftDeletedUsers, schemas, startUserRetention, toAdminUserDTO, validateAuthConfig };
package/dist/index.d.ts CHANGED
@@ -1,10 +1,12 @@
1
- export { IMfaChallenge, ISession, IUser } from './types.js';
2
- import { IFonderieModule, IReadinessProblem, IFonderieApp } from '@fonderie/core';
1
+ import { IUser } from './types.js';
2
+ export { IMfaChallenge, ISession } from './types.js';
3
+ import { IFonderieModule, IAdminDescription, IReadinessProblem, IFonderieApp, IAdminRoute } from '@fonderie/core';
3
4
  import { IStoreAdapter } from '@fonderie/store';
4
5
  import { EventBus } from '@fonderie/events';
5
6
  import { I as IAuthConfig } from './session-Bv4qu9xV.js';
6
7
  export { A as AUTH_CONFIG_KEYS, a as AuthLimitedRoute, b as AuthMessageKey, c as IAuthRateLimitConfig, d as IAuthRuntimeConfig, e as IAuthSecrets, f as IDataExportContributor, M as MESSAGE_KEYS, g as buildAuthAccountLimiter, h as buildAuthIpLimiter, w as withSession } from './session-Bv4qu9xV.js';
7
- export { IUserDTO, toUserDTO } from './dtos/user.js';
8
+ import { IUserDTO } from './dtos/user.js';
9
+ export { toUserDTO } from './dtos/user.js';
8
10
  export { requireAuth, validate } from '@fonderie/core/middlewares';
9
11
  import { z } from 'zod';
10
12
  import '@fonderie/rate-limit';
@@ -15,6 +17,7 @@ declare class AuthModule implements IFonderieModule {
15
17
  private bus?;
16
18
  readonly name = "@fonderie/auth";
17
19
  constructor(store: IStoreAdapter, config: IAuthConfig, bus?: EventBus | undefined);
20
+ describeAdmin(): IAdminDescription;
18
21
  checkReadiness(): IReadinessProblem[];
19
22
  install(app: IFonderieApp): void;
20
23
  }
@@ -233,4 +236,12 @@ declare function startUserRetention(store: IStoreAdapter, options: IUserRetentio
233
236
 
234
237
  declare function validateAuthConfig(config: IAuthConfig): void;
235
238
 
236
- export { AuthModule, type ChangePasswordInput, DEFAULT_TEMPLATES, IAuthConfig, type IImportUser, type ILoginEventDTO, type ILoginHistoryPageDTO, type IPurgeOptions, type ISessionDTO, type IUserRetentionScheduleOptions, type LoginInput, type RegisterInput, type ResetPasswordInput, importUser, normalizeEmail, normalizeEmailSafe, purgeSoftDeletedUsers, schemas, startUserRetention, validateAuthConfig };
239
+ interface IAdminUserDTO extends IUserDTO {
240
+ suspended: boolean;
241
+ deletedAt: string | null;
242
+ createdAt: string;
243
+ }
244
+ declare function toAdminUserDTO(user: IUser): IAdminUserDTO;
245
+ declare function describeAuthAdminRoutes(store: IStoreAdapter): IAdminRoute[];
246
+
247
+ export { AuthModule, type ChangePasswordInput, DEFAULT_TEMPLATES, type IAdminUserDTO, IAuthConfig, type IImportUser, type ILoginEventDTO, type ILoginHistoryPageDTO, type IPurgeOptions, type ISessionDTO, IUser, IUserDTO, type IUserRetentionScheduleOptions, type LoginInput, type RegisterInput, type ResetPasswordInput, describeAuthAdminRoutes, importUser, normalizeEmail, normalizeEmailSafe, purgeSoftDeletedUsers, schemas, startUserRetention, toAdminUserDTO, validateAuthConfig };
package/dist/index.js CHANGED
@@ -646,6 +646,15 @@ var UserModel = class {
646
646
  [id]
647
647
  );
648
648
  }
649
+ // The operator's lock: login, refresh and the session middleware all refuse a
650
+ // suspended user. Returns false when there is no such user.
651
+ async setSuspended(id, suspended) {
652
+ const rows = await this.store.query(
653
+ `UPDATE fonderie_users SET suspended = $2, updated_at = now() WHERE id = $1 AND deleted_at IS NULL RETURNING id`,
654
+ [id, suspended]
655
+ );
656
+ return rows.length > 0;
657
+ }
649
658
  async softDelete(id) {
650
659
  await this.store.query(
651
660
  `UPDATE fonderie_users SET deleted_at = now(), updated_at = now() WHERE id = $1`,
@@ -3182,6 +3191,115 @@ function extractToken(request) {
3182
3191
  return match?.[1] ?? null;
3183
3192
  }
3184
3193
 
3194
+ // src/admin.ts
3195
+ import { HTTP as HTTP7, setApiResponse as setApiResponse7 } from "@fonderie/core";
3196
+ function toAdminUserDTO(user) {
3197
+ return {
3198
+ ...toUserDTO(user),
3199
+ suspended: Boolean(user.suspended),
3200
+ deletedAt: user.deletedAt ? user.deletedAt.toISOString() : null,
3201
+ createdAt: user.createdAt.toISOString()
3202
+ };
3203
+ }
3204
+ var ADMIN_PREFIX = "/_admin";
3205
+ var idOf = (ctx) => ctx.meta.params?.["id"] ?? "";
3206
+ var NOT_FOUND = () => setApiResponse7(HTTP7.NOT_FOUND, "NOT_FOUND", "No such user");
3207
+ function adminRouteTable(store) {
3208
+ const users = new UserModel(store);
3209
+ const sessions = new SessionModel(store);
3210
+ const events = new LoginEventModel(store);
3211
+ const setSuspended = (suspended) => async (ctx) => {
3212
+ const ok = await users.setSuspended(idOf(ctx), suspended);
3213
+ if (!ok) return NOT_FOUND();
3214
+ const user = await users.findById(idOf(ctx));
3215
+ return user ? setApiResponse7(
3216
+ HTTP7.OK,
3217
+ suspended ? "USER_SUSPENDED" : "USER_UNSUSPENDED",
3218
+ suspended ? "User suspended" : "User unsuspended",
3219
+ toAdminUserDTO(user)
3220
+ ) : NOT_FOUND();
3221
+ };
3222
+ return [
3223
+ [
3224
+ "GET",
3225
+ "/_admin/users",
3226
+ async (ctx) => {
3227
+ const email2 = new URL(ctx.request.url).searchParams.get("email")?.trim().toLowerCase();
3228
+ if (!email2)
3229
+ return setApiResponse7(HTTP7.UNPROCESSABLE, "INVALID_PARAMETER", "email is required");
3230
+ const user = await users.findByEmail(email2);
3231
+ return user ? setApiResponse7(HTTP7.OK, "USER", "User", toAdminUserDTO(user)) : NOT_FOUND();
3232
+ }
3233
+ ],
3234
+ [
3235
+ "GET",
3236
+ "/_admin/users/:id",
3237
+ async (ctx) => {
3238
+ const user = await users.findById(idOf(ctx));
3239
+ return user ? setApiResponse7(HTTP7.OK, "USER", "User", toAdminUserDTO(user)) : NOT_FOUND();
3240
+ }
3241
+ ],
3242
+ [
3243
+ "GET",
3244
+ "/_admin/users/:id/sessions",
3245
+ async (ctx) => {
3246
+ if (!await users.findById(idOf(ctx))) return NOT_FOUND();
3247
+ const rows = await sessions.listLiveByUser(idOf(ctx));
3248
+ return setApiResponse7(
3249
+ HTTP7.OK,
3250
+ "SESSIONS",
3251
+ "Live sessions",
3252
+ rows.map((r) => toSessionDTO(r, null))
3253
+ );
3254
+ }
3255
+ ],
3256
+ // Signs the user out everywhere. The one write support reaches for first.
3257
+ [
3258
+ "DELETE",
3259
+ "/_admin/users/:id/sessions",
3260
+ async (ctx) => {
3261
+ if (!await users.findById(idOf(ctx))) return NOT_FOUND();
3262
+ await sessions.deleteByUser(idOf(ctx));
3263
+ return setApiResponse7(HTTP7.OK, "SESSIONS_REVOKED", "All sessions revoked");
3264
+ }
3265
+ ],
3266
+ [
3267
+ "GET",
3268
+ "/_admin/users/:id/login-history",
3269
+ async (ctx) => {
3270
+ if (!await users.findById(idOf(ctx))) return NOT_FOUND();
3271
+ const params = new URL(ctx.request.url).searchParams;
3272
+ const rawLimit = Number(params.get("limit") ?? 50);
3273
+ const limit = Number.isFinite(rawLimit) ? Math.min(Math.max(Math.trunc(rawLimit), 1), 200) : 50;
3274
+ const cursorParam = params.get("cursor");
3275
+ const cursor = cursorParam ? decodeLoginCursor(cursorParam) : null;
3276
+ if (cursorParam && !cursor)
3277
+ return setApiResponse7(HTTP7.UNPROCESSABLE, "INVALID_PARAMETER", "Invalid cursor");
3278
+ const page = await events.listByUser({
3279
+ userId: idOf(ctx),
3280
+ limit,
3281
+ ...cursor ? { cursor } : {}
3282
+ });
3283
+ return setApiResponse7(
3284
+ HTTP7.OK,
3285
+ "LOGIN_HISTORY",
3286
+ "Login history",
3287
+ toLoginHistoryPageDTO(page)
3288
+ );
3289
+ }
3290
+ ],
3291
+ ["POST", "/_admin/users/:id/suspend", setSuspended(true)],
3292
+ ["POST", "/_admin/users/:id/unsuspend", setSuspended(false)]
3293
+ ];
3294
+ }
3295
+ function describeAuthAdminRoutes(store) {
3296
+ return adminRouteTable(store).map(([method, path, h]) => ({
3297
+ method,
3298
+ path: path.slice(ADMIN_PREFIX.length),
3299
+ handlers: [h]
3300
+ }));
3301
+ }
3302
+
3185
3303
  // src/module.ts
3186
3304
  var AuthModule = class {
3187
3305
  constructor(store, config, bus) {
@@ -3194,6 +3312,10 @@ var AuthModule = class {
3194
3312
  config;
3195
3313
  bus;
3196
3314
  name = "@fonderie/auth";
3315
+ // Users, sessions, login history, suspend — only through @fonderie/admin.
3316
+ describeAdmin() {
3317
+ return { routes: describeAuthAdminRoutes(this.store) };
3318
+ }
3197
3319
  // Report config problems for app.checkProductionReadiness() (data, not throw).
3198
3320
  checkReadiness() {
3199
3321
  return collectAuthConfigProblems(this.config);
@@ -3460,6 +3582,7 @@ export {
3460
3582
  MESSAGE_KEYS,
3461
3583
  buildAuthAccountLimiter,
3462
3584
  buildAuthIpLimiter,
3585
+ describeAuthAdminRoutes,
3463
3586
  importUser,
3464
3587
  normalizeEmail,
3465
3588
  normalizeEmailSafe,
@@ -3467,6 +3590,7 @@ export {
3467
3590
  requireAuth2 as requireAuth,
3468
3591
  schemas_exports as schemas,
3469
3592
  startUserRetention,
3593
+ toAdminUserDTO,
3470
3594
  toUserDTO,
3471
3595
  validate,
3472
3596
  validateAuthConfig,