@spfn/auth 0.3.0-beta.6 → 0.3.0-beta.7
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 +64 -0
- package/dist/{authenticate-98lBIMxP.d.ts → authenticate-Mg9D7Nys.d.ts} +60 -7
- package/dist/index.d.ts +6 -6
- package/dist/server.d.ts +87 -87
- package/dist/server.js +18 -1
- package/dist/server.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1178,6 +1178,70 @@ app.post('/v1/echo', createClientProofGuard(state), (c) => { /* handler */ });
|
|
|
1178
1178
|
Responses and errors MUST be canonical bytes with the contract envelope — build them with
|
|
1179
1179
|
`encodeCanonicalJson`/`ClientProofRefusal`, never `c.json()` (key order and int64 differ).
|
|
1180
1180
|
|
|
1181
|
+
## Custom auth profiles (`registerAuthProfile`)
|
|
1182
|
+
|
|
1183
|
+
`clientProofV1` is not a special case in the middleware — it is one entry in a registry
|
|
1184
|
+
`authenticate` and `optionalAuth` dispatch on. An app registers its own scheme the same way,
|
|
1185
|
+
without forking the middleware or wrapping it:
|
|
1186
|
+
|
|
1187
|
+
```typescript
|
|
1188
|
+
import { registerAuthProfile, type AuthContext } from '@spfn/auth/server';
|
|
1189
|
+
import { UnauthorizedError } from '@spfn/core/errors';
|
|
1190
|
+
|
|
1191
|
+
// At boot — server.config.ts, before the server starts taking requests.
|
|
1192
|
+
registerAuthProfile('serviceTokenV1', {
|
|
1193
|
+
verify: async (c): Promise<AuthContext> =>
|
|
1194
|
+
{
|
|
1195
|
+
const user = await findServiceAccount(c.req.header('x-acme-service-token'));
|
|
1196
|
+
if (user === null)
|
|
1197
|
+
{
|
|
1198
|
+
// A refusal leaves the verifier as a throw. It reaches the app's
|
|
1199
|
+
// error handler exactly as the Bearer path's does.
|
|
1200
|
+
throw new UnauthorizedError({ message: 'Invalid service token' });
|
|
1201
|
+
}
|
|
1202
|
+
|
|
1203
|
+
return {
|
|
1204
|
+
user,
|
|
1205
|
+
userId: String(user.id),
|
|
1206
|
+
keyId: 'service-token',
|
|
1207
|
+
role: null,
|
|
1208
|
+
locale: 'en',
|
|
1209
|
+
scheme: 'serviceTokenV1',
|
|
1210
|
+
};
|
|
1211
|
+
},
|
|
1212
|
+
});
|
|
1213
|
+
```
|
|
1214
|
+
|
|
1215
|
+
A request naming the profile is then answered by that verifier:
|
|
1216
|
+
|
|
1217
|
+
```http
|
|
1218
|
+
POST /v1/reports
|
|
1219
|
+
x-spfn-auth-profile: serviceTokenV1
|
|
1220
|
+
x-acme-service-token: <the app's own credential>
|
|
1221
|
+
```
|
|
1222
|
+
|
|
1223
|
+
- **Register at boot, before the first request.** The registry is read on every dispatch, so a
|
|
1224
|
+
profile registered later is simply a profile the requests before it did not have. Registration
|
|
1225
|
+
is not frozen after startup — it is a contract, not a runtime check.
|
|
1226
|
+
- **A duplicate name throws**, `clientProofV1` included. Replacing a registered verifier silently
|
|
1227
|
+
is how an import order or a copied profile name swaps the code that decides who is admitted, so
|
|
1228
|
+
there is no override — and no unregistration API for the same reason.
|
|
1229
|
+
- **The verifier must expose a callable `verify`**, and what it resolves must carry a `userId` — a
|
|
1230
|
+
verifier that cannot admit anyone is refused at boot, and a resolve without a principal (`null`,
|
|
1231
|
+
the JS idiom for "no user") is refused as a throw rather than routed as authenticated.
|
|
1232
|
+
- **An unknown profile is still refused** (`PROFILE_REJECTED`, 400): registering one name does not
|
|
1233
|
+
open the header to others.
|
|
1234
|
+
- **Mixing is still refused.** A request carrying both `x-spfn-auth-profile` and `Authorization` is
|
|
1235
|
+
rejected before either path runs; a custom verifier never sees it.
|
|
1236
|
+
- **A verifier's throw propagates**, and only the internal clientProofV1 contract refusal is
|
|
1237
|
+
answered with the canonical envelope. Under `optionalAuth` too: credentials that were presented
|
|
1238
|
+
and refused are never downgraded to anonymous passage — only "presented nothing" continues
|
|
1239
|
+
without an auth context.
|
|
1240
|
+
- **`AuthContext.scheme` is an open union** — `'bearer' | 'clientProofV1' | 'oneTimeToken' | (string
|
|
1241
|
+
& {})`. The built-in names keep their autocomplete and a registered profile names its own scheme.
|
|
1242
|
+
The field stays informational: downstream permission and tenant code takes one principal shape and
|
|
1243
|
+
never branches on how it was produced.
|
|
1244
|
+
|
|
1181
1245
|
## Account Deletion & Recovery
|
|
1182
1246
|
|
|
1183
1247
|
Grace-period deletion with in-window recovery, an admin/GDPR-response entry point for immediate
|
|
@@ -993,7 +993,7 @@ declare const mainAuthRouter: _spfn_core_route.Router<{
|
|
|
993
993
|
id: number;
|
|
994
994
|
name: string;
|
|
995
995
|
displayName: string;
|
|
996
|
-
category: "
|
|
996
|
+
category: "custom" | "user" | "auth" | "rbac" | "system" | undefined;
|
|
997
997
|
}[];
|
|
998
998
|
userId: number;
|
|
999
999
|
publicId: string;
|
|
@@ -1293,8 +1293,8 @@ declare const mainAuthRouter: _spfn_core_route.Router<{
|
|
|
1293
1293
|
}, {}, {
|
|
1294
1294
|
roles: {
|
|
1295
1295
|
description: string | null;
|
|
1296
|
-
name: string;
|
|
1297
1296
|
id: number;
|
|
1297
|
+
name: string;
|
|
1298
1298
|
displayName: string;
|
|
1299
1299
|
isBuiltin: boolean;
|
|
1300
1300
|
isSystem: boolean;
|
|
@@ -1315,8 +1315,8 @@ declare const mainAuthRouter: _spfn_core_route.Router<{
|
|
|
1315
1315
|
}, {}, {
|
|
1316
1316
|
role: {
|
|
1317
1317
|
description: string | null;
|
|
1318
|
-
name: string;
|
|
1319
1318
|
id: number;
|
|
1319
|
+
name: string;
|
|
1320
1320
|
displayName: string;
|
|
1321
1321
|
isBuiltin: boolean;
|
|
1322
1322
|
isSystem: boolean;
|
|
@@ -1339,8 +1339,8 @@ declare const mainAuthRouter: _spfn_core_route.Router<{
|
|
|
1339
1339
|
}, {}, {
|
|
1340
1340
|
role: {
|
|
1341
1341
|
description: string | null;
|
|
1342
|
-
name: string;
|
|
1343
1342
|
id: number;
|
|
1343
|
+
name: string;
|
|
1344
1344
|
displayName: string;
|
|
1345
1345
|
isBuiltin: boolean;
|
|
1346
1346
|
isSystem: boolean;
|
|
@@ -1424,6 +1424,11 @@ declare const mainAuthRouter: _spfn_core_route.Router<{
|
|
|
1424
1424
|
* downstream permission/tenant code consumes one principal shape and never
|
|
1425
1425
|
* branches on how it was authenticated.
|
|
1426
1426
|
*
|
|
1427
|
+
* An app adds its own scheme with `registerAuthProfile` at boot. The dispatch
|
|
1428
|
+
* it joins is the one below, unchanged: a name nobody registered is still
|
|
1429
|
+
* refused, and profile credentials mixed with an Authorization header are
|
|
1430
|
+
* still refused before either path runs.
|
|
1431
|
+
*
|
|
1427
1432
|
* The clientProofV1 verifier reuses the phase-1 admission pieces (header
|
|
1428
1433
|
* shape, canonical body, proof-input assembly, ECDSA verification) with two
|
|
1429
1434
|
* production substitutions: the key directory is `user_public_keys` via
|
|
@@ -1444,8 +1449,13 @@ interface AuthContext {
|
|
|
1444
1449
|
keyId: string;
|
|
1445
1450
|
role: string | null;
|
|
1446
1451
|
locale: string;
|
|
1447
|
-
/**
|
|
1448
|
-
|
|
1452
|
+
/**
|
|
1453
|
+
* How the principal was authenticated. Informational — downstream code
|
|
1454
|
+
* never branches on it. The union stays open for the profiles an app
|
|
1455
|
+
* registers itself: the built-in names keep their autocomplete, and a
|
|
1456
|
+
* registered profile names its own scheme without editing this file.
|
|
1457
|
+
*/
|
|
1458
|
+
scheme: 'bearer' | 'clientProofV1' | 'oneTimeToken' | (string & {});
|
|
1449
1459
|
}
|
|
1450
1460
|
/** A profile's verifier: admits the request and returns the principal, or throws. */
|
|
1451
1461
|
interface AuthProfileVerifier {
|
|
@@ -1492,6 +1502,49 @@ type AuthProfileOutcome = {
|
|
|
1492
1502
|
* handed to the generic error handler is classified by its class name instead.
|
|
1493
1503
|
*/
|
|
1494
1504
|
declare function runAuthProfile(c: Context): Promise<AuthProfileOutcome>;
|
|
1505
|
+
/**
|
|
1506
|
+
* Registers an app's own verifier under a profile name.
|
|
1507
|
+
*
|
|
1508
|
+
* Call it at boot, before the first request: the registry is a module-global
|
|
1509
|
+
* read on every dispatch, so a profile registered later is simply a profile
|
|
1510
|
+
* the requests before it did not have. There is no freeze and no
|
|
1511
|
+
* unregistration — an auth surface that can be rearranged at runtime is a
|
|
1512
|
+
* surface an app bug can rearrange.
|
|
1513
|
+
*
|
|
1514
|
+
* A duplicate name throws rather than replacing the verifier that holds it,
|
|
1515
|
+
* `clientProofV1` included. A silent override is how a second import order, or
|
|
1516
|
+
* a copied profile name, quietly swaps the code that decides who is admitted.
|
|
1517
|
+
*
|
|
1518
|
+
* The verifier must expose a callable `verify` — a value that cannot admit
|
|
1519
|
+
* anyone is refused at boot rather than becoming a registry entry the dispatch
|
|
1520
|
+
* reads as "no profile header", which is anonymous passage under
|
|
1521
|
+
* `optionalAuth` for a request that presented profile credentials.
|
|
1522
|
+
*
|
|
1523
|
+
* The verifier returns the same `AuthContext` the Bearer path sets and refuses
|
|
1524
|
+
* by throwing. A resolve that carries no `userId` is refused as a throw too —
|
|
1525
|
+
* "no user" is a refusal, never a principal. A throw is not caught here:
|
|
1526
|
+
* `runAuthProfile` answers the internal clientProofV1 contract refusal and
|
|
1527
|
+
* nothing else, so a verifier's own error reaches the app's generic error
|
|
1528
|
+
* handler exactly as the Bearer path's `UnauthorizedError` does — and never
|
|
1529
|
+
* becomes anonymous passage, not even under `optionalAuth`.
|
|
1530
|
+
*
|
|
1531
|
+
* @example
|
|
1532
|
+
* ```typescript
|
|
1533
|
+
* registerAuthProfile('serviceTokenV1', {
|
|
1534
|
+
* verify: async (c) =>
|
|
1535
|
+
* {
|
|
1536
|
+
* const user = await authenticateServiceToken(c.req.header('x-acme-service-token'));
|
|
1537
|
+
* if (user === null)
|
|
1538
|
+
* {
|
|
1539
|
+
* throw new UnauthorizedError({ message: 'Invalid service token' });
|
|
1540
|
+
* }
|
|
1541
|
+
*
|
|
1542
|
+
* return { user, userId: String(user.id), keyId: 'service', role: null, locale: 'en', scheme: 'serviceTokenV1' };
|
|
1543
|
+
* },
|
|
1544
|
+
* });
|
|
1545
|
+
* ```
|
|
1546
|
+
*/
|
|
1547
|
+
declare function registerAuthProfile(profileId: string, verifier: AuthProfileVerifier): void;
|
|
1495
1548
|
|
|
1496
1549
|
declare module 'hono' {
|
|
1497
1550
|
interface ContextVariableMap {
|
|
@@ -1558,4 +1611,4 @@ declare const authenticate: _spfn_core_route.NamedMiddleware<"auth">;
|
|
|
1558
1611
|
*/
|
|
1559
1612
|
declare const optionalAuth: _spfn_core_route.NamedMiddleware<"optionalAuth">;
|
|
1560
1613
|
|
|
1561
|
-
export { type SendVerificationCodeParams as $, type AuthInitOptions as A, type OAuthCodeExchangeOptions as B, type ConfirmSignupLinkResult as C, DeviceNameSchema as D, EmailSchema as E, type OAuthNativeParams as F, type OAuthStartParams as G, type OAuthTokens as H, type IssueOneTimeTokenResult as I, PasswordSchema as J, type KeySummary as K, type LoginResult as L, PhoneSchema as M, type NativeVerifyOptions as N, type OAuthStartResult as O, type PermissionConfig as P, PlatformSchema as Q, type RoleConfig as R, type SendVerificationCodeResult as S, type RegisterParams as T, type UserProfile as U, VERIFICATION_PURPOSES as V, type RegisterPublicKeyParams as W, type RequestSignupLinkParams as X, type RevokeAllKeysParams as Y, type RevokeKeyParams as Z, type RotateKeyParams as _, type RegisterResult as a, TargetTypeSchema as a0, type UnlinkNotification as a1, UnlinkNotifyRejection as a2, type UnlinkNotifyRequest as a3, type UnlinkNotifyResult as a4, VerificationPurposeSchema as a5, type VerifyCodeParams as a6, type VerifyCodeResult as a7, authenticate as a8, buildOAuthErrorUrl as a9,
|
|
1614
|
+
export { type SendVerificationCodeParams as $, type AuthInitOptions as A, type OAuthCodeExchangeOptions as B, type ConfirmSignupLinkResult as C, DeviceNameSchema as D, EmailSchema as E, type OAuthNativeParams as F, type OAuthStartParams as G, type OAuthTokens as H, type IssueOneTimeTokenResult as I, PasswordSchema as J, type KeySummary as K, type LoginResult as L, PhoneSchema as M, type NativeVerifyOptions as N, type OAuthStartResult as O, type PermissionConfig as P, PlatformSchema as Q, type RoleConfig as R, type SendVerificationCodeResult as S, type RegisterParams as T, type UserProfile as U, VERIFICATION_PURPOSES as V, type RegisterPublicKeyParams as W, type RequestSignupLinkParams as X, type RevokeAllKeysParams as Y, type RevokeKeyParams as Z, type RotateKeyParams as _, type RegisterResult as a, TargetTypeSchema as a0, type UnlinkNotification as a1, UnlinkNotifyRejection as a2, type UnlinkNotifyRequest as a3, type UnlinkNotifyResult as a4, VerificationPurposeSchema as a5, type VerifyCodeParams as a6, type VerifyCodeResult as a7, authenticate as a8, buildOAuthErrorUrl as a9, revokeKeyService as aA, rotateKeyService as aB, runAuthProfile as aC, selectAuthProfile as aD, sendVerificationCodeService as aE, verifyCodeService as aF, verifyOneTimeTokenService as aG, changePasswordService as aa, completeSignupService as ab, confirmSignupLinkService as ac, getEnabledOAuthProviders as ad, getGoogleAccessToken as ae, getOAuthProvider as af, getRegisteredProviders as ag, isOAuthProviderEnabled as ah, isSafeReturnPath as ai, issueOneTimeTokenService as aj, listKeysService as ak, loginService as al, logoutService as am, oauthCallbackService as an, oauthNativeService as ao, oauthStartService as ap, oauthUnlinkNotifyService as aq, optionalAuth as ar, registerAuthProfile as as, registerOAuthProvider as at, registerPublicKeyService as au, registerService as av, requestSignupLinkService as aw, requireEnabledProvider as ax, resolveAuthenticatedUser as ay, revokeAllKeysService as az, type RequestSignupLinkResult as b, type RotateKeyResult as c, type RevokeAllKeysResult as d, type OAuthNativeResult as e, type ProfileInfo as f, type AuthSession as g, PERMISSION_CATEGORIES as h, type PermissionCategory as i, VERIFICATION_TARGET_TYPES as j, type VerificationPurpose as k, type VerificationTargetType as l, mainAuthRouter as m, type OAuthProvider as n, type AuthContext as o, type AuthProfileOutcome as p, type AuthProfileVerifier as q, type ChangePasswordParams as r, type CompleteSignupParams as s, type ConfirmSignupLinkParams as t, KEY_FINGERPRINT_PREFIX_LENGTH as u, type LoginParams as v, type LogoutParams as w, type NormalizedIdentity as x, type OAuthCallbackParams as y, type OAuthCallbackResult as z };
|
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, b as RequestSignupLinkResult, C as ConfirmSignupLinkResult, L as LoginResult, c as RotateKeyResult, K as KeySummary, d as RevokeAllKeysResult, I as IssueOneTimeTokenResult, O as OAuthStartResult, e as OAuthNativeResult, U as UserProfile, f as ProfileInfo } from './authenticate-
|
|
3
|
-
export { A as AuthInitOptions, g as AuthSession, h as PERMISSION_CATEGORIES, i as PermissionCategory, V as VERIFICATION_PURPOSES, j as VERIFICATION_TARGET_TYPES, k as VerificationPurpose, l as VerificationTargetType } from './authenticate-
|
|
2
|
+
import { P as PermissionConfig, R as RoleConfig, m as mainAuthRouter, S as SendVerificationCodeResult, a as RegisterResult, b as RequestSignupLinkResult, C as ConfirmSignupLinkResult, L as LoginResult, c as RotateKeyResult, K as KeySummary, d as RevokeAllKeysResult, I as IssueOneTimeTokenResult, O as OAuthStartResult, e as OAuthNativeResult, U as UserProfile, f as ProfileInfo } from './authenticate-Mg9D7Nys.js';
|
|
3
|
+
export { A as AuthInitOptions, g as AuthSession, h as PERMISSION_CATEGORIES, i as PermissionCategory, V as VERIFICATION_PURPOSES, j as VERIFICATION_TARGET_TYPES, k as VerificationPurpose, l as VerificationTargetType } from './authenticate-Mg9D7Nys.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';
|
|
@@ -217,7 +217,7 @@ declare const authApi: _spfn_core_nextjs.Client<_spfn_core_route.Router<{
|
|
|
217
217
|
id: number;
|
|
218
218
|
name: string;
|
|
219
219
|
displayName: string;
|
|
220
|
-
category: "
|
|
220
|
+
category: "custom" | "user" | "auth" | "rbac" | "system" | undefined;
|
|
221
221
|
}[];
|
|
222
222
|
userId: number;
|
|
223
223
|
publicId: string;
|
|
@@ -517,8 +517,8 @@ declare const authApi: _spfn_core_nextjs.Client<_spfn_core_route.Router<{
|
|
|
517
517
|
}, {}, {
|
|
518
518
|
roles: {
|
|
519
519
|
description: string | null;
|
|
520
|
-
name: string;
|
|
521
520
|
id: number;
|
|
521
|
+
name: string;
|
|
522
522
|
displayName: string;
|
|
523
523
|
isBuiltin: boolean;
|
|
524
524
|
isSystem: boolean;
|
|
@@ -539,8 +539,8 @@ declare const authApi: _spfn_core_nextjs.Client<_spfn_core_route.Router<{
|
|
|
539
539
|
}, {}, {
|
|
540
540
|
role: {
|
|
541
541
|
description: string | null;
|
|
542
|
-
name: string;
|
|
543
542
|
id: number;
|
|
543
|
+
name: string;
|
|
544
544
|
displayName: string;
|
|
545
545
|
isBuiltin: boolean;
|
|
546
546
|
isSystem: boolean;
|
|
@@ -563,8 +563,8 @@ declare const authApi: _spfn_core_nextjs.Client<_spfn_core_route.Router<{
|
|
|
563
563
|
}, {}, {
|
|
564
564
|
role: {
|
|
565
565
|
description: string | null;
|
|
566
|
-
name: string;
|
|
567
566
|
id: number;
|
|
567
|
+
name: string;
|
|
568
568
|
displayName: string;
|
|
569
569
|
isBuiltin: boolean;
|
|
570
570
|
isSystem: boolean;
|