@boopkit/cap-users 0.6.0 → 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.
@@ -0,0 +1,69 @@
1
+ // @boopkit/cap-users template — Rehash cleartext credentials stored in property
2
+ // tables (boop-j8xc credential-hashing epic).
3
+ //
4
+ // Some property payloads once stored a raw, redeemable bearer credential beside
5
+ // its correctly-hashed twin: session tokens and API keys in the `authorization`
6
+ // user-property (and, once cap-users ships it, invitation tokens in the
7
+ // `team_invitations` account-property). The upgraded services store the hash and
8
+ // read tolerantly, but existing rows keep the raw value until they are rewritten.
9
+ // This script rehashes them IN PLACE, so no user is logged out and no pending
10
+ // invitation is voided.
11
+ //
12
+ // Usage:
13
+ // npm run credentials:migrate # REPORT ONLY — counts what would change, writes nothing
14
+ // npm run credentials:migrate -- --run # apply the rehash
15
+ //
16
+ // ⚠️ ONE-WAY, NO ROLLBACK. Hashing is irreversible — once a row is rewritten the
17
+ // raw value is gone for good. TAKE A DATABASE BACKUP before running --run.
18
+ //
19
+ // Safe to re-run: idempotency is decided by value shape (a SHA-256 digest is left
20
+ // untouched), so a value is never double-hashed. Provider access/refresh tokens
21
+ // in the `authentication` property are never touched — they are replayed to
22
+ // Google/Microsoft and must stay raw.
23
+
24
+ import { migrateCredentialStore, authorizationStore, teamInvitationsStore, type CredentialStore, type MigrationDelegate } from '@boopkit/cap-users';
25
+ import db from '../db';
26
+
27
+ const run = process.argv.includes('--run');
28
+
29
+ // The stores this deployment migrates — the same runner drives every one:
30
+ // - `authorization` user-property: raw session tokens and API keys (boop-j8xc.2)
31
+ // - `team_invitations` account-property: raw invitation tokens (boop-j8xc.3)
32
+ const targets: { model: MigrationDelegate; store: CredentialStore }[] = [
33
+ { model: db.userProperty as unknown as MigrationDelegate, store: authorizationStore },
34
+ { model: db.accountProperty as unknown as MigrationDelegate, store: teamInvitationsStore },
35
+ ];
36
+
37
+ async function main() {
38
+ if (!run) {
39
+ console.log('=== REPORT ONLY (no writes) — pass --run to apply ===\n');
40
+ } else {
41
+ console.log('=== APPLYING migration (--run) ===\n');
42
+ }
43
+
44
+ let totalPending = 0;
45
+ for (const { model, store } of targets) {
46
+ const report = await migrateCredentialStore(model, store, { run });
47
+ totalPending += report.needsMigration;
48
+ const action = run ? `rehashed ${report.migrated}` : `${report.needsMigration} would be rehashed`;
49
+ console.log(
50
+ `${store.label}: scanned ${report.scanned}, ${action} ` +
51
+ `(${report.scanned - report.needsMigration} already migrated)`,
52
+ );
53
+ }
54
+
55
+ if (!run && totalPending > 0) {
56
+ console.log(`\n${totalPending} row(s) hold a raw credential. Back up your database, then re-run with --run.`);
57
+ } else if (run) {
58
+ console.log('\nDone. All targeted rows are hashed and tagged.');
59
+ } else {
60
+ console.log('\nNothing to migrate — every targeted row is already hashed.');
61
+ }
62
+ }
63
+
64
+ main()
65
+ .catch((err) => {
66
+ console.error('Migration failed:', err);
67
+ process.exitCode = 1;
68
+ })
69
+ .finally(() => db.$disconnect());
@@ -60,8 +60,18 @@ declare global {
60
60
  * the session cookie via resolveVisitor. A gate that must accept only one
61
61
  * kind of credential (requireApiKey) reads this — req.user alone cannot
62
62
  * tell a key principal from a cookie one.
63
+ *
64
+ * `string`, not the `'api-key'` literal, because the label is chosen by
65
+ * whoever built the bearer middleware: `createBearerMiddleware` writes
66
+ * whatever `options.strategy` it was given, so a capability that issues
67
+ * its own tokens labels them its own way (oauth-server uses `'oauth'`).
68
+ * cap-users' own `resolveBearer` still writes `'api-key'`, and
69
+ * `requireApiKey` still compares against that literal — which is what
70
+ * makes another capability's token fail a key-only gate rather than pass
71
+ * it. Widening the label and widening the gate are NOT the same change;
72
+ * only the label widens.
63
73
  */
64
- authStrategy?: 'api-key';
74
+ authStrategy?: string;
65
75
  }
66
76
  }
67
77
  }