@cat-factory/workspaces 0.7.46 → 0.8.0

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.ts CHANGED
@@ -2,4 +2,5 @@ export { WorkspaceService, type WorkspaceServiceDependencies, } from './modules/
2
2
  export { AccountService, type AccountServiceDependencies, type AccountUser, } from './modules/accounts/AccountService.js';
3
3
  export { UserService, type UserServiceDependencies, type IdentityProfile, } from './modules/users/UserService.js';
4
4
  export { InvitationService, type InvitationServiceDependencies, type CreatedInvitation, } from './modules/invitations/InvitationService.js';
5
+ export { PasswordResetService, type PasswordResetServiceDependencies, type ResetLogger, } from './modules/auth/PasswordResetService.js';
5
6
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAEA,OAAO,EACL,gBAAgB,EAChB,KAAK,4BAA4B,GAClC,MAAM,0CAA0C,CAAA;AACjD,OAAO,EACL,cAAc,EACd,KAAK,0BAA0B,EAC/B,KAAK,WAAW,GACjB,MAAM,sCAAsC,CAAA;AAC7C,OAAO,EACL,WAAW,EACX,KAAK,uBAAuB,EAC5B,KAAK,eAAe,GACrB,MAAM,gCAAgC,CAAA;AACvC,OAAO,EACL,iBAAiB,EACjB,KAAK,6BAA6B,EAClC,KAAK,iBAAiB,GACvB,MAAM,4CAA4C,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAEA,OAAO,EACL,gBAAgB,EAChB,KAAK,4BAA4B,GAClC,MAAM,0CAA0C,CAAA;AACjD,OAAO,EACL,cAAc,EACd,KAAK,0BAA0B,EAC/B,KAAK,WAAW,GACjB,MAAM,sCAAsC,CAAA;AAC7C,OAAO,EACL,WAAW,EACX,KAAK,uBAAuB,EAC5B,KAAK,eAAe,GACrB,MAAM,gCAAgC,CAAA;AACvC,OAAO,EACL,iBAAiB,EACjB,KAAK,6BAA6B,EAClC,KAAK,iBAAiB,GACvB,MAAM,4CAA4C,CAAA;AACnD,OAAO,EACL,oBAAoB,EACpB,KAAK,gCAAgC,EACrC,KAAK,WAAW,GACjB,MAAM,wCAAwC,CAAA"}
package/dist/index.js CHANGED
@@ -3,4 +3,5 @@ export { WorkspaceService, } from './modules/workspaces/WorkspaceService.js';
3
3
  export { AccountService, } from './modules/accounts/AccountService.js';
4
4
  export { UserService, } from './modules/users/UserService.js';
5
5
  export { InvitationService, } from './modules/invitations/InvitationService.js';
6
+ export { PasswordResetService, } from './modules/auth/PasswordResetService.js';
6
7
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,4CAA4C;AAE5C,OAAO,EACL,gBAAgB,GAEjB,MAAM,0CAA0C,CAAA;AACjD,OAAO,EACL,cAAc,GAGf,MAAM,sCAAsC,CAAA;AAC7C,OAAO,EACL,WAAW,GAGZ,MAAM,gCAAgC,CAAA;AACvC,OAAO,EACL,iBAAiB,GAGlB,MAAM,4CAA4C,CAAA"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,4CAA4C;AAE5C,OAAO,EACL,gBAAgB,GAEjB,MAAM,0CAA0C,CAAA;AACjD,OAAO,EACL,cAAc,GAGf,MAAM,sCAAsC,CAAA;AAC7C,OAAO,EACL,WAAW,GAGZ,MAAM,gCAAgC,CAAA;AACvC,OAAO,EACL,iBAAiB,GAGlB,MAAM,4CAA4C,CAAA;AACnD,OAAO,EACL,oBAAoB,GAGrB,MAAM,wCAAwC,CAAA"}
@@ -0,0 +1,48 @@
1
+ import type { Clock, EmailSender, IdGenerator, PasswordHasher, PasswordResetTokenRepository, UserRepository } from '@cat-factory/kernel';
2
+ /** Minimal structural logger (satisfied by the facade's pino logger) — kept local so
3
+ * this base-layer package doesn't depend on the server layer. */
4
+ export interface ResetLogger {
5
+ info(obj: Record<string, unknown>, msg?: string): void;
6
+ }
7
+ export interface PasswordResetServiceDependencies {
8
+ passwordResetTokenRepository: PasswordResetTokenRepository;
9
+ userRepository: UserRepository;
10
+ passwordHasher: PasswordHasher;
11
+ idGenerator: IdGenerator;
12
+ clock: Clock;
13
+ /**
14
+ * Resolve the deployment's system email sender at send time. Absent / returning null
15
+ * ⇒ no email is sent; the reset link is logged (dev convenience) instead, never
16
+ * returned to the caller.
17
+ */
18
+ resolveSystemEmailSender?: () => Promise<EmailSender | null>;
19
+ /** Public base URL the reset link points at (the SPA origin). */
20
+ appBaseUrl?: string;
21
+ logger?: ResetLogger;
22
+ }
23
+ export declare class PasswordResetService {
24
+ private readonly deps;
25
+ constructor(deps: PasswordResetServiceDependencies);
26
+ /**
27
+ * Request a password reset for `email`. No-op (silent) when no password identity owns
28
+ * the email — the caller must respond identically regardless so the endpoint can't be
29
+ * used to enumerate registered emails. Mints a single-use token (superseding any prior
30
+ * pending ones) and emails the reset link; when no sender is configured the link is
31
+ * logged for local/dev testing rather than returned.
32
+ */
33
+ request(email: string): Promise<void>;
34
+ /**
35
+ * Redeem a reset token and set the user's new password. Throws on a missing / already-
36
+ * used / expired token (the controller maps these to a generic 400). On success the
37
+ * password identity's secret is replaced, the token is consumed, and every other
38
+ * pending token for the user is superseded.
39
+ *
40
+ * NOTE: sessions are stateless (signed, self-expiring tokens with no server-side
41
+ * store), so a reset does NOT retroactively revoke a session already issued to this
42
+ * user — any live session stays valid until its own TTL lapses. Revoking on reset would
43
+ * require a per-user session epoch checked on every request, i.e. trading away the
44
+ * stateless model; that is a separate, deliberate decision, not handled here.
45
+ */
46
+ reset(token: string, newPassword: string): Promise<void>;
47
+ }
48
+ //# sourceMappingURL=PasswordResetService.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"PasswordResetService.d.ts","sourceRoot":"","sources":["../../../src/modules/auth/PasswordResetService.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EACV,KAAK,EACL,WAAW,EACX,WAAW,EACX,cAAc,EAEd,4BAA4B,EAC5B,cAAc,EACf,MAAM,qBAAqB,CAAA;AAiB5B;iEACiE;AACjE,MAAM,WAAW,WAAW;IAC1B,IAAI,CAAC,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,GAAG,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;CACvD;AAED,MAAM,WAAW,gCAAgC;IAC/C,4BAA4B,EAAE,4BAA4B,CAAA;IAC1D,cAAc,EAAE,cAAc,CAAA;IAC9B,cAAc,EAAE,cAAc,CAAA;IAC9B,WAAW,EAAE,WAAW,CAAA;IACxB,KAAK,EAAE,KAAK,CAAA;IACZ;;;;OAIG;IACH,wBAAwB,CAAC,EAAE,MAAM,OAAO,CAAC,WAAW,GAAG,IAAI,CAAC,CAAA;IAC5D,iEAAiE;IACjE,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,MAAM,CAAC,EAAE,WAAW,CAAA;CACrB;AAQD,qBAAa,oBAAoB;IACnB,OAAO,CAAC,QAAQ,CAAC,IAAI;IAAjC,YAA6B,IAAI,EAAE,gCAAgC,EAAI;IAEvE;;;;;;OAMG;IACG,OAAO,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CA+D1C;IAED;;;;;;;;;;;OAWG;IACG,KAAK,CAAC,KAAK,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAwC7D;CACF"}
@@ -0,0 +1,147 @@
1
+ import { ConflictError, NotFoundError, ValidationError } from '@cat-factory/kernel';
2
+ // ---------------------------------------------------------------------------
3
+ // PasswordResetService: the "forgot my password" flow for password-based logins.
4
+ // A user requests a reset by email; an opaque token (delivered by email) is redeemed
5
+ // to set a new password. Only the token's SHA-256 hash is stored — the raw token lives
6
+ // only in the emailed link. Mirrors InvitationService (token minting + hash storage +
7
+ // EmailSender delivery), and reuses the PasswordHasher exactly as UserService does.
8
+ //
9
+ // Security: the request path never reveals whether an email is registered (it returns
10
+ // the same way for a hit and a miss — the controller always responds generically), the
11
+ // raw token is never returned over HTTP, tokens are single-use + short-lived, and a
12
+ // successful reset (or a fresh request) supersedes every other pending token.
13
+ // ---------------------------------------------------------------------------
14
+ const RESET_TTL_MS = 60 * 60 * 1000;
15
+ /** SHA-256 hex digest — Web Crypto, runs on both runtimes. */
16
+ async function sha256Hex(input) {
17
+ const digest = await crypto.subtle.digest('SHA-256', new TextEncoder().encode(input));
18
+ return [...new Uint8Array(digest)].map((b) => b.toString(16).padStart(2, '0')).join('');
19
+ }
20
+ export class PasswordResetService {
21
+ deps;
22
+ constructor(deps) {
23
+ this.deps = deps;
24
+ }
25
+ /**
26
+ * Request a password reset for `email`. No-op (silent) when no password identity owns
27
+ * the email — the caller must respond identically regardless so the endpoint can't be
28
+ * used to enumerate registered emails. Mints a single-use token (superseding any prior
29
+ * pending ones) and emails the reset link; when no sender is configured the link is
30
+ * logged for local/dev testing rather than returned.
31
+ */
32
+ async request(email) {
33
+ const normalizedEmail = email.toLowerCase().trim();
34
+ if (!normalizedEmail)
35
+ return;
36
+ // Only password-based logins can reset a password (OAuth-only users have no secret).
37
+ const identity = await this.deps.userRepository.getIdentity('password', normalizedEmail);
38
+ if (!identity?.secret)
39
+ return;
40
+ // Supersede any still-pending token so only the freshly-minted one stays live.
41
+ const pending = await this.deps.passwordResetTokenRepository.listPendingByUser(identity.userId);
42
+ for (const prior of pending) {
43
+ await this.deps.passwordResetTokenRepository.setStatus(prior.id, 'used');
44
+ }
45
+ const token = `${crypto.randomUUID()}${crypto.randomUUID()}`.replace(/-/g, '');
46
+ const record = {
47
+ id: this.deps.idGenerator.next('prt'),
48
+ userId: identity.userId,
49
+ tokenHash: await sha256Hex(token),
50
+ status: 'pending',
51
+ expiresAt: this.deps.clock.now() + RESET_TTL_MS,
52
+ createdAt: this.deps.clock.now(),
53
+ };
54
+ await this.deps.passwordResetTokenRepository.create(record);
55
+ const resetUrl = this.deps.appBaseUrl
56
+ ? `${this.deps.appBaseUrl.replace(/\/$/, '')}/reset-password?token=${token}`
57
+ : null;
58
+ const sender = this.deps.resolveSystemEmailSender
59
+ ? await this.deps.resolveSystemEmailSender()
60
+ : null;
61
+ if (sender && resetUrl) {
62
+ // Best-effort: a provider/transport failure must NOT propagate. The controller
63
+ // answers identically (204) for a registered and an unregistered email so the
64
+ // endpoint can't enumerate accounts — letting a send error bubble to a 500 would
65
+ // turn a flaky provider into exactly that registration oracle. Log and move on.
66
+ try {
67
+ await sender.send({
68
+ to: normalizedEmail,
69
+ subject: 'Reset your Cat Factory password',
70
+ text: `Reset your password using this link (valid for 1 hour): ${resetUrl}`,
71
+ html: resetEmailHtml(resetUrl),
72
+ });
73
+ }
74
+ catch (err) {
75
+ this.deps.logger?.info({ userId: identity.userId, err: String(err) }, 'Password reset email failed to send');
76
+ }
77
+ }
78
+ else if (resetUrl) {
79
+ // No system sender configured: surface the link in the logs so local/dev can test.
80
+ // It is NEVER returned to the unauthenticated caller.
81
+ this.deps.logger?.info({ resetUrl, userId: identity.userId }, 'Password reset requested but no email sender configured; link logged for dev');
82
+ }
83
+ else {
84
+ // A token was minted but there is no app base URL to build a link from, so the user
85
+ // can never receive it. Flag the misconfiguration rather than failing silently.
86
+ this.deps.logger?.info({ userId: identity.userId }, 'Password reset requested but appBaseUrl is not configured; no reset link could be built');
87
+ }
88
+ }
89
+ /**
90
+ * Redeem a reset token and set the user's new password. Throws on a missing / already-
91
+ * used / expired token (the controller maps these to a generic 400). On success the
92
+ * password identity's secret is replaced, the token is consumed, and every other
93
+ * pending token for the user is superseded.
94
+ *
95
+ * NOTE: sessions are stateless (signed, self-expiring tokens with no server-side
96
+ * store), so a reset does NOT retroactively revoke a session already issued to this
97
+ * user — any live session stays valid until its own TTL lapses. Revoking on reset would
98
+ * require a per-user session epoch checked on every request, i.e. trading away the
99
+ * stateless model; that is a separate, deliberate decision, not handled here.
100
+ */
101
+ async reset(token, newPassword) {
102
+ if (newPassword.length < 8) {
103
+ throw new ValidationError('Password must be at least 8 characters');
104
+ }
105
+ const record = await this.deps.passwordResetTokenRepository.findByTokenHash(await sha256Hex(token));
106
+ if (!record || record.status !== 'pending') {
107
+ throw new NotFoundError('PasswordResetToken', 'token');
108
+ }
109
+ if (record.expiresAt < this.deps.clock.now()) {
110
+ throw new ConflictError('This password reset link has expired');
111
+ }
112
+ // Resolve the password identity straight from the token's user — not by round-tripping
113
+ // through `users.email` — so the lookup can't drift from how the identity subject was
114
+ // stored. A user with no password identity (e.g. OAuth-only) has nothing to reset.
115
+ const identity = (await this.deps.userRepository.listIdentities(record.userId)).find((i) => i.provider === 'password');
116
+ if (!identity)
117
+ throw new NotFoundError('PasswordResetToken', 'token');
118
+ // Atomically consume the token BEFORE touching the password: only the call that flips
119
+ // `pending` → `used` proceeds, so two concurrent redemptions can't both reset.
120
+ if (!(await this.deps.passwordResetTokenRepository.consume(record.id))) {
121
+ throw new NotFoundError('PasswordResetToken', 'token');
122
+ }
123
+ // Replace the secret in place (idempotent on `(provider, subject)`, exactly like the
124
+ // rehash-on-login path in UserService).
125
+ await this.deps.userRepository.linkIdentity({
126
+ ...identity,
127
+ secret: await this.deps.passwordHasher.hash(newPassword),
128
+ });
129
+ // Invalidate any other live tokens for this user — the password is now changed.
130
+ const pending = await this.deps.passwordResetTokenRepository.listPendingByUser(record.userId);
131
+ for (const other of pending) {
132
+ if (other.id !== record.id) {
133
+ await this.deps.passwordResetTokenRepository.setStatus(other.id, 'used');
134
+ }
135
+ }
136
+ }
137
+ }
138
+ function resetEmailHtml(resetUrl) {
139
+ return `<!doctype html><html><body style="font-family:sans-serif">
140
+ <h2>Reset your password</h2>
141
+ <p>We received a request to reset your Cat Factory password. This link is valid for 1 hour.</p>
142
+ <p><a href="${resetUrl}" style="display:inline-block;padding:10px 18px;background:#111;color:#fff;border-radius:6px;text-decoration:none">Reset password</a></p>
143
+ <p>Or paste this link into your browser:<br>${resetUrl}</p>
144
+ <p>If you didn't request this, you can safely ignore this email.</p>
145
+ </body></html>`;
146
+ }
147
+ //# sourceMappingURL=PasswordResetService.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"PasswordResetService.js","sourceRoot":"","sources":["../../../src/modules/auth/PasswordResetService.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cat-factory/workspaces",
3
- "version": "0.7.46",
3
+ "version": "0.8.0",
4
4
  "description": "Tenancy base services (workspaces and accounts) for the Agent Architecture Board.",
5
5
  "repository": {
6
6
  "type": "git",
@@ -24,14 +24,17 @@
24
24
  "access": "public"
25
25
  },
26
26
  "dependencies": {
27
- "@cat-factory/contracts": "0.36.0",
28
- "@cat-factory/kernel": "0.38.1"
27
+ "@cat-factory/contracts": "0.37.0",
28
+ "@cat-factory/kernel": "0.39.0"
29
29
  },
30
30
  "devDependencies": {
31
- "typescript": "7.0.1-rc"
31
+ "typescript": "7.0.1-rc",
32
+ "vitest": "^4.1.9"
32
33
  },
33
34
  "scripts": {
34
35
  "build": "tsc -b tsconfig.build.json",
35
- "typecheck": "tsc -p tsconfig.json --noEmit"
36
+ "typecheck": "tsc -p tsconfig.json --noEmit",
37
+ "test": "vitest run",
38
+ "test:run": "vitest run"
36
39
  }
37
40
  }