@spfn/auth 0.3.0-beta.1 → 0.3.0-beta.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/README.md CHANGED
@@ -720,9 +720,18 @@ Programmatic checks (server): `hasPermission`, `hasAnyPermission`, `hasAllPermis
720
720
 
721
721
  Yes, and that is the point of the operator half of this package. The day after you deploy,
722
722
  someone has to refund an order, look up a user, publish a change, retry a failed job. The
723
- usual answer is to build screens for each of those. `@spfn/auth` already knows who your
724
- operators are and which of them may do what; [`@spfn/mcp`](../mcp/README.md) turns those
725
- operations into tools an AI agent can run, so the screens never get built.
723
+ usual answer is to build screens for each of those. SPFN's answer is to expose those
724
+ operations to an agent instead, and there are two transports for that:
725
+
726
+ - **CLI-first (the default)**: develop ops as routes with
727
+ [`createOpsRouter`](../core/README.md#how-do-i-operate-the-app-from-the-terminal),
728
+ authenticate them with [ops tokens](#ops-tokens-spfn-ops), and drive them with
729
+ `spfn ops` from the same terminal the app was built in.
730
+ - **MCP**: [`@spfn/mcp`](../mcp/README.md) turns operations into tools a chat client's
731
+ agent can run — the fit when operators work outside a terminal.
732
+
733
+ `@spfn/auth` already knows who your operators are and which of them may do what; the MCP
734
+ wiring below shows how those answers reach `@spfn/mcp`.
726
735
 
727
736
  The connection is app code, deliberately. `@spfn/mcp` does not read this package's RBAC on
728
737
  its own — it asks you for a `validateToken` and a `listTools`, and those are where auth's
@@ -832,6 +841,68 @@ For short-lived authenticated handshakes (e.g. SSE) where a `Bearer` header is a
832
841
  with `authApi.issueOneTimeToken`, protect the consuming route with the `oneTimeTokenAuth`
833
842
  middleware. Call `initOneTimeTokenManager({ ttl, store })` during setup for a custom TTL/store.
834
843
 
844
+ ## Ops tokens (`spfn ops`)
845
+
846
+ The machine credential behind the CLI-first ops surface
847
+ ([`@spfn/core/ops`](../core/README.md#how-do-i-operate-the-app-from-the-terminal)). An ops
848
+ token is not a user session: it carries a label and a scope list, only its SHA-256 hash is
849
+ stored, and the secret is shown exactly once at issuance.
850
+
851
+ ```typescript
852
+ // src/server/ops.ts — the app develops its own ops as routes
853
+ import { createOpsRouter, opsRoute } from '@spfn/core/ops';
854
+ import { opsTokenAuth, requireOpsScope } from '@spfn/auth/server';
855
+
856
+ export const opsRouter = createOpsRouter({
857
+ listSignups: opsRoute.get('/signups')
858
+ .use([requireOpsScope('waitlist:read')])
859
+ .handler(async () => signupsRepository.list()),
860
+ }, { auth: opsTokenAuth });
861
+ ```
862
+
863
+ `opsRoute` comes from `@spfn/core` **0.3.0-beta.2** onwards; before that release an ops
864
+ route spelled its own `/_ops/` prefix with `route`.
865
+
866
+ Issue and manage tokens against the running app, signed in as an administrator. The CLI
867
+ prompts for the administrator's email and password, so nothing here needs database access:
868
+
869
+ ```bash
870
+ spfn ops token issue --name laptop --scopes 'waitlist:read' --app https://api.example.com
871
+ spfn ops token issue --name laptop --scopes '*' --to-keychain --app https://api.example.com
872
+ spfn ops token list --app https://api.example.com
873
+ spfn ops token revoke 3 --app https://api.example.com
874
+ ```
875
+
876
+ Behind those commands are three admin-only routes, mounted with the rest of the auth
877
+ router:
878
+
879
+ | Route | What it does |
880
+ | --- | --- |
881
+ | `POST /_auth/ops-tokens` | Issue. The secret is in this answer and nowhere else. |
882
+ | `GET /_auth/ops-tokens` | List. Only hashes were stored, so no secret can be returned. |
883
+ | `DELETE /_auth/ops-tokens/:id` | Revoke. Permanent, and effective immediately. |
884
+
885
+ Each requires `authenticate` plus `requireRole('admin', 'superadmin')`. The administrator
886
+ seeded from `SPFN_AUTH_ADMIN_*` (see [Admin seeding](#admin-seeding))
887
+ signs in with a password, so this works in an app whose end users only sign in socially.
888
+
889
+ Issuance takes `expiresInDays` from 1 to 36500 (about a century), or `null` for a token that
890
+ never expires. There is an upper bound because a day count becomes a date by arithmetic, and
891
+ a big enough count produces an invalid date rather than a distant one — a refusal the route
892
+ should answer with a message, not with whatever the driver says about a value it cannot store.
893
+
894
+ SPFN authenticates a request with a JWT the client signs itself, so the CLI generates a key
895
+ pair, hands the public half over at login, signs the one call it needs, and revokes the key
896
+ before the command ends — on the failing path as much as the succeeding one.
897
+ `@spfn/auth/crypto` exports the two functions that take part (`generateKeyPair`,
898
+ `generateClientToken`) without pulling in the auth server; it exists from **0.3.0-beta.2**,
899
+ which is the floor the `spfn` CLI declares for this package.
900
+
901
+ Verification refuses uniformly: an expired, revoked, or never-issued token all answer the
902
+ same 401, so whether a presented secret ever existed is not inferable. A valid token
903
+ missing a route's scope answers 403 naming only the missing scope. `'*'` grants every
904
+ scope.
905
+
835
906
  ## Mobile clientProofV1 (`@spfn/auth/client-proof`)
836
907
 
837
908
  Server side of the spfn-mobile native SDK auth profile (issue #46; asymmetric revision in
@@ -1252,6 +1252,50 @@ declare const mainAuthRouter: _spfn_core_route.Router<{
1252
1252
  userId: number;
1253
1253
  roleId: number;
1254
1254
  }>;
1255
+ issueOpsToken: _spfn_core_route.RouteDef<{
1256
+ body: _sinclair_typebox.TObject<{
1257
+ name: _sinclair_typebox.TString;
1258
+ scopes: _sinclair_typebox.TArray<_sinclair_typebox.TString>;
1259
+ expiresInDays: _sinclair_typebox.TOptional<_sinclair_typebox.TUnion<[_sinclair_typebox.TNumber, _sinclair_typebox.TNull]>>;
1260
+ }>;
1261
+ }, {}, {
1262
+ token: string;
1263
+ opsToken: {
1264
+ id: number;
1265
+ name: string;
1266
+ scopes: string[];
1267
+ expiresAt: string | null;
1268
+ revokedAt: string | null;
1269
+ lastUsedAt: string | null;
1270
+ createdAt: string | null;
1271
+ };
1272
+ }>;
1273
+ listOpsTokens: _spfn_core_route.RouteDef<{}, {}, {
1274
+ opsTokens: {
1275
+ id: number;
1276
+ name: string;
1277
+ scopes: string[];
1278
+ expiresAt: string | null;
1279
+ revokedAt: string | null;
1280
+ lastUsedAt: string | null;
1281
+ createdAt: string | null;
1282
+ }[];
1283
+ }>;
1284
+ revokeOpsToken: _spfn_core_route.RouteDef<{
1285
+ params: _sinclair_typebox.TObject<{
1286
+ id: _sinclair_typebox.TNumber;
1287
+ }>;
1288
+ }, {}, {
1289
+ opsToken: {
1290
+ id: number;
1291
+ name: string;
1292
+ scopes: string[];
1293
+ expiresAt: string | null;
1294
+ revokedAt: string | null;
1295
+ lastUsedAt: string | null;
1296
+ createdAt: string | null;
1297
+ };
1298
+ }>;
1255
1299
  }>;
1256
1300
 
1257
1301
  /**
@@ -0,0 +1,61 @@
1
+ import { K as KeyAlgorithmType } from './types-DYyhze28.js';
2
+ import { Algorithm } from 'jsonwebtoken';
3
+
4
+ /**
5
+ * @spfn/auth - Client Crypto Helpers
6
+ *
7
+ * ES256 (ECDSA P-256) key generation and JWT signing for Next.js
8
+ * Keys are stored in DER format (Base64 encoded) for efficiency
9
+ *
10
+ * Key Sizes:
11
+ * - ES256 (ECDSA P-256): ~91 bytes (Base64: ~120 chars)
12
+ * - RS256 (RSA 2048): ~294 bytes (Base64: ~392 chars)
13
+ */
14
+
15
+ type Unit = 'Years' | 'Year' | 'Yrs' | 'Yr' | 'Y' | 'Weeks' | 'Week' | 'W' | 'Days' | 'Day' | 'D' | 'Hours' | 'Hour' | 'Hrs' | 'Hr' | 'H' | 'Minutes' | 'Minute' | 'Mins' | 'Min' | 'M' | 'Seconds' | 'Second' | 'Secs' | 'Sec' | 's' | 'Milliseconds' | 'Millisecond' | 'Msecs' | 'Msec' | 'Ms';
16
+ type UnitAnyCase = Unit | Uppercase<Unit> | Lowercase<Unit>;
17
+ type StringValue = `${number}` | `${number}${UnitAnyCase}` | `${number} ${UnitAnyCase}`;
18
+ interface KeyPair {
19
+ privateKey: string;
20
+ publicKey: string;
21
+ keyId: string;
22
+ fingerprint: string;
23
+ algorithm: KeyAlgorithmType;
24
+ }
25
+ /**
26
+ * Generate ECDSA P-256 key pair (ES256)
27
+ * Recommended for optimal size and performance
28
+ */
29
+ declare function generateKeyPairES256(): KeyPair;
30
+ /**
31
+ * Generate RSA 2048 key pair (RS256)
32
+ * Fallback option, larger size but wider compatibility
33
+ */
34
+ declare function generateKeyPairRS256(): KeyPair;
35
+ /**
36
+ * Generate key pair (defaults to ES256)
37
+ */
38
+ declare function generateKeyPair(algorithm?: KeyAlgorithmType): KeyPair;
39
+ /**
40
+ * Generate JWT signed with client private key (DER format)
41
+ */
42
+ declare function generateClientToken(payload: Record<string, any>, privateKeyB64: string, algorithm: Algorithm, options?: {
43
+ expiresIn?: StringValue | number;
44
+ issuer?: string;
45
+ }): string;
46
+ /**
47
+ * Get key size information
48
+ */
49
+ declare function getKeySize(publicKeyB64: string): {
50
+ bytes: number;
51
+ base64Length: number;
52
+ };
53
+ /**
54
+ * Check if key should be rotated based on creation date
55
+ */
56
+ declare function shouldRotateKey(createdAt: Date, rotationDays?: number): {
57
+ shouldRotate: boolean;
58
+ daysRemaining: number;
59
+ };
60
+
61
+ export { KeyAlgorithmType, type KeyPair, generateClientToken, generateKeyPair, generateKeyPairES256, generateKeyPairRS256, getKeySize, shouldRotateKey };
package/dist/crypto.js ADDED
@@ -0,0 +1,108 @@
1
+ // src/server/lib/crypto.ts
2
+ import crypto from "crypto";
3
+ import jwt from "jsonwebtoken";
4
+ function generateKeyPairES256() {
5
+ const keyId = crypto.randomUUID();
6
+ const { privateKey, publicKey } = crypto.generateKeyPairSync("ec", {
7
+ namedCurve: "P-256",
8
+ // ES256
9
+ publicKeyEncoding: {
10
+ type: "spki",
11
+ format: "der"
12
+ },
13
+ privateKeyEncoding: {
14
+ type: "pkcs8",
15
+ format: "der"
16
+ }
17
+ });
18
+ const privateKeyB64 = privateKey.toString("base64");
19
+ const publicKeyB64 = publicKey.toString("base64");
20
+ const fingerprint = crypto.createHash("sha256").update(publicKey).digest("hex");
21
+ return {
22
+ privateKey: privateKeyB64,
23
+ publicKey: publicKeyB64,
24
+ keyId,
25
+ fingerprint,
26
+ algorithm: "ES256"
27
+ };
28
+ }
29
+ function generateKeyPairRS256() {
30
+ const keyId = crypto.randomUUID();
31
+ const { privateKey, publicKey } = crypto.generateKeyPairSync("rsa", {
32
+ modulusLength: 2048,
33
+ publicKeyEncoding: {
34
+ type: "spki",
35
+ format: "der"
36
+ },
37
+ privateKeyEncoding: {
38
+ type: "pkcs8",
39
+ format: "der"
40
+ }
41
+ });
42
+ const privateKeyB64 = privateKey.toString("base64");
43
+ const publicKeyB64 = publicKey.toString("base64");
44
+ const fingerprint = crypto.createHash("sha256").update(publicKey).digest("hex");
45
+ return {
46
+ privateKey: privateKeyB64,
47
+ publicKey: publicKeyB64,
48
+ keyId,
49
+ fingerprint,
50
+ algorithm: "RS256"
51
+ };
52
+ }
53
+ function generateKeyPair(algorithm = "ES256") {
54
+ return algorithm === "ES256" ? generateKeyPairES256() : generateKeyPairRS256();
55
+ }
56
+ function generateClientToken(payload, privateKeyB64, algorithm, options) {
57
+ try {
58
+ const privateKeyDER = Buffer.from(privateKeyB64, "base64");
59
+ const privateKeyObject = crypto.createPrivateKey({
60
+ key: privateKeyDER,
61
+ format: "der",
62
+ type: "pkcs8"
63
+ });
64
+ const privateKeyPEM = privateKeyObject.export({
65
+ type: "pkcs8",
66
+ format: "pem"
67
+ });
68
+ const signOptions = {
69
+ algorithm,
70
+ issuer: options?.issuer || "spfn-client",
71
+ expiresIn: options?.expiresIn ?? "15m"
72
+ // Default to 15 minutes
73
+ };
74
+ return jwt.sign(payload, privateKeyPEM, signOptions);
75
+ } catch (error) {
76
+ throw new Error(
77
+ `Failed to generate client token: ${error instanceof Error ? error.message : "Unknown error"}`
78
+ );
79
+ }
80
+ }
81
+ function getKeySize(publicKeyB64) {
82
+ const keyDER = Buffer.from(publicKeyB64, "base64");
83
+ return {
84
+ bytes: keyDER.length,
85
+ base64Length: publicKeyB64.length
86
+ };
87
+ }
88
+ function shouldRotateKey(createdAt, rotationDays = 90) {
89
+ const now = /* @__PURE__ */ new Date();
90
+ const ageInDays = Math.floor(
91
+ (now.getTime() - createdAt.getTime()) / (1e3 * 60 * 60 * 24)
92
+ );
93
+ const daysRemaining = Math.max(0, rotationDays - ageInDays);
94
+ return {
95
+ shouldRotate: daysRemaining <= 7,
96
+ // Warn 7 days before expiry
97
+ daysRemaining
98
+ };
99
+ }
100
+ export {
101
+ generateClientToken,
102
+ generateKeyPair,
103
+ generateKeyPairES256,
104
+ generateKeyPairRS256,
105
+ getKeySize,
106
+ shouldRotateKey
107
+ };
108
+ //# sourceMappingURL=crypto.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/server/lib/crypto.ts"],"sourcesContent":["/**\n * @spfn/auth - Client Crypto Helpers\n *\n * ES256 (ECDSA P-256) key generation and JWT signing for Next.js\n * Keys are stored in DER format (Base64 encoded) for efficiency\n *\n * Key Sizes:\n * - ES256 (ECDSA P-256): ~91 bytes (Base64: ~120 chars)\n * - RS256 (RSA 2048): ~294 bytes (Base64: ~392 chars)\n */\n\nimport { type KeyAlgorithmType } from '../types';\nimport crypto from 'crypto';\nimport jwt, { type Algorithm, type SignOptions } from 'jsonwebtoken';\n\ntype Unit =\n | 'Years'\n | 'Year'\n | 'Yrs'\n | 'Yr'\n | 'Y'\n | 'Weeks'\n | 'Week'\n | 'W'\n | 'Days'\n | 'Day'\n | 'D'\n | 'Hours'\n | 'Hour'\n | 'Hrs'\n | 'Hr'\n | 'H'\n | 'Minutes'\n | 'Minute'\n | 'Mins'\n | 'Min'\n | 'M'\n | 'Seconds'\n | 'Second'\n | 'Secs'\n | 'Sec'\n | 's'\n | 'Milliseconds'\n | 'Millisecond'\n | 'Msecs'\n | 'Msec'\n | 'Ms';\n\ntype UnitAnyCase = Unit | Uppercase<Unit> | Lowercase<Unit>;\n\ntype StringValue =\n | `${number}`\n | `${number}${UnitAnyCase}`\n | `${number} ${UnitAnyCase}`;\n\nexport interface KeyPair\n{\n privateKey: string; // Base64 encoded DER\n publicKey: string; // Base64 encoded DER\n keyId: string; // UUID\n fingerprint: string; // SHA-256 hash\n algorithm: KeyAlgorithmType;\n}\n\n/**\n * Generate ECDSA P-256 key pair (ES256)\n * Recommended for optimal size and performance\n */\nexport function generateKeyPairES256(): KeyPair\n{\n const keyId = crypto.randomUUID();\n\n const { privateKey, publicKey } = crypto.generateKeyPairSync('ec', {\n namedCurve: 'P-256', // ES256\n publicKeyEncoding: {\n type: 'spki',\n format: 'der',\n },\n privateKeyEncoding: {\n type: 'pkcs8',\n format: 'der',\n },\n });\n\n // Convert Buffer to Base64\n const privateKeyB64 = privateKey.toString('base64');\n const publicKeyB64 = publicKey.toString('base64');\n\n // Generate fingerprint (SHA-256 of public key)\n const fingerprint = crypto\n .createHash('sha256')\n .update(publicKey)\n .digest('hex');\n\n return {\n privateKey: privateKeyB64,\n publicKey: publicKeyB64,\n keyId,\n fingerprint,\n algorithm: 'ES256',\n };\n}\n\n/**\n * Generate RSA 2048 key pair (RS256)\n * Fallback option, larger size but wider compatibility\n */\nexport function generateKeyPairRS256(): KeyPair\n{\n const keyId = crypto.randomUUID();\n\n const { privateKey, publicKey } = crypto.generateKeyPairSync('rsa', {\n modulusLength: 2048,\n publicKeyEncoding: {\n type: 'spki',\n format: 'der',\n },\n privateKeyEncoding: {\n type: 'pkcs8',\n format: 'der',\n },\n });\n\n const privateKeyB64 = privateKey.toString('base64');\n const publicKeyB64 = publicKey.toString('base64');\n\n const fingerprint = crypto\n .createHash('sha256')\n .update(publicKey)\n .digest('hex');\n\n return {\n privateKey: privateKeyB64,\n publicKey: publicKeyB64,\n keyId,\n fingerprint,\n algorithm: 'RS256',\n };\n}\n\n/**\n * Generate key pair (defaults to ES256)\n */\nexport function generateKeyPair(\n algorithm: KeyAlgorithmType = 'ES256',\n): KeyPair\n{\n return algorithm === 'ES256'\n ? generateKeyPairES256()\n : generateKeyPairRS256();\n}\n\n/**\n * Generate JWT signed with client private key (DER format)\n */\nexport function generateClientToken(\n payload: Record<string, any>,\n privateKeyB64: string,\n algorithm: Algorithm,\n options?: {\n expiresIn?: StringValue | number;\n issuer?: string;\n },\n): string\n{\n try\n {\n // Convert Base64 back to Buffer\n const privateKeyDER = Buffer.from(privateKeyB64, 'base64');\n\n // Create key object for signing\n const privateKeyObject = crypto.createPrivateKey({\n key: privateKeyDER,\n format: 'der',\n type: 'pkcs8',\n });\n\n // Export as PEM for jwt.sign\n const privateKeyPEM = privateKeyObject.export({\n type: 'pkcs8',\n format: 'pem',\n });\n\n const signOptions: SignOptions = {\n algorithm,\n issuer: options?.issuer || 'spfn-client',\n expiresIn: options?.expiresIn ?? '15m', // Default to 15 minutes\n };\n\n return jwt.sign(payload, privateKeyPEM, signOptions);\n }\n catch (error)\n {\n throw new Error(\n `Failed to generate client token: ${error instanceof Error ? error.message : 'Unknown error'}`,\n );\n }\n}\n\n/**\n * Get key size information\n */\nexport function getKeySize(publicKeyB64: string): {\n bytes: number;\n base64Length: number;\n}\n{\n const keyDER = Buffer.from(publicKeyB64, 'base64');\n\n return {\n bytes: keyDER.length,\n base64Length: publicKeyB64.length,\n };\n}\n\n/**\n * Check if key should be rotated based on creation date\n */\nexport function shouldRotateKey(\n createdAt: Date,\n rotationDays: number = 90,\n): {\n shouldRotate: boolean;\n daysRemaining: number;\n}\n{\n const now = new Date();\n const ageInDays = Math.floor(\n (now.getTime() - createdAt.getTime()) / (1000 * 60 * 60 * 24),\n );\n const daysRemaining = Math.max(0, rotationDays - ageInDays);\n\n return {\n shouldRotate: daysRemaining <= 7, // Warn 7 days before expiry\n daysRemaining,\n };\n}\n"],"mappings":";AAYA,OAAO,YAAY;AACnB,OAAO,SAA+C;AAuD/C,SAAS,uBAChB;AACI,QAAM,QAAQ,OAAO,WAAW;AAEhC,QAAM,EAAE,YAAY,UAAU,IAAI,OAAO,oBAAoB,MAAM;AAAA,IAC/D,YAAY;AAAA;AAAA,IACZ,mBAAmB;AAAA,MACf,MAAM;AAAA,MACN,QAAQ;AAAA,IACZ;AAAA,IACA,oBAAoB;AAAA,MAChB,MAAM;AAAA,MACN,QAAQ;AAAA,IACZ;AAAA,EACJ,CAAC;AAGD,QAAM,gBAAgB,WAAW,SAAS,QAAQ;AAClD,QAAM,eAAe,UAAU,SAAS,QAAQ;AAGhD,QAAM,cAAc,OACf,WAAW,QAAQ,EACnB,OAAO,SAAS,EAChB,OAAO,KAAK;AAEjB,SAAO;AAAA,IACH,YAAY;AAAA,IACZ,WAAW;AAAA,IACX;AAAA,IACA;AAAA,IACA,WAAW;AAAA,EACf;AACJ;AAMO,SAAS,uBAChB;AACI,QAAM,QAAQ,OAAO,WAAW;AAEhC,QAAM,EAAE,YAAY,UAAU,IAAI,OAAO,oBAAoB,OAAO;AAAA,IAChE,eAAe;AAAA,IACf,mBAAmB;AAAA,MACf,MAAM;AAAA,MACN,QAAQ;AAAA,IACZ;AAAA,IACA,oBAAoB;AAAA,MAChB,MAAM;AAAA,MACN,QAAQ;AAAA,IACZ;AAAA,EACJ,CAAC;AAED,QAAM,gBAAgB,WAAW,SAAS,QAAQ;AAClD,QAAM,eAAe,UAAU,SAAS,QAAQ;AAEhD,QAAM,cAAc,OACf,WAAW,QAAQ,EACnB,OAAO,SAAS,EAChB,OAAO,KAAK;AAEjB,SAAO;AAAA,IACH,YAAY;AAAA,IACZ,WAAW;AAAA,IACX;AAAA,IACA;AAAA,IACA,WAAW;AAAA,EACf;AACJ;AAKO,SAAS,gBACZ,YAA8B,SAElC;AACI,SAAO,cAAc,UACf,qBAAqB,IACrB,qBAAqB;AAC/B;AAKO,SAAS,oBACZ,SACA,eACA,WACA,SAKJ;AACI,MACA;AAEI,UAAM,gBAAgB,OAAO,KAAK,eAAe,QAAQ;AAGzD,UAAM,mBAAmB,OAAO,iBAAiB;AAAA,MAC7C,KAAK;AAAA,MACL,QAAQ;AAAA,MACR,MAAM;AAAA,IACV,CAAC;AAGD,UAAM,gBAAgB,iBAAiB,OAAO;AAAA,MAC1C,MAAM;AAAA,MACN,QAAQ;AAAA,IACZ,CAAC;AAED,UAAM,cAA2B;AAAA,MAC7B;AAAA,MACA,QAAQ,SAAS,UAAU;AAAA,MAC3B,WAAW,SAAS,aAAa;AAAA;AAAA,IACrC;AAEA,WAAO,IAAI,KAAK,SAAS,eAAe,WAAW;AAAA,EACvD,SACO,OACP;AACI,UAAM,IAAI;AAAA,MACN,oCAAoC,iBAAiB,QAAQ,MAAM,UAAU,eAAe;AAAA,IAChG;AAAA,EACJ;AACJ;AAKO,SAAS,WAAW,cAI3B;AACI,QAAM,SAAS,OAAO,KAAK,cAAc,QAAQ;AAEjD,SAAO;AAAA,IACH,OAAO,OAAO;AAAA,IACd,cAAc,aAAa;AAAA,EAC/B;AACJ;AAKO,SAAS,gBACZ,WACA,eAAuB,IAK3B;AACI,QAAM,MAAM,oBAAI,KAAK;AACrB,QAAM,YAAY,KAAK;AAAA,KAClB,IAAI,QAAQ,IAAI,UAAU,QAAQ,MAAM,MAAO,KAAK,KAAK;AAAA,EAC9D;AACA,QAAM,gBAAgB,KAAK,IAAI,GAAG,eAAe,SAAS;AAE1D,SAAO;AAAA,IACH,cAAc,iBAAiB;AAAA;AAAA,IAC/B;AAAA,EACJ;AACJ;","names":[]}
package/dist/index.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import * as _spfn_core_nextjs from '@spfn/core/nextjs';
2
- import { P as PermissionConfig, R as RoleConfig, m as mainAuthRouter, S as SendVerificationCodeResult, a as RegisterResult, L as LoginResult, b as RotateKeyResult, K as KeySummary, c as RevokeAllKeysResult, I as IssueOneTimeTokenResult, O as OAuthStartResult, d as OAuthNativeResult, U as UserProfile, e as ProfileInfo } from './authenticate-DlTGaBT8.js';
3
- export { A as AuthInitOptions, f as AuthSession, g as PERMISSION_CATEGORIES, h as PermissionCategory, V as VERIFICATION_PURPOSES, i as VERIFICATION_TARGET_TYPES, j as VerificationPurpose, k as VerificationTargetType } from './authenticate-DlTGaBT8.js';
2
+ import { P as PermissionConfig, R as RoleConfig, m as mainAuthRouter, S as SendVerificationCodeResult, a as RegisterResult, L as LoginResult, b as RotateKeyResult, K as KeySummary, c as RevokeAllKeysResult, I as IssueOneTimeTokenResult, O as OAuthStartResult, d as OAuthNativeResult, U as UserProfile, e as ProfileInfo } from './authenticate-55LeXHqZ.js';
3
+ export { A as AuthInitOptions, f as AuthSession, g as PERMISSION_CATEGORIES, h as PermissionCategory, V as VERIFICATION_PURPOSES, i as VERIFICATION_TARGET_TYPES, j as VerificationPurpose, k as VerificationTargetType } from './authenticate-55LeXHqZ.js';
4
4
  import * as _spfn_core_route from '@spfn/core/route';
5
5
  import { HttpMethod } from '@spfn/core/route';
6
6
  export { A as ACCOUNT_DELETION_REQUESTED_BY, a as ACCOUNT_DELETION_REQUEST_STATUSES, b as AccountDeletionRequestStatus, c as AccountDeletionRequestedBy, I as INVITATION_STATUSES, d as InvitationStatus, e as KEY_ALGORITHM, f as KEY_DEVICE_NAME_MAX_LENGTH, g as KEY_PLATFORM, K as KeyAlgorithmType, h as KeyPlatformType, P as PURGE_STRATEGIES, i as PurgeStrategy, S as SOCIAL_PROVIDERS, j as SocialProvider, U as USER_STATUSES, k as UserStatus } from './types-DYyhze28.js';
@@ -563,6 +563,50 @@ declare const authApi: _spfn_core_nextjs.Client<_spfn_core_route.Router<{
563
563
  userId: number;
564
564
  roleId: number;
565
565
  }>;
566
+ issueOpsToken: _spfn_core_route.RouteDef<{
567
+ body: _sinclair_typebox.TObject<{
568
+ name: _sinclair_typebox.TString;
569
+ scopes: _sinclair_typebox.TArray<_sinclair_typebox.TString>;
570
+ expiresInDays: _sinclair_typebox.TOptional<_sinclair_typebox.TUnion<[_sinclair_typebox.TNumber, _sinclair_typebox.TNull]>>;
571
+ }>;
572
+ }, {}, {
573
+ token: string;
574
+ opsToken: {
575
+ id: number;
576
+ name: string;
577
+ scopes: string[];
578
+ expiresAt: string | null;
579
+ revokedAt: string | null;
580
+ lastUsedAt: string | null;
581
+ createdAt: string | null;
582
+ };
583
+ }>;
584
+ listOpsTokens: _spfn_core_route.RouteDef<{}, {}, {
585
+ opsTokens: {
586
+ id: number;
587
+ name: string;
588
+ scopes: string[];
589
+ expiresAt: string | null;
590
+ revokedAt: string | null;
591
+ lastUsedAt: string | null;
592
+ createdAt: string | null;
593
+ }[];
594
+ }>;
595
+ revokeOpsToken: _spfn_core_route.RouteDef<{
596
+ params: _sinclair_typebox.TObject<{
597
+ id: _sinclair_typebox.TNumber;
598
+ }>;
599
+ }, {}, {
600
+ opsToken: {
601
+ id: number;
602
+ name: string;
603
+ scopes: string[];
604
+ expiresAt: string | null;
605
+ revokedAt: string | null;
606
+ lastUsedAt: string | null;
607
+ createdAt: string | null;
608
+ };
609
+ }>;
566
610
  }>>;
567
611
  type AuthRouter = typeof mainAuthRouter;
568
612
 
package/dist/index.js CHANGED
@@ -298,7 +298,10 @@ var routeMap = {
298
298
  deleteAdminRole: { method: "DELETE", path: "/_auth/admin/roles/:id" },
299
299
  updateUserRole: { method: "PATCH", path: "/_auth/admin/users/:userId/role" },
300
300
  requestAccountDeletion: { method: "POST", path: "/_auth/deletion/request" },
301
- cancelAccountDeletion: { method: "POST", path: "/_auth/deletion/cancel" }
301
+ cancelAccountDeletion: { method: "POST", path: "/_auth/deletion/cancel" },
302
+ issueOpsToken: { method: "POST", path: "/_auth/ops-tokens" },
303
+ listOpsTokens: { method: "GET", path: "/_auth/ops-tokens" },
304
+ revokeOpsToken: { method: "DELETE", path: "/_auth/ops-tokens/:id" }
302
305
  };
303
306
 
304
307
  // src/lib/types.ts