@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.
Files changed (274) hide show
  1. package/CHANGELOG.md +44 -0
  2. package/README.md +283 -8
  3. package/dist/actions.d.ts +62 -0
  4. package/dist/actions.js +12 -0
  5. package/dist/adapters/cloudAdapters.d.ts +72 -0
  6. package/dist/adapters/cloudAdapters.js +303 -0
  7. package/dist/adapters/space/index.d.ts +93 -0
  8. package/dist/adapters/space/index.js +122 -0
  9. package/dist/adapters/space/resolvers.d.ts +69 -0
  10. package/dist/adapters/space/resolvers.js +79 -0
  11. package/dist/adapters/space/space.test.d.ts +1 -0
  12. package/dist/adapters/space/types.d.ts +64 -0
  13. package/dist/adapters/space/types.js +41 -0
  14. package/dist/auth.d.ts +50 -0
  15. package/dist/auth.js +19 -0
  16. package/dist/core/auth/api.d.ts +400 -11
  17. package/dist/core/auth/api.js +1044 -50
  18. package/dist/core/auth/createAuth.d.ts +135 -30
  19. package/dist/core/auth/createAuth.js +96 -24
  20. package/dist/core/auth/credentials.d.ts +9 -3
  21. package/dist/core/auth/credentials.js +10 -4
  22. package/dist/core/auth/csrf.d.ts +89 -0
  23. package/dist/core/auth/csrf.js +234 -0
  24. package/dist/core/auth/csrf.test.d.ts +1 -0
  25. package/dist/core/auth/identity.d.ts +6 -0
  26. package/dist/core/auth/identity.js +37 -8
  27. package/dist/core/auth/lifecycle.test.d.ts +1 -0
  28. package/dist/core/auth/oauth/index.d.ts +8 -0
  29. package/dist/core/auth/oauth/index.js +13 -15
  30. package/dist/core/auth/passwords.d.ts +15 -0
  31. package/dist/core/auth/passwords.js +32 -0
  32. package/dist/core/auth/redirects.d.ts +18 -0
  33. package/dist/core/auth/redirects.js +31 -0
  34. package/dist/core/auth/redirects.test.d.ts +1 -0
  35. package/dist/core/auth/routes.d.ts +4 -2
  36. package/dist/core/auth/routes.js +234 -9
  37. package/dist/core/auth/session.d.ts +11 -0
  38. package/dist/core/auth/session.js +1 -1
  39. package/dist/core/auth/spaceTokens.d.ts +38 -2
  40. package/dist/core/auth/spaceTokens.js +54 -2
  41. package/dist/core/auth/throttle.d.ts +5 -0
  42. package/dist/core/auth/throttle.js +81 -0
  43. package/dist/core/auth/throttle.test.d.ts +1 -0
  44. package/dist/core/auth/tokens.d.ts +83 -12
  45. package/dist/core/auth/tokens.js +51 -9
  46. package/dist/core/auth/totp.d.ts +48 -0
  47. package/dist/core/auth/totp.js +118 -0
  48. package/dist/core/auth/totp.test.d.ts +1 -0
  49. package/dist/core/createServer.d.ts +0 -6
  50. package/dist/core/createServer.js +35 -3
  51. package/dist/core/fontStore.d.ts +33 -0
  52. package/dist/core/fontStore.js +53 -0
  53. package/dist/core/fontStore.test.d.ts +1 -0
  54. package/dist/core/handlers/authMiddleware.js +1 -1
  55. package/dist/core/handlers/authRouteHandlers.d.ts +9 -1
  56. package/dist/core/handlers/authRouteHandlers.js +4 -3
  57. package/dist/core/handlers/csrfMiddleware.d.ts +20 -0
  58. package/dist/core/handlers/csrfMiddleware.js +32 -0
  59. package/dist/core/handlers/oauthRouteHandlers.d.ts +46 -0
  60. package/dist/core/handlers/oauthRouteHandlers.js +159 -0
  61. package/dist/core/handlers/socialRouteHandlers.d.ts +40 -0
  62. package/dist/core/handlers/socialRouteHandlers.js +129 -0
  63. package/dist/core/handlers/socialRouteHandlers.test.d.ts +1 -0
  64. package/dist/core/handlers/types.d.ts +22 -5
  65. package/dist/core/health.d.ts +11 -1
  66. package/dist/core/health.js +27 -3
  67. package/dist/core/http/dispatcher.d.ts +1 -1
  68. package/dist/core/http/dispatcher.js +20 -1
  69. package/dist/core/http/stages/authApi.js +1 -1
  70. package/dist/core/http/stages/fontAssets.d.ts +9 -0
  71. package/dist/core/http/stages/fontAssets.js +27 -0
  72. package/dist/core/http/stages/oauth.d.ts +19 -0
  73. package/dist/core/http/stages/oauth.js +118 -0
  74. package/dist/core/http/types.d.ts +12 -0
  75. package/dist/core/oauth/authorize.d.ts +22 -0
  76. package/dist/core/oauth/authorize.js +254 -0
  77. package/dist/core/oauth/authorize.test.d.ts +1 -0
  78. package/dist/core/oauth/challenge.d.ts +11 -0
  79. package/dist/core/oauth/challenge.js +33 -0
  80. package/dist/core/oauth/consentPage.d.ts +12 -0
  81. package/dist/core/oauth/consentPage.js +131 -0
  82. package/dist/core/oauth/metadata.d.ts +41 -0
  83. package/dist/core/oauth/metadata.js +71 -0
  84. package/dist/core/oauth/params.d.ts +5 -0
  85. package/dist/core/oauth/params.js +5 -0
  86. package/dist/core/oauth/pkce.d.ts +6 -0
  87. package/dist/core/oauth/pkce.js +15 -0
  88. package/dist/core/oauth/records.d.ts +72 -0
  89. package/dist/core/oauth/records.js +49 -0
  90. package/dist/core/oauth/register.d.ts +5 -0
  91. package/dist/core/oauth/register.js +55 -0
  92. package/dist/core/oauth/respond.d.ts +21 -0
  93. package/dist/core/oauth/respond.js +59 -0
  94. package/dist/core/oauth/token.d.ts +14 -0
  95. package/dist/core/oauth/token.js +130 -0
  96. package/dist/core/previewToken.d.ts +20 -0
  97. package/dist/core/previewToken.js +29 -1
  98. package/dist/core/server/pageServer.js +4 -1
  99. package/dist/core/services/action.d.ts +6 -4
  100. package/dist/core/services/action.js +103 -10
  101. package/dist/core/services/registry.js +2 -0
  102. package/dist/core/services/resolve.d.ts +8 -0
  103. package/dist/core/services/resolve.js +12 -1
  104. package/dist/core/staticFiles.js +1 -2
  105. package/dist/handlers.d.ts +7 -1
  106. package/dist/handlers.js +4 -1
  107. package/dist/helpers/buildResponseHelpers.d.ts +8 -0
  108. package/dist/helpers/buildResponseHelpers.js +9 -1
  109. package/dist/helpers/buildServerInfo.d.ts +8 -3
  110. package/dist/helpers/buildServerInfo.js +18 -14
  111. package/dist/helpers/cache/keys.d.ts +13 -0
  112. package/dist/helpers/cache/keys.js +12 -1
  113. package/dist/helpers/onAbort.d.ts +12 -0
  114. package/dist/helpers/onAbort.js +22 -0
  115. package/dist/helpers/onAbort.test.d.ts +1 -0
  116. package/dist/helpers/outboundGuard.d.ts +27 -0
  117. package/dist/helpers/outboundGuard.js +49 -0
  118. package/dist/helpers/outboundGuard.test.d.ts +1 -0
  119. package/dist/helpers/serverLog.d.ts +6 -3
  120. package/dist/helpers/serverLog.js +20 -3
  121. package/dist/index.d.ts +6 -0
  122. package/dist/index.js +9 -4
  123. package/dist/kernel.d.ts +5 -22
  124. package/dist/kernel.js +1 -14
  125. package/dist/modules/actions/{handler.js → connectorWrite.js} +5 -2
  126. package/dist/modules/actions/connectorWrite.test.d.ts +1 -0
  127. package/dist/modules/actions/index.d.ts +40 -0
  128. package/dist/modules/actions/index.js +46 -0
  129. package/dist/modules/actions/moduleFor.d.ts +3 -0
  130. package/dist/modules/actions/moduleFor.js +35 -0
  131. package/dist/modules/actions/moduleFor.test.d.ts +1 -0
  132. package/dist/modules/actions/runtime/check.d.ts +24 -0
  133. package/dist/modules/actions/runtime/check.js +111 -0
  134. package/dist/modules/actions/runtime/check.test.d.ts +1 -0
  135. package/dist/modules/actions/runtime/errors.d.ts +11 -0
  136. package/dist/modules/actions/runtime/errors.js +17 -0
  137. package/dist/modules/actions/runtime/guards.d.ts +126 -0
  138. package/dist/modules/actions/runtime/guards.js +246 -0
  139. package/dist/modules/actions/runtime/guards.test.d.ts +1 -0
  140. package/dist/modules/actions/runtime/kvStore.d.ts +27 -0
  141. package/dist/modules/actions/runtime/kvStore.js +41 -0
  142. package/dist/modules/actions/runtime/kvStore.test.d.ts +1 -0
  143. package/dist/modules/actions/runtime/limits.d.ts +11 -0
  144. package/dist/modules/actions/runtime/limits.js +35 -0
  145. package/dist/modules/actions/runtime/memoryKv.d.ts +2 -0
  146. package/dist/modules/actions/runtime/memoryKv.js +77 -0
  147. package/dist/modules/actions/runtime/namespaceKv.d.ts +9 -0
  148. package/dist/modules/actions/runtime/namespaceKv.js +19 -0
  149. package/dist/modules/actions/runtime/precheck.d.ts +27 -0
  150. package/dist/modules/actions/runtime/precheck.js +45 -0
  151. package/dist/modules/actions/runtime/renderResolver.d.ts +15 -0
  152. package/dist/modules/actions/runtime/renderResolver.js +130 -0
  153. package/dist/modules/actions/runtime/renderResolver.test.d.ts +1 -0
  154. package/dist/modules/actions/runtime/renderShare.d.ts +29 -0
  155. package/dist/modules/actions/runtime/renderShare.js +36 -0
  156. package/dist/modules/actions/runtime/renderShare.test.d.ts +1 -0
  157. package/dist/modules/actions/runtime/report.d.ts +14 -0
  158. package/dist/modules/actions/runtime/report.js +22 -0
  159. package/dist/modules/actions/runtime/runAction.d.ts +12 -0
  160. package/dist/modules/actions/runtime/runAction.js +405 -0
  161. package/dist/modules/actions/runtime/runAction.test.d.ts +1 -0
  162. package/dist/modules/actions/runtime/runLogger.d.ts +31 -0
  163. package/dist/modules/actions/runtime/runLogger.js +66 -0
  164. package/dist/modules/actions/runtime/runLogger.test.d.ts +1 -0
  165. package/dist/modules/actions/runtime/schedule.d.ts +35 -0
  166. package/dist/modules/actions/runtime/schedule.js +76 -0
  167. package/dist/modules/actions/runtime/schedule.test.d.ts +1 -0
  168. package/dist/modules/actions/runtime/scope.d.ts +40 -0
  169. package/dist/modules/actions/runtime/scope.js +103 -0
  170. package/dist/modules/actions/runtime/triggers.d.ts +12 -0
  171. package/dist/modules/actions/runtime/triggers.js +14 -0
  172. package/dist/modules/actions/taskCatalog.d.ts +26 -0
  173. package/dist/modules/actions/taskCatalog.js +24 -0
  174. package/dist/modules/actions/taskCatalog.test.d.ts +1 -0
  175. package/dist/modules/actions/tasks/auth.d.ts +2 -0
  176. package/dist/modules/actions/tasks/auth.js +40 -0
  177. package/dist/modules/actions/tasks/builtins.d.ts +8 -0
  178. package/dist/modules/actions/tasks/builtins.js +24 -0
  179. package/dist/modules/actions/tasks/connector.d.ts +2 -0
  180. package/dist/modules/actions/tasks/connector.js +141 -0
  181. package/dist/modules/actions/tasks/db.d.ts +2 -0
  182. package/dist/modules/actions/tasks/db.js +59 -0
  183. package/dist/modules/actions/tasks/db.test.d.ts +1 -0
  184. package/dist/modules/actions/tasks/flow.d.ts +3 -0
  185. package/dist/modules/actions/tasks/flow.js +109 -0
  186. package/dist/modules/actions/tasks/helpers.d.ts +16 -0
  187. package/dist/modules/actions/tasks/helpers.js +30 -0
  188. package/dist/modules/actions/tasks/http.d.ts +2 -0
  189. package/dist/modules/actions/tasks/http.js +111 -0
  190. package/dist/modules/actions/tasks/http.test.d.ts +1 -0
  191. package/dist/modules/actions/tasks/kv.d.ts +2 -0
  192. package/dist/modules/actions/tasks/kv.js +102 -0
  193. package/dist/modules/actions/tasks/kv.test.d.ts +1 -0
  194. package/dist/modules/actions/tasks/registry.d.ts +10 -0
  195. package/dist/modules/actions/tasks/registry.js +36 -0
  196. package/dist/modules/actions/tasks/transform.d.ts +2 -0
  197. package/dist/modules/actions/tasks/transform.js +34 -0
  198. package/dist/modules/actions/transport/callHandler.d.ts +24 -0
  199. package/dist/modules/actions/transport/callHandler.js +233 -0
  200. package/dist/modules/actions/transport/callHandler.test.d.ts +1 -0
  201. package/dist/modules/actions/transport/cancelHandler.d.ts +23 -0
  202. package/dist/modules/actions/transport/cancelHandler.js +23 -0
  203. package/dist/modules/actions/transport/catalogHandler.d.ts +18 -0
  204. package/dist/modules/actions/transport/catalogHandler.js +21 -0
  205. package/dist/modules/actions/transport/stream.d.ts +23 -0
  206. package/dist/modules/actions/transport/stream.js +100 -0
  207. package/dist/modules/actions/transport/verifySignature.d.ts +16 -0
  208. package/dist/modules/actions/transport/verifySignature.js +72 -0
  209. package/dist/modules/actions/transport/webhookHandler.d.ts +25 -0
  210. package/dist/modules/actions/transport/webhookHandler.js +206 -0
  211. package/dist/modules/actions/transport/webhookHandler.test.d.ts +1 -0
  212. package/dist/modules/actions/types.d.ts +234 -0
  213. package/dist/modules/connectors/engine.js +4 -1
  214. package/dist/modules/connectors/resolver.d.ts +10 -2
  215. package/dist/modules/connectors/resolver.js +8 -3
  216. package/dist/modules/mysql/accounts.d.ts +5 -0
  217. package/dist/modules/mysql/accounts.js +365 -0
  218. package/dist/modules/mysql/accounts.test.d.ts +1 -0
  219. package/dist/modules/mysql/admin.d.ts +58 -0
  220. package/dist/modules/mysql/admin.js +133 -0
  221. package/dist/modules/mysql/config.d.ts +82 -0
  222. package/dist/modules/mysql/config.js +30 -0
  223. package/dist/modules/mysql/identities.d.ts +35 -0
  224. package/dist/modules/mysql/identities.js +43 -0
  225. package/dist/modules/mysql/index.d.ts +67 -0
  226. package/dist/modules/mysql/index.js +62 -0
  227. package/dist/modules/mysql/pool.d.ts +3 -0
  228. package/dist/modules/mysql/pool.js +88 -0
  229. package/dist/modules/mysql/query.d.ts +12 -0
  230. package/dist/modules/mysql/query.js +21 -0
  231. package/dist/modules/mysql/schema.d.ts +43 -0
  232. package/dist/modules/mysql/schema.js +340 -0
  233. package/dist/modules/mysql/schema.test.d.ts +1 -0
  234. package/dist/modules/mysql/sessions.d.ts +33 -0
  235. package/dist/modules/mysql/sessions.js +46 -0
  236. package/dist/modules/mysql/spaceTokens.d.ts +11 -0
  237. package/dist/modules/mysql/spaceTokens.js +59 -0
  238. package/dist/modules/mysql/store.integration.test.d.ts +1 -0
  239. package/dist/modules/rsc/connectorRscData.d.ts +25 -0
  240. package/dist/modules/rsc/connectorRscData.js +51 -0
  241. package/dist/modules/rsc/connectorRscData.test.d.ts +1 -0
  242. package/dist/modules/rsc/handler.js +16 -3
  243. package/dist/modules/rsc/matchRscPage.d.ts +14 -0
  244. package/dist/modules/rsc/matchRscPage.js +26 -0
  245. package/dist/modules/rsc/resolveRscData.d.ts +14 -1
  246. package/dist/modules/rsc/resolveRscData.js +23 -34
  247. package/dist/modules/ssr/Component.d.ts +6 -2
  248. package/dist/modules/ssr/Component.js +3 -1
  249. package/dist/modules/ssr/draftSession.test.d.ts +1 -0
  250. package/dist/modules/ssr/handler.js +15 -2
  251. package/dist/modules/ssr/loadPluginComponents.js +22 -5
  252. package/dist/modules/ssr/prepareRender.js +87 -8
  253. package/dist/modules/ssr/prepareRender.test.d.ts +1 -0
  254. package/dist/modules/ssr/preview.d.ts +21 -8
  255. package/dist/modules/ssr/preview.js +38 -14
  256. package/dist/modules/ssr/registerExternalPlugins.js +2 -2
  257. package/dist/modules/ssr/resolvePageSeo.d.ts +18 -0
  258. package/dist/modules/ssr/resolvePageSeo.js +26 -0
  259. package/dist/modules/ssr/resolvePageSeo.test.d.ts +1 -0
  260. package/dist/modules/ssr/template.test.d.ts +1 -0
  261. package/dist/modules/ssr/views/template.ejs +71 -5
  262. package/dist/mysql.d.ts +26 -0
  263. package/dist/mysql.js +4 -0
  264. package/dist/oauth.d.ts +24 -0
  265. package/dist/oauth.js +10 -0
  266. package/dist/plugins/compile.d.ts +1 -0
  267. package/dist/plugins/compile.js +37 -3
  268. package/dist/plugins/manager.d.ts +40 -0
  269. package/dist/plugins/manager.js +113 -13
  270. package/dist/plugins/manager.test.d.ts +1 -0
  271. package/package.json +43 -14
  272. package/skills/plitzi-authoring/SKILL.md +252 -0
  273. /package/dist/{modules/actions/handler.test.d.ts → adapters/cloudAdapters.test.d.ts} +0 -0
  274. /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
- access: 86400;
64
- refresh: number;
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
- describe: () => import('./api').AuthOutcome;
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<import('./api').AuthOutcome>;
107
- revokeSessions: (userId?: number) => Promise<import('./api').AuthOutcome>;
108
- exchange: (provider: string, token: string, carrier: CredentialCarrier) => Promise<import('./api').AuthOutcome>;
109
- signup: (fields: Record<string, unknown>) => Promise<import('./api').AuthOutcome>;
110
- forgotPassword: (email: string) => Promise<import('./api').AuthOutcome>;
111
- resetPassword: (token: string, password: string) => Promise<import('./api').AuthOutcome>;
112
- validateAccount: (token: string) => Promise<import('./api').AuthOutcome>;
113
- resendVerification: (email: string) => Promise<import('./api').AuthOutcome>;
114
- issueSession: (userId: number) => Promise<SSRSession>;
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: import('./session').CookieSink, session: SSRSession) => void;
169
+ }, res: CookieSink, session: SSRSession) => void;
121
170
  clear: (req: {
122
171
  hostname: string;
123
- }, res: import('./session').CookieSink) => void;
172
+ }, res: CookieSink) => void;
124
173
  writeFlow: (req: {
125
174
  hostname: string;
126
- }, res: import('./session').CookieSink, value: string, ttlSeconds: number) => void;
175
+ }, res: CookieSink, value: string, ttlSeconds: number) => void;
127
176
  clearFlow: (req: {
128
177
  hostname: string;
129
- }, res: import('./session').CookieSink) => void;
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
- /** Does what a flow's answer says about the session cookies. */
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: import('./session').CookieSink, outcome: import('./api').AuthOutcome, cookies: import('./session').SessionCookies) => void;
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
- * The three adapters a page server asks for, already answered. Spread these into `createServer`'s `adapters`
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
- authenticate: (credentials: Record<string, string>) => Promise<SSRSession | undefined>;
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: config.api
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
- /** Does what a flow's answer says about the session cookies. */
66
- applySession: applySessionOutcome,
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
- * The three adapters a page server asks for, already answered. Spread these into `createServer`'s `adapters`
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, falling back to the host it addressed.
26
- * Distinct from this server's own origin (see `requestOrigin` in the request parser): this one is the claim a
27
- * credential's domain binding is checked against.
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, falling back to the host it addressed.
43
- * Distinct from this server's own origin (see `requestOrigin` in the request parser): this one is the claim a
44
- * credential's domain binding is checked against.
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) || carrier.hostname;
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>;