@_mustachio/openauth 0.13.2 → 0.13.3

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 (48) hide show
  1. package/dist/esm/domain/authorize.js +11 -3
  2. package/dist/esm/domain/callback.js +9 -2
  3. package/dist/esm/domain/method-dispatch.js +1 -0
  4. package/dist/esm/domain/method-route.js +2 -0
  5. package/dist/esm/domain/mount.js +29 -0
  6. package/dist/esm/http/handlers/method-route.js +8 -3
  7. package/dist/esm/index.js +3 -0
  8. package/dist/esm/methods/code.js +9 -8
  9. package/dist/esm/methods/oauth2-generic.js +2 -1
  10. package/dist/esm/methods/passkey.js +3 -2
  11. package/dist/esm/methods/password.js +9 -8
  12. package/dist/esm/methods/saml-sp/metadata.js +2 -1
  13. package/dist/types/domain/authorize.d.ts.map +1 -1
  14. package/dist/types/domain/callback.d.ts +6 -5
  15. package/dist/types/domain/callback.d.ts.map +1 -1
  16. package/dist/types/domain/method-dispatch.d.ts +5 -0
  17. package/dist/types/domain/method-dispatch.d.ts.map +1 -1
  18. package/dist/types/domain/method-route.d.ts +6 -0
  19. package/dist/types/domain/method-route.d.ts.map +1 -1
  20. package/dist/types/domain/mount.d.ts +90 -0
  21. package/dist/types/domain/mount.d.ts.map +1 -0
  22. package/dist/types/http/handlers/method-route.d.ts.map +1 -1
  23. package/dist/types/index.d.ts +16 -0
  24. package/dist/types/index.d.ts.map +1 -1
  25. package/dist/types/methods/code.d.ts.map +1 -1
  26. package/dist/types/methods/oauth2-generic.d.ts.map +1 -1
  27. package/dist/types/methods/passkey.d.ts.map +1 -1
  28. package/dist/types/methods/password.d.ts.map +1 -1
  29. package/dist/types/methods/saml-sp/metadata.d.ts.map +1 -1
  30. package/dist/types/types/idp.d.ts +16 -1
  31. package/dist/types/types/idp.d.ts.map +1 -1
  32. package/dist/types/types/method.d.ts +11 -0
  33. package/dist/types/types/method.d.ts.map +1 -1
  34. package/package.json +2 -1
  35. package/src/domain/authorize.ts +15 -4
  36. package/src/domain/callback.ts +18 -7
  37. package/src/domain/method-dispatch.ts +6 -0
  38. package/src/domain/method-route.ts +8 -0
  39. package/src/domain/mount.ts +113 -0
  40. package/src/http/handlers/method-route.ts +9 -5
  41. package/src/index.ts +17 -0
  42. package/src/methods/code.ts +15 -6
  43. package/src/methods/oauth2-generic.ts +6 -1
  44. package/src/methods/passkey.ts +6 -2
  45. package/src/methods/password.ts +19 -4
  46. package/src/methods/saml-sp/metadata.ts +5 -1
  47. package/src/types/idp.ts +16 -1
  48. package/src/types/method.ts +11 -0
@@ -26,6 +26,7 @@ import type {
26
26
  MethodContext,
27
27
  MethodResult,
28
28
  } from "../types/method"
29
+ import { mountedPath } from "../domain/mount"
29
30
  import { renderForm } from "../ui/forms"
30
31
 
31
32
  export type PasswordProperties = {
@@ -140,7 +141,7 @@ async function renderLogin(
140
141
  ): Promise<MethodResult<PasswordProperties, PasswordState>> {
141
142
  const body = renderForm({
142
143
  title,
143
- action: `/m/${methodId}/login`,
144
+ action: mountedPath(ctx.issuerUrl, `/m/${methodId}/login`),
144
145
  fields: [
145
146
  {
146
147
  name: "email",
@@ -180,6 +181,7 @@ async function handleLogin(
180
181
  const parsed = loginBodySchema.safeParse(form)
181
182
  if (!parsed.success) {
182
183
  return reLoginWithError(
184
+ ctx.issuerUrl,
183
185
  methodId,
184
186
  title,
185
187
  "Please enter your email and password.",
@@ -188,12 +190,22 @@ async function handleLogin(
188
190
 
189
191
  const user = await users.findByEmail(parsed.data.email, ctx.tenant.id)
190
192
  if (!user) {
191
- return reLoginWithError(methodId, title, "Invalid email or password.")
193
+ return reLoginWithError(
194
+ ctx.issuerUrl,
195
+ methodId,
196
+ title,
197
+ "Invalid email or password.",
198
+ )
192
199
  }
193
200
 
194
201
  const verified = await hasher.verify(parsed.data.password, user.passwordHash)
195
202
  if (!verified) {
196
- return reLoginWithError(methodId, title, "Invalid email or password.")
203
+ return reLoginWithError(
204
+ ctx.issuerUrl,
205
+ methodId,
206
+ title,
207
+ "Invalid email or password.",
208
+ )
197
209
  }
198
210
 
199
211
  return {
@@ -218,6 +230,7 @@ async function handleRegister(
218
230
  const parsed = registerBodySchema.safeParse(form)
219
231
  if (!parsed.success) {
220
232
  return reLoginWithError(
233
+ ctx.issuerUrl,
221
234
  methodId,
222
235
  title,
223
236
  "Password must be at least 8 characters and email must be valid.",
@@ -227,6 +240,7 @@ async function handleRegister(
227
240
  const existing = await users.findByEmail(parsed.data.email, ctx.tenant.id)
228
241
  if (existing) {
229
242
  return reLoginWithError(
243
+ ctx.issuerUrl,
230
244
  methodId,
231
245
  title,
232
246
  "An account with that email already exists.",
@@ -250,6 +264,7 @@ async function handleRegister(
250
264
  }
251
265
 
252
266
  function reLoginWithError(
267
+ issuerUrl: string,
253
268
  methodId: string,
254
269
  title: string,
255
270
  error: string,
@@ -259,7 +274,7 @@ function reLoginWithError(
259
274
  response: new Response(
260
275
  renderForm({
261
276
  title,
262
- action: `/m/${methodId}/login`,
277
+ action: mountedPath(issuerUrl, `/m/${methodId}/login`),
263
278
  fields: [
264
279
  {
265
280
  name: "email",
@@ -22,6 +22,7 @@
22
22
  * `WantAssertionsSigned` are both read from config, so they state
23
23
  * actual runtime behaviour rather than a hardcoded assumption.
24
24
  */
25
+ import { mountedPath } from "../../domain/mount"
25
26
  import { authError } from "../../types/error"
26
27
  import type { MethodContext, MethodResult } from "../../types/method"
27
28
 
@@ -184,7 +185,10 @@ export async function buildSpMetadata(
184
185
  // served (idp.sloUrl set ⇒ /sls is in publicRoutes). Derived from the
185
186
  // same dispatch input as the ACS so it cannot drift.
186
187
  const cb = new URL(ctx.dispatch.callbackUrl)
187
- const slsUrl = `${cb.protocol}//${cb.host}/m/${methodId}/sls`
188
+ const slsUrl = `${cb.protocol}//${cb.host}${mountedPath(
189
+ ctx.dispatch.issuerUrl,
190
+ `/m/${methodId}/sls`,
191
+ )}`
188
192
  const xml = buildSpMetadataXml({
189
193
  spEntityId,
190
194
  acsUrl: ctx.dispatch.callbackUrl,
package/src/types/idp.ts CHANGED
@@ -255,7 +255,22 @@ export type IdPOptions = {
255
255
  */
256
256
  resolveTenant: (req: Request) => Promise<Result<TenantId, AuthError>>
257
257
 
258
- /** Opt-in for partitioned callback hosts (recovery mechanism #2). */
258
+ /**
259
+ * Opt-in for partitioned callback hosts (recovery mechanism #2).
260
+ *
261
+ * Returns the hostname a given tenant's callbacks arrive on, so the
262
+ * tenant is recoverable from the `Host` header before the state
263
+ * envelope is verified.
264
+ *
265
+ * **The issuer's mount prefix still applies to the returned host.** If
266
+ * `issuerUrl` is `https://example.com/idp`, a tenant host of
267
+ * `acme.example.com` produces `https://acme.example.com/idp/cb/<id>`.
268
+ * This option varies the *authority* of one deployment — those hosts
269
+ * are served by this same service behind the same proxy, so they share
270
+ * its mount. A deployment needing partitioned hosts mounted differently
271
+ * from the issuer is describing two mounts, which one `issuerUrl`
272
+ * cannot express; a second option must not be added to paper over it.
273
+ */
259
274
  callbackHostFor?: (tenantId: TenantId) => string
260
275
 
261
276
  /**
@@ -113,6 +113,17 @@ export type MethodHandler<P, S> = (
113
113
  export type MethodContext<S = unknown> = {
114
114
  /** The raw Web Fetch `Request`. Web standard, not Hono `Context`. */
115
115
  request: Request
116
+ /**
117
+ * The issuer URL of this IdP, always present.
118
+ *
119
+ * Distinct from `dispatch.issuerUrl`, which is only populated for
120
+ * `GET /authorize`. Methods that render their own URLs — a form action
121
+ * re-rendered on a validation error, say — need it on every route, and
122
+ * must build those URLs with `mountedPath` so they carry the
123
+ * deployment's mount prefix. `ctx.request.url` is not a substitute: a
124
+ * proxy-stripped inbound URL has already lost that prefix.
125
+ */
126
+ issuerUrl: string
116
127
  /** Path within the method's mount, e.g. `"/callback"` (without the `/<id>` prefix). */
117
128
  subPath: string
118
129
  tenant: TenantContext