@plitzi/sdk-server 0.33.2 → 0.34.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/CHANGELOG.md +44 -0
- package/README.md +283 -8
- package/dist/actions.d.ts +62 -0
- package/dist/actions.js +12 -0
- package/dist/adapters/cloudAdapters.d.ts +72 -0
- package/dist/adapters/cloudAdapters.js +303 -0
- package/dist/adapters/space/index.d.ts +93 -0
- package/dist/adapters/space/index.js +122 -0
- package/dist/adapters/space/resolvers.d.ts +69 -0
- package/dist/adapters/space/resolvers.js +79 -0
- package/dist/adapters/space/space.test.d.ts +1 -0
- package/dist/adapters/space/types.d.ts +64 -0
- package/dist/adapters/space/types.js +41 -0
- package/dist/auth.d.ts +50 -0
- package/dist/auth.js +19 -0
- package/dist/core/auth/api.d.ts +400 -11
- package/dist/core/auth/api.js +1044 -50
- package/dist/core/auth/createAuth.d.ts +135 -30
- package/dist/core/auth/createAuth.js +96 -24
- package/dist/core/auth/credentials.d.ts +9 -3
- package/dist/core/auth/credentials.js +10 -4
- package/dist/core/auth/csrf.d.ts +89 -0
- package/dist/core/auth/csrf.js +234 -0
- package/dist/core/auth/csrf.test.d.ts +1 -0
- package/dist/core/auth/identity.d.ts +6 -0
- package/dist/core/auth/identity.js +37 -8
- package/dist/core/auth/lifecycle.test.d.ts +1 -0
- package/dist/core/auth/oauth/index.d.ts +8 -0
- package/dist/core/auth/oauth/index.js +13 -15
- package/dist/core/auth/passwords.d.ts +15 -0
- package/dist/core/auth/passwords.js +32 -0
- package/dist/core/auth/redirects.d.ts +18 -0
- package/dist/core/auth/redirects.js +31 -0
- package/dist/core/auth/redirects.test.d.ts +1 -0
- package/dist/core/auth/routes.d.ts +4 -2
- package/dist/core/auth/routes.js +234 -9
- package/dist/core/auth/session.d.ts +11 -0
- package/dist/core/auth/session.js +1 -1
- package/dist/core/auth/spaceTokens.d.ts +38 -2
- package/dist/core/auth/spaceTokens.js +54 -2
- package/dist/core/auth/throttle.d.ts +5 -0
- package/dist/core/auth/throttle.js +81 -0
- package/dist/core/auth/throttle.test.d.ts +1 -0
- package/dist/core/auth/tokens.d.ts +83 -12
- package/dist/core/auth/tokens.js +51 -9
- package/dist/core/auth/totp.d.ts +48 -0
- package/dist/core/auth/totp.js +118 -0
- package/dist/core/auth/totp.test.d.ts +1 -0
- package/dist/core/createServer.d.ts +0 -6
- package/dist/core/createServer.js +35 -3
- package/dist/core/fontStore.d.ts +33 -0
- package/dist/core/fontStore.js +53 -0
- package/dist/core/fontStore.test.d.ts +1 -0
- package/dist/core/handlers/authMiddleware.js +1 -1
- package/dist/core/handlers/authRouteHandlers.d.ts +9 -1
- package/dist/core/handlers/authRouteHandlers.js +4 -3
- package/dist/core/handlers/csrfMiddleware.d.ts +20 -0
- package/dist/core/handlers/csrfMiddleware.js +32 -0
- package/dist/core/handlers/oauthRouteHandlers.d.ts +46 -0
- package/dist/core/handlers/oauthRouteHandlers.js +159 -0
- package/dist/core/handlers/socialRouteHandlers.d.ts +40 -0
- package/dist/core/handlers/socialRouteHandlers.js +129 -0
- package/dist/core/handlers/socialRouteHandlers.test.d.ts +1 -0
- package/dist/core/handlers/types.d.ts +22 -5
- package/dist/core/health.d.ts +11 -1
- package/dist/core/health.js +27 -3
- package/dist/core/http/dispatcher.d.ts +1 -1
- package/dist/core/http/dispatcher.js +20 -1
- package/dist/core/http/stages/authApi.js +1 -1
- package/dist/core/http/stages/fontAssets.d.ts +9 -0
- package/dist/core/http/stages/fontAssets.js +27 -0
- package/dist/core/http/stages/oauth.d.ts +19 -0
- package/dist/core/http/stages/oauth.js +118 -0
- package/dist/core/http/types.d.ts +12 -0
- package/dist/core/oauth/authorize.d.ts +22 -0
- package/dist/core/oauth/authorize.js +254 -0
- package/dist/core/oauth/authorize.test.d.ts +1 -0
- package/dist/core/oauth/challenge.d.ts +11 -0
- package/dist/core/oauth/challenge.js +33 -0
- package/dist/core/oauth/consentPage.d.ts +12 -0
- package/dist/core/oauth/consentPage.js +131 -0
- package/dist/core/oauth/metadata.d.ts +41 -0
- package/dist/core/oauth/metadata.js +71 -0
- package/dist/core/oauth/params.d.ts +5 -0
- package/dist/core/oauth/params.js +5 -0
- package/dist/core/oauth/pkce.d.ts +6 -0
- package/dist/core/oauth/pkce.js +15 -0
- package/dist/core/oauth/records.d.ts +72 -0
- package/dist/core/oauth/records.js +49 -0
- package/dist/core/oauth/register.d.ts +5 -0
- package/dist/core/oauth/register.js +55 -0
- package/dist/core/oauth/respond.d.ts +21 -0
- package/dist/core/oauth/respond.js +59 -0
- package/dist/core/oauth/token.d.ts +14 -0
- package/dist/core/oauth/token.js +130 -0
- package/dist/core/previewToken.d.ts +20 -0
- package/dist/core/previewToken.js +29 -1
- package/dist/core/server/pageServer.js +4 -1
- package/dist/core/services/action.d.ts +6 -4
- package/dist/core/services/action.js +103 -10
- package/dist/core/services/registry.js +2 -0
- package/dist/core/services/resolve.d.ts +8 -0
- package/dist/core/services/resolve.js +12 -1
- package/dist/core/staticFiles.js +1 -2
- package/dist/handlers.d.ts +7 -1
- package/dist/handlers.js +4 -1
- package/dist/helpers/buildResponseHelpers.d.ts +8 -0
- package/dist/helpers/buildResponseHelpers.js +9 -1
- package/dist/helpers/buildServerInfo.d.ts +8 -3
- package/dist/helpers/buildServerInfo.js +18 -14
- package/dist/helpers/cache/keys.d.ts +13 -0
- package/dist/helpers/cache/keys.js +12 -1
- package/dist/helpers/onAbort.d.ts +12 -0
- package/dist/helpers/onAbort.js +22 -0
- package/dist/helpers/onAbort.test.d.ts +1 -0
- package/dist/helpers/outboundGuard.d.ts +27 -0
- package/dist/helpers/outboundGuard.js +49 -0
- package/dist/helpers/outboundGuard.test.d.ts +1 -0
- package/dist/helpers/serverLog.d.ts +6 -3
- package/dist/helpers/serverLog.js +20 -3
- package/dist/index.d.ts +6 -0
- package/dist/index.js +9 -4
- package/dist/kernel.d.ts +5 -22
- package/dist/kernel.js +1 -14
- package/dist/modules/actions/{handler.js → connectorWrite.js} +5 -2
- package/dist/modules/actions/connectorWrite.test.d.ts +1 -0
- package/dist/modules/actions/index.d.ts +40 -0
- package/dist/modules/actions/index.js +46 -0
- package/dist/modules/actions/moduleFor.d.ts +3 -0
- package/dist/modules/actions/moduleFor.js +35 -0
- package/dist/modules/actions/moduleFor.test.d.ts +1 -0
- package/dist/modules/actions/runtime/check.d.ts +24 -0
- package/dist/modules/actions/runtime/check.js +111 -0
- package/dist/modules/actions/runtime/check.test.d.ts +1 -0
- package/dist/modules/actions/runtime/errors.d.ts +11 -0
- package/dist/modules/actions/runtime/errors.js +17 -0
- package/dist/modules/actions/runtime/guards.d.ts +126 -0
- package/dist/modules/actions/runtime/guards.js +246 -0
- package/dist/modules/actions/runtime/guards.test.d.ts +1 -0
- package/dist/modules/actions/runtime/kvStore.d.ts +27 -0
- package/dist/modules/actions/runtime/kvStore.js +41 -0
- package/dist/modules/actions/runtime/kvStore.test.d.ts +1 -0
- package/dist/modules/actions/runtime/limits.d.ts +11 -0
- package/dist/modules/actions/runtime/limits.js +35 -0
- package/dist/modules/actions/runtime/memoryKv.d.ts +2 -0
- package/dist/modules/actions/runtime/memoryKv.js +77 -0
- package/dist/modules/actions/runtime/namespaceKv.d.ts +9 -0
- package/dist/modules/actions/runtime/namespaceKv.js +19 -0
- package/dist/modules/actions/runtime/precheck.d.ts +27 -0
- package/dist/modules/actions/runtime/precheck.js +45 -0
- package/dist/modules/actions/runtime/renderResolver.d.ts +15 -0
- package/dist/modules/actions/runtime/renderResolver.js +130 -0
- package/dist/modules/actions/runtime/renderResolver.test.d.ts +1 -0
- package/dist/modules/actions/runtime/renderShare.d.ts +29 -0
- package/dist/modules/actions/runtime/renderShare.js +36 -0
- package/dist/modules/actions/runtime/renderShare.test.d.ts +1 -0
- package/dist/modules/actions/runtime/report.d.ts +14 -0
- package/dist/modules/actions/runtime/report.js +22 -0
- package/dist/modules/actions/runtime/runAction.d.ts +12 -0
- package/dist/modules/actions/runtime/runAction.js +405 -0
- package/dist/modules/actions/runtime/runAction.test.d.ts +1 -0
- package/dist/modules/actions/runtime/runLogger.d.ts +31 -0
- package/dist/modules/actions/runtime/runLogger.js +66 -0
- package/dist/modules/actions/runtime/runLogger.test.d.ts +1 -0
- package/dist/modules/actions/runtime/schedule.d.ts +35 -0
- package/dist/modules/actions/runtime/schedule.js +76 -0
- package/dist/modules/actions/runtime/schedule.test.d.ts +1 -0
- package/dist/modules/actions/runtime/scope.d.ts +40 -0
- package/dist/modules/actions/runtime/scope.js +103 -0
- package/dist/modules/actions/runtime/triggers.d.ts +12 -0
- package/dist/modules/actions/runtime/triggers.js +14 -0
- package/dist/modules/actions/taskCatalog.d.ts +26 -0
- package/dist/modules/actions/taskCatalog.js +24 -0
- package/dist/modules/actions/taskCatalog.test.d.ts +1 -0
- package/dist/modules/actions/tasks/auth.d.ts +2 -0
- package/dist/modules/actions/tasks/auth.js +40 -0
- package/dist/modules/actions/tasks/builtins.d.ts +8 -0
- package/dist/modules/actions/tasks/builtins.js +24 -0
- package/dist/modules/actions/tasks/connector.d.ts +2 -0
- package/dist/modules/actions/tasks/connector.js +141 -0
- package/dist/modules/actions/tasks/db.d.ts +2 -0
- package/dist/modules/actions/tasks/db.js +59 -0
- package/dist/modules/actions/tasks/db.test.d.ts +1 -0
- package/dist/modules/actions/tasks/flow.d.ts +3 -0
- package/dist/modules/actions/tasks/flow.js +109 -0
- package/dist/modules/actions/tasks/helpers.d.ts +16 -0
- package/dist/modules/actions/tasks/helpers.js +30 -0
- package/dist/modules/actions/tasks/http.d.ts +2 -0
- package/dist/modules/actions/tasks/http.js +111 -0
- package/dist/modules/actions/tasks/http.test.d.ts +1 -0
- package/dist/modules/actions/tasks/kv.d.ts +2 -0
- package/dist/modules/actions/tasks/kv.js +102 -0
- package/dist/modules/actions/tasks/kv.test.d.ts +1 -0
- package/dist/modules/actions/tasks/registry.d.ts +10 -0
- package/dist/modules/actions/tasks/registry.js +36 -0
- package/dist/modules/actions/tasks/transform.d.ts +2 -0
- package/dist/modules/actions/tasks/transform.js +34 -0
- package/dist/modules/actions/transport/callHandler.d.ts +24 -0
- package/dist/modules/actions/transport/callHandler.js +233 -0
- package/dist/modules/actions/transport/callHandler.test.d.ts +1 -0
- package/dist/modules/actions/transport/cancelHandler.d.ts +23 -0
- package/dist/modules/actions/transport/cancelHandler.js +23 -0
- package/dist/modules/actions/transport/catalogHandler.d.ts +18 -0
- package/dist/modules/actions/transport/catalogHandler.js +21 -0
- package/dist/modules/actions/transport/stream.d.ts +23 -0
- package/dist/modules/actions/transport/stream.js +100 -0
- package/dist/modules/actions/transport/verifySignature.d.ts +16 -0
- package/dist/modules/actions/transport/verifySignature.js +72 -0
- package/dist/modules/actions/transport/webhookHandler.d.ts +25 -0
- package/dist/modules/actions/transport/webhookHandler.js +206 -0
- package/dist/modules/actions/transport/webhookHandler.test.d.ts +1 -0
- package/dist/modules/actions/types.d.ts +234 -0
- package/dist/modules/connectors/engine.js +4 -1
- package/dist/modules/connectors/resolver.d.ts +10 -2
- package/dist/modules/connectors/resolver.js +8 -3
- package/dist/modules/mysql/accounts.d.ts +5 -0
- package/dist/modules/mysql/accounts.js +365 -0
- package/dist/modules/mysql/accounts.test.d.ts +1 -0
- package/dist/modules/mysql/admin.d.ts +58 -0
- package/dist/modules/mysql/admin.js +133 -0
- package/dist/modules/mysql/config.d.ts +82 -0
- package/dist/modules/mysql/config.js +30 -0
- package/dist/modules/mysql/identities.d.ts +35 -0
- package/dist/modules/mysql/identities.js +43 -0
- package/dist/modules/mysql/index.d.ts +67 -0
- package/dist/modules/mysql/index.js +62 -0
- package/dist/modules/mysql/pool.d.ts +3 -0
- package/dist/modules/mysql/pool.js +88 -0
- package/dist/modules/mysql/query.d.ts +12 -0
- package/dist/modules/mysql/query.js +21 -0
- package/dist/modules/mysql/schema.d.ts +43 -0
- package/dist/modules/mysql/schema.js +340 -0
- package/dist/modules/mysql/schema.test.d.ts +1 -0
- package/dist/modules/mysql/sessions.d.ts +33 -0
- package/dist/modules/mysql/sessions.js +46 -0
- package/dist/modules/mysql/spaceTokens.d.ts +11 -0
- package/dist/modules/mysql/spaceTokens.js +59 -0
- package/dist/modules/mysql/store.integration.test.d.ts +1 -0
- package/dist/modules/rsc/connectorRscData.d.ts +25 -0
- package/dist/modules/rsc/connectorRscData.js +51 -0
- package/dist/modules/rsc/connectorRscData.test.d.ts +1 -0
- package/dist/modules/rsc/handler.js +16 -3
- package/dist/modules/rsc/matchRscPage.d.ts +14 -0
- package/dist/modules/rsc/matchRscPage.js +26 -0
- package/dist/modules/rsc/resolveRscData.d.ts +14 -1
- package/dist/modules/rsc/resolveRscData.js +23 -34
- package/dist/modules/ssr/Component.d.ts +6 -2
- package/dist/modules/ssr/Component.js +3 -1
- package/dist/modules/ssr/draftSession.test.d.ts +1 -0
- package/dist/modules/ssr/handler.js +15 -2
- package/dist/modules/ssr/loadPluginComponents.js +22 -5
- package/dist/modules/ssr/prepareRender.js +87 -8
- package/dist/modules/ssr/prepareRender.test.d.ts +1 -0
- package/dist/modules/ssr/preview.d.ts +21 -8
- package/dist/modules/ssr/preview.js +38 -14
- package/dist/modules/ssr/registerExternalPlugins.js +2 -2
- package/dist/modules/ssr/resolvePageSeo.d.ts +18 -0
- package/dist/modules/ssr/resolvePageSeo.js +26 -0
- package/dist/modules/ssr/resolvePageSeo.test.d.ts +1 -0
- package/dist/modules/ssr/template.test.d.ts +1 -0
- package/dist/modules/ssr/views/template.ejs +71 -5
- package/dist/mysql.d.ts +26 -0
- package/dist/mysql.js +4 -0
- package/dist/oauth.d.ts +24 -0
- package/dist/oauth.js +10 -0
- package/dist/plugins/compile.d.ts +1 -0
- package/dist/plugins/compile.js +37 -3
- package/dist/plugins/manager.d.ts +40 -0
- package/dist/plugins/manager.js +113 -13
- package/dist/plugins/manager.test.d.ts +1 -0
- package/package.json +43 -14
- package/skills/plitzi-authoring/SKILL.md +252 -0
- /package/dist/{modules/actions/handler.test.d.ts → adapters/cloudAdapters.test.d.ts} +0 -0
- /package/dist/modules/actions/{handler.d.ts → connectorWrite.d.ts} +0 -0
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
import { AccountAdapters, AuthApiConfig } from './api';
|
|
1
|
+
import { AccountAdapters, AuthApiConfig, AuthOutcome } from './api';
|
|
2
2
|
import { AuthPolicy, Requirement } from './authorize';
|
|
3
3
|
import { CredentialCarrier } from './credentials';
|
|
4
4
|
import { IdentityAdapters, IdentityConfig } from './identity';
|
|
5
|
+
import { CookieSink } from './session';
|
|
5
6
|
import { TokenConfig } from './tokens';
|
|
6
7
|
import { SSRAuthCookie, SSRSession, SSRUser } from '@plitzi/sdk-shared';
|
|
7
8
|
/**
|
|
@@ -40,6 +41,15 @@ export interface AuthConfig {
|
|
|
40
41
|
fallback?: Requirement;
|
|
41
42
|
/** Extra rules, applied BEFORE the derived `/auth` ones so a deployment can always widen its own surface. */
|
|
42
43
|
rules?: AuthPolicy['rules'];
|
|
44
|
+
/**
|
|
45
|
+
* Cross-site request forgery. **On, and it configures itself** from what is already here: the token secret signs
|
|
46
|
+
* it, the cookie naming above scopes it, and `identity.platformOrigins` says which sites are this deployment's.
|
|
47
|
+
*
|
|
48
|
+
* `false` turns it off, for a deployment that has its own. There is nothing else to say about it, deliberately —
|
|
49
|
+
* every dial this used to have was one more thing to understand before you could start, and none of them had a
|
|
50
|
+
* second answer worth having.
|
|
51
|
+
*/
|
|
52
|
+
csrf?: false;
|
|
43
53
|
}
|
|
44
54
|
/**
|
|
45
55
|
* The whole auth cycle, wired.
|
|
@@ -59,18 +69,16 @@ export interface AuthConfig {
|
|
|
59
69
|
*/
|
|
60
70
|
export declare const createAuth: (config: AuthConfig) => {
|
|
61
71
|
tokens: {
|
|
62
|
-
lifetimes:
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
agent: number;
|
|
66
|
-
widget: number;
|
|
67
|
-
};
|
|
68
|
-
generateUserToken: (userId: number | string) => string;
|
|
72
|
+
lifetimes: Record<"session" | "refresh" | "widget" | "agent" | "access" | "mfaChallenge", number>;
|
|
73
|
+
issuer: string;
|
|
74
|
+
generateUserToken: (userId: number | string, { actingAs, ttlSeconds }?: import('./tokens').UserTokenOptions) => string;
|
|
69
75
|
generateRefreshToken: (userId: number | string) => string;
|
|
70
76
|
generateWidgetToken: () => string;
|
|
77
|
+
generateMfaChallenge: (userId: number | string) => string;
|
|
71
78
|
generateSpaceToken: (spaceId: number | string, origins: string[], scope?: import('./tokens').SpaceScope, { expiresAt }?: import('./tokens').SpaceTokenOptions) => string;
|
|
72
79
|
verifyUserToken: (token: string) => import('./tokens').VerifyResult<import('./tokens').UserTokenPayload>;
|
|
73
80
|
verifyRefreshToken: (token: string) => import('./tokens').VerifyResult<import('./tokens').RefreshTokenPayload>;
|
|
81
|
+
verifyMfaChallenge: (token: string) => import('./tokens').VerifyResult<import('./tokens').MfaChallengePayload>;
|
|
74
82
|
verifyWidgetToken: (token: string) => import('./tokens').VerifyResult<import('./tokens').WidgetTokenPayload>;
|
|
75
83
|
verifySpaceToken: (token: string) => import('./tokens').VerifyResult<import('./tokens').SpaceTokenPayload & {
|
|
76
84
|
spaceId: number;
|
|
@@ -95,38 +103,79 @@ export declare const createAuth: (config: AuthConfig) => {
|
|
|
95
103
|
passwordReset: boolean;
|
|
96
104
|
emailVerification: boolean;
|
|
97
105
|
exchange: boolean;
|
|
106
|
+
mfa: boolean;
|
|
107
|
+
passwordless: boolean;
|
|
108
|
+
profile: boolean;
|
|
109
|
+
passwordChange: boolean;
|
|
110
|
+
accountDeletion: boolean;
|
|
111
|
+
sessionList: boolean;
|
|
112
|
+
administration: boolean;
|
|
113
|
+
emailChange: boolean;
|
|
114
|
+
impersonation: boolean;
|
|
115
|
+
};
|
|
116
|
+
describe: () => AuthOutcome;
|
|
117
|
+
session: (actor?: import('./identity').Actor) => AuthOutcome;
|
|
118
|
+
login: (credentials: Record<string, unknown>, carrier?: CredentialCarrier) => Promise<AuthOutcome>;
|
|
119
|
+
completeMfa: (mfaToken: string, code: string, carrier?: CredentialCarrier) => Promise<AuthOutcome>;
|
|
120
|
+
passwordless: {
|
|
121
|
+
request: (email: string, carrier?: CredentialCarrier) => Promise<AuthOutcome>;
|
|
122
|
+
complete: (email: string, code: string, carrier?: CredentialCarrier) => Promise<AuthOutcome>;
|
|
123
|
+
};
|
|
124
|
+
mfa: {
|
|
125
|
+
status: (actor: import('./identity').Actor | undefined) => Promise<AuthOutcome>;
|
|
126
|
+
begin: (actor: import('./identity').Actor | undefined) => Promise<AuthOutcome>;
|
|
127
|
+
confirm: (actor: import('./identity').Actor | undefined, code: string) => Promise<AuthOutcome>;
|
|
128
|
+
disable: (actor: import('./identity').Actor | undefined, password?: string) => Promise<AuthOutcome>;
|
|
98
129
|
};
|
|
99
|
-
|
|
100
|
-
session: (actor?: import('./identity').Actor) => import('./api').AuthOutcome;
|
|
101
|
-
login: (credentials: Record<string, unknown>) => Promise<import('./api').AuthOutcome>;
|
|
102
|
-
refresh: (refreshToken?: string) => Promise<import('./api').AuthOutcome>;
|
|
130
|
+
refresh: (refreshToken?: string, carrier?: CredentialCarrier) => Promise<AuthOutcome>;
|
|
103
131
|
logout: (credentials: {
|
|
104
132
|
accessToken?: string;
|
|
105
133
|
refreshToken?: string;
|
|
106
|
-
}) => Promise<
|
|
107
|
-
revokeSessions: (userId?: number) => Promise<
|
|
108
|
-
exchange: (provider: string, token: string, carrier: CredentialCarrier) => Promise<
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
134
|
+
}) => Promise<AuthOutcome>;
|
|
135
|
+
revokeSessions: (userId?: number) => Promise<AuthOutcome>;
|
|
136
|
+
exchange: (provider: string, token: string, carrier: CredentialCarrier) => Promise<AuthOutcome>;
|
|
137
|
+
exchangeAccount: (provider: string, token: string, carrier: CredentialCarrier) => Promise<import('./api').ExchangeResult>;
|
|
138
|
+
signup: (fields: Record<string, unknown>) => Promise<AuthOutcome>;
|
|
139
|
+
forgotPassword: (email: string) => Promise<AuthOutcome>;
|
|
140
|
+
resetPassword: (token: string, password: string) => Promise<AuthOutcome>;
|
|
141
|
+
validateAccount: (token: string) => Promise<AuthOutcome>;
|
|
142
|
+
resendVerification: (email: string, redirect?: string) => Promise<AuthOutcome>;
|
|
143
|
+
updateProfile: (actor: import('./identity').Actor | undefined, fields: Record<string, unknown>) => Promise<AuthOutcome>;
|
|
144
|
+
confirmEmailChange: (token: string) => Promise<AuthOutcome>;
|
|
145
|
+
changePassword: (actor: import('./identity').Actor | undefined, currentPassword: string, newPassword: string) => Promise<AuthOutcome>;
|
|
146
|
+
deleteSelf: (actor: import('./identity').Actor | undefined, password?: string) => Promise<AuthOutcome>;
|
|
147
|
+
listSessions: (actor: import('./identity').Actor | undefined) => Promise<AuthOutcome>;
|
|
148
|
+
revokeSession: (actor: import('./identity').Actor | undefined, sessionId: number) => Promise<AuthOutcome>;
|
|
149
|
+
revokeOtherSessions: (actor: import('./identity').Actor | undefined) => Promise<AuthOutcome>;
|
|
150
|
+
admin: {
|
|
151
|
+
list: (actor: import('./identity').Actor | undefined, query: import('./api').AccountQuery) => Promise<AuthOutcome>;
|
|
152
|
+
get: (actor: import('./identity').Actor | undefined, userId: number) => Promise<AuthOutcome>;
|
|
153
|
+
setStatus: (actor: import('./identity').Actor | undefined, userId: number, status: import('./api').AccountStatus) => Promise<AuthOutcome>;
|
|
154
|
+
setRoles: (actor: import('./identity').Actor | undefined, userId: number, roles: unknown) => Promise<AuthOutcome>;
|
|
155
|
+
remove: (actor: import('./identity').Actor | undefined, userId: number) => Promise<AuthOutcome>;
|
|
156
|
+
impersonate: (actor: import('./identity').Actor | undefined, userId: number, carrier?: CredentialCarrier) => Promise<AuthOutcome>;
|
|
157
|
+
};
|
|
158
|
+
issueCsrf: (sessionToken?: string) => AuthOutcome;
|
|
159
|
+
issueSession: (userId: number, context?: import('./api').SessionContext, options?: {
|
|
160
|
+
actingAs?: number;
|
|
161
|
+
ttlSeconds?: number;
|
|
162
|
+
renewable?: boolean;
|
|
163
|
+
}) => Promise<SSRSession>;
|
|
115
164
|
resolveActor: (carrier: CredentialCarrier) => Promise<import('./identity').ActorResult>;
|
|
116
165
|
};
|
|
117
166
|
cookies: {
|
|
118
167
|
write: (req: {
|
|
119
168
|
hostname: string;
|
|
120
|
-
}, res:
|
|
169
|
+
}, res: CookieSink, session: SSRSession) => void;
|
|
121
170
|
clear: (req: {
|
|
122
171
|
hostname: string;
|
|
123
|
-
}, res:
|
|
172
|
+
}, res: CookieSink) => void;
|
|
124
173
|
writeFlow: (req: {
|
|
125
174
|
hostname: string;
|
|
126
|
-
}, res:
|
|
175
|
+
}, res: CookieSink, value: string, ttlSeconds: number) => void;
|
|
127
176
|
clearFlow: (req: {
|
|
128
177
|
hostname: string;
|
|
129
|
-
}, res:
|
|
178
|
+
}, res: CookieSink) => void;
|
|
130
179
|
readFlow: (req: import('./session').CookieCarrier) => string | undefined;
|
|
131
180
|
resolveSessionToken: (req: import('./session').CookieCarrier) => string | undefined;
|
|
132
181
|
resolveRefreshToken: (req: import('./session').CookieCarrier, body?: unknown) => string | undefined;
|
|
@@ -137,28 +186,84 @@ export declare const createAuth: (config: AuthConfig) => {
|
|
|
137
186
|
basePath: string;
|
|
138
187
|
/** The naming this was built with, so a page server takes it from here instead of being told a second time. */
|
|
139
188
|
cookieConfig: SSRAuthCookie | undefined;
|
|
189
|
+
/**
|
|
190
|
+
* Cross-site request forgery: issuing tokens, writing the readable cookie, and the check. `undefined` when the
|
|
191
|
+
* deployment turned it off. The `/auth` routes below already enforce it.
|
|
192
|
+
*/
|
|
193
|
+
csrf: {
|
|
194
|
+
issue: (sessionToken?: string) => string;
|
|
195
|
+
cookieName: (hostname: string) => string;
|
|
196
|
+
headerName: string;
|
|
197
|
+
write: (req: {
|
|
198
|
+
hostname: string;
|
|
199
|
+
}, res: CookieSink, token: string) => void;
|
|
200
|
+
clear: (req: {
|
|
201
|
+
hostname: string;
|
|
202
|
+
}, res: CookieSink) => void;
|
|
203
|
+
read: (carrier: import('./csrf').CsrfCarrier) => string | undefined;
|
|
204
|
+
required: (carrier: import('./csrf').CsrfCarrier, subject?: import('./csrf').CsrfSubject) => boolean;
|
|
205
|
+
verify: (carrier: import('./csrf').CsrfCarrier, sessionToken?: string) => import('./csrf').CsrfResult;
|
|
206
|
+
} | undefined;
|
|
140
207
|
/** The `/auth` flows as descriptors, ready for whatever serves HTTP here. */
|
|
141
208
|
routes: import('./routes').AuthRoute[];
|
|
142
209
|
/** May this request proceed, and as whom. */
|
|
143
210
|
authorize: (carrier: CredentialCarrier, path: string) => Promise<import('./authorize').AuthorizeResult>;
|
|
144
|
-
/**
|
|
211
|
+
/**
|
|
212
|
+
* Does what a flow's answer says about the cookies — the session, and the CSRF token that has to be re-issued
|
|
213
|
+
* alongside it. Bound to this deployment's `csrf`, so a caller cannot forget to pass it.
|
|
214
|
+
*/
|
|
145
215
|
applySession: (req: {
|
|
146
216
|
hostname: string;
|
|
147
|
-
}, res:
|
|
217
|
+
}, res: CookieSink, outcome: AuthOutcome) => void;
|
|
148
218
|
/** Both halves of a space permission: a global capability, and membership of that space. */
|
|
149
219
|
can: (actor: import('./identity').Actor, spaceId: number, permission: string) => Promise<boolean>;
|
|
150
220
|
/**
|
|
151
|
-
*
|
|
221
|
+
* WHO a page request carries, and nothing about signing anyone in or out.
|
|
222
|
+
*
|
|
223
|
+
* This is the set a deployment wants when its renderer and its sign-in live in different processes — a page
|
|
224
|
+
* server that must know who is looking at it, while the `/auth` flows are served by an API tier next to it.
|
|
225
|
+
* That shape had no name, so a deployment expressed it by reaching into `ssrAdapters` and lifting two keys out
|
|
226
|
+
* by hand: correct until a third identity adapter appears, which it then silently does not get.
|
|
227
|
+
*
|
|
228
|
+
* `exchangeCredential` belongs here rather than with the flows: turning a credential a browser already holds
|
|
229
|
+
* into a session is a question about identity, and it is the one the page server itself answers.
|
|
230
|
+
*/
|
|
231
|
+
ssrIdentityAdapters: {
|
|
232
|
+
exchangeCredential?: ((provider: string, token: string, req: CredentialCarrier) => Promise<{
|
|
233
|
+
ok: true;
|
|
234
|
+
session: SSRSession;
|
|
235
|
+
user?: SSRUser;
|
|
236
|
+
} | {
|
|
237
|
+
ok: false;
|
|
238
|
+
error: string;
|
|
239
|
+
status?: number;
|
|
240
|
+
reason?: string;
|
|
241
|
+
}>) | undefined;
|
|
242
|
+
getUser: (req: CredentialCarrier) => Promise<SSRUser | undefined>;
|
|
243
|
+
};
|
|
244
|
+
/**
|
|
245
|
+
* Every adapter a page server asks for, already answered. Spread these into `createServer`'s `adapters`
|
|
152
246
|
* and a rendered page knows who is looking at it, `POST /auth/login` signs them in, `POST /auth/logout` signs
|
|
153
|
-
* them out, and the cookies are written and cleared by the same code that reads them back.
|
|
247
|
+
* them out, and the cookies are written and cleared by the same code that reads them back. A renderer that
|
|
248
|
+
* serves no login of its own wants {@link ssrIdentityAdapters} instead.
|
|
154
249
|
*
|
|
155
250
|
* They exist because the page server speaks in sessions and users while the flows above answer in outcomes,
|
|
156
251
|
* and translating between the two is a step with exactly one right answer — which makes it the server's, not
|
|
157
252
|
* something for every deployment to rediscover.
|
|
158
253
|
*/
|
|
159
254
|
ssrAdapters: {
|
|
160
|
-
|
|
255
|
+
exchangeCredential?: ((provider: string, token: string, req: CredentialCarrier) => Promise<{
|
|
256
|
+
ok: true;
|
|
257
|
+
session: SSRSession;
|
|
258
|
+
user?: SSRUser;
|
|
259
|
+
} | {
|
|
260
|
+
ok: false;
|
|
261
|
+
error: string;
|
|
262
|
+
status?: number;
|
|
263
|
+
reason?: string;
|
|
264
|
+
}>) | undefined;
|
|
161
265
|
getUser: (req: CredentialCarrier) => Promise<SSRUser | undefined>;
|
|
266
|
+
authenticate: (credentials: Record<string, string>) => Promise<SSRSession | undefined>;
|
|
162
267
|
endSession: (req: CredentialCarrier) => Promise<void>;
|
|
163
268
|
};
|
|
164
269
|
};
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { createSessionCookies, sessionCookieParams } from "./session.js";
|
|
2
|
+
import { createAuthorizer } from "./authorize.js";
|
|
2
3
|
import { createTokens } from "./tokens.js";
|
|
4
|
+
import { createAuthApi } from "./api.js";
|
|
3
5
|
import { createCarriers, presentedOrigin } from "./credentials.js";
|
|
6
|
+
import { createCsrf } from "./csrf.js";
|
|
4
7
|
import { createIdentity } from "./identity.js";
|
|
5
|
-
import { createAuthorizer } from "./authorize.js";
|
|
6
|
-
import { createAuthApi } from "./api.js";
|
|
7
8
|
import { applySessionOutcome, authPolicyRules, authRoutes } from "./routes.js";
|
|
8
9
|
//#region src/core/auth/createAuth.ts
|
|
9
10
|
/**
|
|
@@ -33,17 +34,80 @@ var createAuth = (config) => {
|
|
|
33
34
|
adapters: config.adapters,
|
|
34
35
|
config: config.identity
|
|
35
36
|
});
|
|
37
|
+
/**
|
|
38
|
+
* The hosts this deployment already declared are the ones its own pages are served from, which is exactly the
|
|
39
|
+
* question the sign-in check asks. Taken from there rather than asked for again: two lists that have to agree
|
|
40
|
+
* are two lists that will not.
|
|
41
|
+
*/
|
|
42
|
+
const csrf = config.csrf === false ? void 0 : createCsrf({
|
|
43
|
+
secret: config.tokens.secret,
|
|
44
|
+
cookie: config.cookie,
|
|
45
|
+
allowedOrigins: config.identity?.platformOrigins
|
|
46
|
+
});
|
|
36
47
|
const api = createAuthApi({
|
|
37
48
|
tokens,
|
|
38
49
|
identity,
|
|
39
50
|
adapters: config.adapters,
|
|
40
|
-
config:
|
|
51
|
+
config: {
|
|
52
|
+
...config.api,
|
|
53
|
+
...csrf ? { csrf } : {}
|
|
54
|
+
}
|
|
41
55
|
});
|
|
42
56
|
const basePath = config.basePath ?? "/auth";
|
|
43
57
|
const policy = {
|
|
44
58
|
rules: [...config.rules ?? [], ...authPolicyRules(basePath)],
|
|
45
59
|
fallback: config.fallback ?? "actor"
|
|
46
60
|
};
|
|
61
|
+
/**
|
|
62
|
+
* Identity as a page server sees it, kept apart from the sign-in flows so a deployment can take one without the
|
|
63
|
+
* other — see the `ssrIdentityAdapters` note below.
|
|
64
|
+
*
|
|
65
|
+
* `exchangeCredential` is omitted entirely when no exchange adapter was supplied. A key that is present but inert
|
|
66
|
+
* would make the page server advertise `sessionExchangeUrl` for a flow that answers 404 — and a browser-side
|
|
67
|
+
* identity provider handing its credential to an endpoint that is not there fails as a hydration mismatch, which
|
|
68
|
+
* is nowhere near where the mistake was made.
|
|
69
|
+
*/
|
|
70
|
+
const ssrIdentityAdapters = {
|
|
71
|
+
getUser: async (req) => {
|
|
72
|
+
const resolved = await identity.resolveActor(req);
|
|
73
|
+
if (!resolved.ok) return;
|
|
74
|
+
const { id, username, email, verified, permissions, roles, token, expiresAt } = resolved.actor;
|
|
75
|
+
return {
|
|
76
|
+
id,
|
|
77
|
+
username,
|
|
78
|
+
email,
|
|
79
|
+
verified,
|
|
80
|
+
permissions,
|
|
81
|
+
roles,
|
|
82
|
+
token,
|
|
83
|
+
expiresAt
|
|
84
|
+
};
|
|
85
|
+
},
|
|
86
|
+
...api.capabilities.exchange ? { exchangeCredential: async (provider, token, req) => {
|
|
87
|
+
const result = await api.exchangeAccount(provider, token, req);
|
|
88
|
+
if (!result.ok) return {
|
|
89
|
+
ok: false,
|
|
90
|
+
error: result.error,
|
|
91
|
+
status: result.status,
|
|
92
|
+
reason: result.reason
|
|
93
|
+
};
|
|
94
|
+
const { account, access, session } = result;
|
|
95
|
+
return {
|
|
96
|
+
ok: true,
|
|
97
|
+
session,
|
|
98
|
+
user: {
|
|
99
|
+
token: session.token,
|
|
100
|
+
expiresAt: session.expiresAt,
|
|
101
|
+
id: account.id,
|
|
102
|
+
username: account.username,
|
|
103
|
+
email: account.email,
|
|
104
|
+
verified: account.verified,
|
|
105
|
+
roles: access.roles,
|
|
106
|
+
permissions: access.permissions
|
|
107
|
+
}
|
|
108
|
+
};
|
|
109
|
+
} } : {}
|
|
110
|
+
};
|
|
47
111
|
return {
|
|
48
112
|
tokens,
|
|
49
113
|
identity,
|
|
@@ -55,21 +119,43 @@ var createAuth = (config) => {
|
|
|
55
119
|
basePath,
|
|
56
120
|
/** The naming this was built with, so a page server takes it from here instead of being told a second time. */
|
|
57
121
|
cookieConfig: config.cookie,
|
|
122
|
+
/**
|
|
123
|
+
* Cross-site request forgery: issuing tokens, writing the readable cookie, and the check. `undefined` when the
|
|
124
|
+
* deployment turned it off. The `/auth` routes below already enforce it.
|
|
125
|
+
*/
|
|
126
|
+
csrf,
|
|
58
127
|
/** The `/auth` flows as descriptors, ready for whatever serves HTTP here. */
|
|
59
128
|
routes: authRoutes({
|
|
60
129
|
api,
|
|
61
|
-
cookies
|
|
130
|
+
cookies,
|
|
131
|
+
csrf
|
|
62
132
|
}),
|
|
63
133
|
/** May this request proceed, and as whom. */
|
|
64
134
|
authorize: createAuthorizer(identity, policy),
|
|
65
|
-
/**
|
|
66
|
-
|
|
135
|
+
/**
|
|
136
|
+
* Does what a flow's answer says about the cookies — the session, and the CSRF token that has to be re-issued
|
|
137
|
+
* alongside it. Bound to this deployment's `csrf`, so a caller cannot forget to pass it.
|
|
138
|
+
*/
|
|
139
|
+
applySession: (req, res, outcome) => applySessionOutcome(req, res, outcome, cookies, csrf),
|
|
67
140
|
/** Both halves of a space permission: a global capability, and membership of that space. */
|
|
68
141
|
can: identity.can,
|
|
69
142
|
/**
|
|
70
|
-
*
|
|
143
|
+
* WHO a page request carries, and nothing about signing anyone in or out.
|
|
144
|
+
*
|
|
145
|
+
* This is the set a deployment wants when its renderer and its sign-in live in different processes — a page
|
|
146
|
+
* server that must know who is looking at it, while the `/auth` flows are served by an API tier next to it.
|
|
147
|
+
* That shape had no name, so a deployment expressed it by reaching into `ssrAdapters` and lifting two keys out
|
|
148
|
+
* by hand: correct until a third identity adapter appears, which it then silently does not get.
|
|
149
|
+
*
|
|
150
|
+
* `exchangeCredential` belongs here rather than with the flows: turning a credential a browser already holds
|
|
151
|
+
* into a session is a question about identity, and it is the one the page server itself answers.
|
|
152
|
+
*/
|
|
153
|
+
ssrIdentityAdapters,
|
|
154
|
+
/**
|
|
155
|
+
* Every adapter a page server asks for, already answered. Spread these into `createServer`'s `adapters`
|
|
71
156
|
* and a rendered page knows who is looking at it, `POST /auth/login` signs them in, `POST /auth/logout` signs
|
|
72
|
-
* them out, and the cookies are written and cleared by the same code that reads them back.
|
|
157
|
+
* them out, and the cookies are written and cleared by the same code that reads them back. A renderer that
|
|
158
|
+
* serves no login of its own wants {@link ssrIdentityAdapters} instead.
|
|
73
159
|
*
|
|
74
160
|
* They exist because the page server speaks in sessions and users while the flows above answer in outcomes,
|
|
75
161
|
* and translating between the two is a step with exactly one right answer — which makes it the server's, not
|
|
@@ -80,24 +166,10 @@ var createAuth = (config) => {
|
|
|
80
166
|
const outcome = await api.login(credentials);
|
|
81
167
|
return outcome.ok ? outcome.session : void 0;
|
|
82
168
|
},
|
|
83
|
-
getUser: async (req) => {
|
|
84
|
-
const resolved = await identity.resolveActor(req);
|
|
85
|
-
if (!resolved.ok) return;
|
|
86
|
-
const { id, username, email, verified, permissions, roles, token, expiresAt } = resolved.actor;
|
|
87
|
-
return {
|
|
88
|
-
id,
|
|
89
|
-
username,
|
|
90
|
-
email,
|
|
91
|
-
verified,
|
|
92
|
-
permissions,
|
|
93
|
-
roles,
|
|
94
|
-
token,
|
|
95
|
-
expiresAt
|
|
96
|
-
};
|
|
97
|
-
},
|
|
98
169
|
endSession: async (req) => {
|
|
99
170
|
await api.logout({ accessToken: cookies.resolveSessionToken(req) });
|
|
100
|
-
}
|
|
171
|
+
},
|
|
172
|
+
...ssrIdentityAdapters
|
|
101
173
|
}
|
|
102
174
|
};
|
|
103
175
|
};
|
|
@@ -22,8 +22,14 @@ export declare const createCarriers: (sessionCookieName: (hostname: string) => s
|
|
|
22
22
|
spaceTokenCandidates: (carrier: CredentialCarrier) => string[];
|
|
23
23
|
};
|
|
24
24
|
/**
|
|
25
|
-
* The origin the caller claims to be presenting from — the `Origin` header,
|
|
26
|
-
*
|
|
27
|
-
*
|
|
25
|
+
* The origin the caller claims to be presenting from — the `Origin` header, and nothing else. Distinct from this
|
|
26
|
+
* server's own origin (see `requestOrigin` in the request parser): this one is the claim a credential's domain
|
|
27
|
+
* binding is checked against.
|
|
28
|
+
*
|
|
29
|
+
* Empty when the header is absent, which is a fact the caller downstream has to be able to see. This used to fall
|
|
30
|
+
* back to `carrier.hostname`, and that fallback could only ever deny: a hostname carries no scheme, so it matched no
|
|
31
|
+
* entry in any origins list, and every server-to-server caller — a self-hosted deployment reading its own space
|
|
32
|
+
* through `createCloudAdapters`, most of all — was refused with `origin-not-allowed` while the branch written to
|
|
33
|
+
* handle a missing Origin was unreachable.
|
|
28
34
|
*/
|
|
29
35
|
export declare const presentedOrigin: (carrier: CredentialCarrier) => string;
|
|
@@ -39,10 +39,16 @@ var createCarriers = (sessionCookieName) => ({
|
|
|
39
39
|
}
|
|
40
40
|
});
|
|
41
41
|
/**
|
|
42
|
-
* The origin the caller claims to be presenting from — the `Origin` header,
|
|
43
|
-
*
|
|
44
|
-
*
|
|
42
|
+
* The origin the caller claims to be presenting from — the `Origin` header, and nothing else. Distinct from this
|
|
43
|
+
* server's own origin (see `requestOrigin` in the request parser): this one is the claim a credential's domain
|
|
44
|
+
* binding is checked against.
|
|
45
|
+
*
|
|
46
|
+
* Empty when the header is absent, which is a fact the caller downstream has to be able to see. This used to fall
|
|
47
|
+
* back to `carrier.hostname`, and that fallback could only ever deny: a hostname carries no scheme, so it matched no
|
|
48
|
+
* entry in any origins list, and every server-to-server caller — a self-hosted deployment reading its own space
|
|
49
|
+
* through `createCloudAdapters`, most of all — was refused with `origin-not-allowed` while the branch written to
|
|
50
|
+
* handle a missing Origin was unreachable.
|
|
45
51
|
*/
|
|
46
|
-
var presentedOrigin = (carrier) => headerValue(carrier.headers.origin)
|
|
52
|
+
var presentedOrigin = (carrier) => headerValue(carrier.headers.origin);
|
|
47
53
|
//#endregion
|
|
48
54
|
export { createCarriers, presentedOrigin };
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { CookieSink } from './session';
|
|
2
|
+
import { SSRAuthCookie } from '@plitzi/sdk-shared';
|
|
3
|
+
export interface CsrfConfig {
|
|
4
|
+
/** Signs the tokens. `createAuth` uses the token secret, so a deployment never states this either. */
|
|
5
|
+
secret: string;
|
|
6
|
+
/** Naming and scope for the cookie, so it lands beside the session one. */
|
|
7
|
+
cookie?: SSRAuthCookie;
|
|
8
|
+
/**
|
|
9
|
+
* Origins that count as this deployment's own, beyond the request's own site.
|
|
10
|
+
*
|
|
11
|
+
* What it decides is which cross-site page may hand out a session — see `foreign` below. `createAuth` fills it
|
|
12
|
+
* from `identity.platformOrigins`, so a deployment that already declared its hosts has said this too, and this
|
|
13
|
+
* whole module ends up configuring itself.
|
|
14
|
+
*/
|
|
15
|
+
allowedOrigins?: string[] | ((origin: string) => boolean);
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* What kind of flow a request is reaching, which is what decides when a token is demanded.
|
|
19
|
+
*
|
|
20
|
+
* - **`write`** — an action taken as somebody. Guarded whenever a cookie could have authenticated it.
|
|
21
|
+
* - **`signIn`** — hands out a session. There is no cookie to protect yet, and the attack is different: another
|
|
22
|
+
* site signing a visitor into an account IT controls, so that what they do next is recorded against it. Guarded
|
|
23
|
+
* by where the request came from, since that is what separates the attack from every legitimate sign-in.
|
|
24
|
+
* - **`delegated`** — also hands out a session, but the flow checks the caller's origin itself against something
|
|
25
|
+
* narrower than any list here: `/auth/exchange` acts for a space and is refused unless the origin is one that
|
|
26
|
+
* space declared. Applying the check below on top would refuse every legitimate embed.
|
|
27
|
+
*/
|
|
28
|
+
export type CsrfSubject = 'write' | 'signIn' | 'delegated';
|
|
29
|
+
export type CsrfFailure = 'missing' | 'malformed' | 'expired' | 'mismatch';
|
|
30
|
+
export declare const csrfFailureMessage: Record<CsrfFailure, string>;
|
|
31
|
+
export type CsrfResult = {
|
|
32
|
+
ok: true;
|
|
33
|
+
} | {
|
|
34
|
+
ok: false;
|
|
35
|
+
reason: CsrfFailure;
|
|
36
|
+
};
|
|
37
|
+
/** What a request has to look like for this to read it. Satisfied by Express, `node:http` plus a parse, and ours. */
|
|
38
|
+
export interface CsrfCarrier {
|
|
39
|
+
method?: string;
|
|
40
|
+
headers: Record<string, string | string[] | undefined>;
|
|
41
|
+
hostname: string;
|
|
42
|
+
cookies?: Record<string, string>;
|
|
43
|
+
body?: unknown;
|
|
44
|
+
}
|
|
45
|
+
export declare const createCsrf: (config: CsrfConfig) => {
|
|
46
|
+
issue: (sessionToken?: string) => string;
|
|
47
|
+
/** The name the cookie lands under, for a client that reads it by hand. */
|
|
48
|
+
cookieName: (hostname: string) => string;
|
|
49
|
+
/** The header the echo is expected in, published by `GET /auth/csrf` so a client is never told it twice. */
|
|
50
|
+
headerName: string;
|
|
51
|
+
/** Readable on purpose: `httpOnly` would make it impossible for a page to echo it back. It is not a credential. */
|
|
52
|
+
write: (req: {
|
|
53
|
+
hostname: string;
|
|
54
|
+
}, res: CookieSink, token: string) => void;
|
|
55
|
+
/**
|
|
56
|
+
* Drops the cookie, on the same attributes it was written with — anything else leaves the original in place and
|
|
57
|
+
* adds a second one the browser then has to choose between.
|
|
58
|
+
*
|
|
59
|
+
* Called when a session ends. Nothing depended on it going: the token is signed over the session it belongs to,
|
|
60
|
+
* so once that session is revoked it verifies against nothing, and a write is only ever asked for a token when a
|
|
61
|
+
* session cookie is present. But a logout that leaves a cookie behind is one somebody has to reason about to
|
|
62
|
+
* dismiss, and on a shared browser "signed out" should mean nothing of the session is still sitting there.
|
|
63
|
+
*/
|
|
64
|
+
clear: (req: {
|
|
65
|
+
hostname: string;
|
|
66
|
+
}, res: CookieSink) => void;
|
|
67
|
+
read: (carrier: CsrfCarrier) => string | undefined;
|
|
68
|
+
/**
|
|
69
|
+
* Does this request need a token at all?
|
|
70
|
+
*
|
|
71
|
+
* Two requests can never be forged into, whatever they are reaching, and neither is ever asked:
|
|
72
|
+
*
|
|
73
|
+
* - a **safe method**, which changes nothing;
|
|
74
|
+
* - one presenting **`Authorization: Bearer`** — a cross-origin page cannot set that header without a
|
|
75
|
+
* preflight this server would have to allow, so requiring a token would break every API client to protect
|
|
76
|
+
* nobody.
|
|
77
|
+
*
|
|
78
|
+
* After that it depends on what the flow does, which is what `subject` says — an action taken as somebody is
|
|
79
|
+
* protected by the session cookie's presence, a sign-in by where the request came from. See {@link CsrfSubject}.
|
|
80
|
+
*/
|
|
81
|
+
required: (carrier: CsrfCarrier, subject?: CsrfSubject) => boolean;
|
|
82
|
+
/**
|
|
83
|
+
* The whole check. The echoed token must verify against the session the request carries, and — when the
|
|
84
|
+
* cookie is also present — match it. The second half costs nothing and blocks a token that leaked somewhere
|
|
85
|
+
* the cookie did not.
|
|
86
|
+
*/
|
|
87
|
+
verify: (carrier: CsrfCarrier, sessionToken?: string) => CsrfResult;
|
|
88
|
+
};
|
|
89
|
+
export type Csrf = ReturnType<typeof createCsrf>;
|