@_mustachio/openauth 0.7.0 → 0.8.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/dist/esm/issuer.js +23 -6
- package/dist/types/issuer.d.ts +87 -3
- package/dist/types/issuer.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/issuer.ts +119 -7
package/dist/esm/issuer.js
CHANGED
|
@@ -66,6 +66,13 @@ function issuer(input) {
|
|
|
66
66
|
const allEncryption = lazy(() => encryptionKeys(storage));
|
|
67
67
|
const signingKey = lazy(() => allSigning().then((all) => all[0]));
|
|
68
68
|
const encryptionKey = lazy(() => allEncryption().then((all) => all[0]));
|
|
69
|
+
const getBasePath = (req) => {
|
|
70
|
+
if (!input.basePath)
|
|
71
|
+
return "";
|
|
72
|
+
if (typeof input.basePath === "string")
|
|
73
|
+
return input.basePath;
|
|
74
|
+
return input.basePath(req);
|
|
75
|
+
};
|
|
69
76
|
const auth = {
|
|
70
77
|
async success(ctx, properties, successOpts) {
|
|
71
78
|
const authorization = await getAuthorization(ctx);
|
|
@@ -125,6 +132,7 @@ function issuer(input) {
|
|
|
125
132
|
}, {
|
|
126
133
|
provider: ctx.get("provider"),
|
|
127
134
|
tenantId: authorization.tenantId,
|
|
135
|
+
context: ctx.get("requestContext"),
|
|
128
136
|
...properties
|
|
129
137
|
}, ctx.req.raw);
|
|
130
138
|
},
|
|
@@ -135,6 +143,7 @@ function issuer(input) {
|
|
|
135
143
|
setCookie(ctx, key, await encrypt(value), {
|
|
136
144
|
maxAge,
|
|
137
145
|
httpOnly: true,
|
|
146
|
+
path: input.cookies?.path,
|
|
138
147
|
...ctx.req.url.startsWith("https://") ? { secure: true, sameSite: "None" } : {}
|
|
139
148
|
});
|
|
140
149
|
},
|
|
@@ -147,7 +156,7 @@ function issuer(input) {
|
|
|
147
156
|
});
|
|
148
157
|
},
|
|
149
158
|
async unset(ctx, key) {
|
|
150
|
-
deleteCookie(ctx, key);
|
|
159
|
+
deleteCookie(ctx, key, { path: input.cookies?.path });
|
|
151
160
|
},
|
|
152
161
|
async invalidate(subject) {
|
|
153
162
|
const keys = await Array.fromAsync(Storage.scan(this.storage, ["oauth:refresh", subject]));
|
|
@@ -207,9 +216,16 @@ function issuer(input) {
|
|
|
207
216
|
return JSON.parse(new TextDecoder().decode(await compactDecrypt(value, await encryptionKey().then((v) => v.private)).then((value2) => value2.plaintext)));
|
|
208
217
|
}
|
|
209
218
|
function issuer2(ctx) {
|
|
210
|
-
|
|
219
|
+
const base = getBasePath(ctx.req.raw);
|
|
220
|
+
return new URL(getRelativeUrl(ctx, base || "/")).origin + base;
|
|
211
221
|
}
|
|
212
222
|
const app = new Hono().use(logger());
|
|
223
|
+
app.use(async (c, next) => {
|
|
224
|
+
if (input.context) {
|
|
225
|
+
c.set("requestContext", input.context(c.req.raw));
|
|
226
|
+
}
|
|
227
|
+
await next();
|
|
228
|
+
});
|
|
213
229
|
const getProviders = async (c) => {
|
|
214
230
|
if (typeof input.providers === "function") {
|
|
215
231
|
return input.providers(c);
|
|
@@ -434,6 +450,7 @@ function issuer(input) {
|
|
|
434
450
|
}
|
|
435
451
|
}, {
|
|
436
452
|
provider: provider.toString(),
|
|
453
|
+
context: c.get("requestContext"),
|
|
437
454
|
...response
|
|
438
455
|
}, c.req.raw);
|
|
439
456
|
}
|
|
@@ -480,11 +497,11 @@ function issuer(input) {
|
|
|
480
497
|
await auth.set(c, "authorization", 60 * 60 * 24, authorization);
|
|
481
498
|
c.set("authorization", authorization);
|
|
482
499
|
if (provider)
|
|
483
|
-
return c.redirect(
|
|
500
|
+
return c.redirect(`${getBasePath(c.req.raw)}/${provider}/authorize`);
|
|
484
501
|
const resolvedProviders = await getProviders(c);
|
|
485
502
|
const providerNames = Object.keys(resolvedProviders);
|
|
486
503
|
if (providerNames.length === 1)
|
|
487
|
-
return c.redirect(
|
|
504
|
+
return c.redirect(`${getBasePath(c.req.raw)}/${providerNames[0]}/authorize`);
|
|
488
505
|
return auth.forward(c, await select()(Object.fromEntries(Object.entries(resolvedProviders).map(([key, value]) => [
|
|
489
506
|
key,
|
|
490
507
|
value.type
|
|
@@ -537,11 +554,11 @@ function issuer(input) {
|
|
|
537
554
|
await auth.set(c, "authorization", 60 * 60 * 24, authorization);
|
|
538
555
|
c.set("authorization", authorization);
|
|
539
556
|
if (provider)
|
|
540
|
-
return c.redirect(
|
|
557
|
+
return c.redirect(`${getBasePath(c.req.raw)}/tenant/${tenantId}/${provider}/authorize`);
|
|
541
558
|
const resolvedProviders = await getProviders(c);
|
|
542
559
|
const providerNames = Object.keys(resolvedProviders);
|
|
543
560
|
if (providerNames.length === 1)
|
|
544
|
-
return c.redirect(
|
|
561
|
+
return c.redirect(`${getBasePath(c.req.raw)}/tenant/${tenantId}/${providerNames[0]}/authorize`);
|
|
545
562
|
return auth.forward(c, await select()(Object.fromEntries(Object.entries(resolvedProviders).map(([key, value]) => [
|
|
546
563
|
key,
|
|
547
564
|
value.type
|
package/dist/types/issuer.d.ts
CHANGED
|
@@ -109,6 +109,51 @@
|
|
|
109
109
|
* })
|
|
110
110
|
* ```
|
|
111
111
|
*
|
|
112
|
+
* #### Advanced multi-tenant configuration
|
|
113
|
+
*
|
|
114
|
+
* For complex multi-tenant scenarios, OpenAuth provides three additional configuration options:
|
|
115
|
+
*
|
|
116
|
+
* - **`basePath`** - Dynamic URL prefix when the issuer is mounted at a variable path
|
|
117
|
+
* - **`cookies.path`** - Set to `"/"` so cookies work across all paths
|
|
118
|
+
* - **`context`** - Extract custom data from requests, available in providers and callbacks
|
|
119
|
+
*
|
|
120
|
+
* ```ts title="issuer.ts"
|
|
121
|
+
* const app = issuer({
|
|
122
|
+
* storage,
|
|
123
|
+
* subjects,
|
|
124
|
+
* // Dynamic base path based on request headers
|
|
125
|
+
* basePath: (req) => `/auth/${req.headers.get("x-org-slug")}`,
|
|
126
|
+
* // Root-level cookies that work across all paths
|
|
127
|
+
* cookies: { path: "/" },
|
|
128
|
+
* // Extract org/app context from each request
|
|
129
|
+
* context: (req) => ({
|
|
130
|
+
* orgSlug: req.headers.get("x-org-slug")!,
|
|
131
|
+
* appSlug: req.headers.get("x-app-slug")!,
|
|
132
|
+
* }),
|
|
133
|
+
* providers: async (ctx) => {
|
|
134
|
+
* const { orgSlug, appSlug } = ctx.get("requestContext") as { orgSlug: string; appSlug: string }
|
|
135
|
+
* const config = await db.getAppConfig(orgSlug, appSlug)
|
|
136
|
+
* return {
|
|
137
|
+
* github: GithubProvider({
|
|
138
|
+
* clientID: config.githubClientId,
|
|
139
|
+
* clientSecret: config.githubClientSecret,
|
|
140
|
+
* })
|
|
141
|
+
* }
|
|
142
|
+
* },
|
|
143
|
+
* success: async (ctx, value) => {
|
|
144
|
+
* const { orgSlug, appSlug } = value.context
|
|
145
|
+
* return ctx.subject("user", {
|
|
146
|
+
* userID: value.email,
|
|
147
|
+
* orgSlug,
|
|
148
|
+
* appSlug,
|
|
149
|
+
* })
|
|
150
|
+
* },
|
|
151
|
+
* })
|
|
152
|
+
*
|
|
153
|
+
* // Mount the issuer at a dynamic route
|
|
154
|
+
* honoApp.route("/auth/:orgSlug/:appSlug", app)
|
|
155
|
+
* ```
|
|
156
|
+
*
|
|
112
157
|
* #### Deploy
|
|
113
158
|
*
|
|
114
159
|
* Since `issuer` is a Hono app, you can deploy it anywhere Hono supports.
|
|
@@ -215,9 +260,11 @@ export declare const aws: <E extends import("hono").Env = import("hono").Env, S
|
|
|
215
260
|
headers: Record<string, string>;
|
|
216
261
|
multiValueHeaders?: undefined;
|
|
217
262
|
})>);
|
|
218
|
-
export interface IssuerInput<Providers extends Record<string, Provider<any>>, Subjects extends SubjectSchema, Result = {
|
|
263
|
+
export interface IssuerInput<Providers extends Record<string, Provider<any>>, Subjects extends SubjectSchema, RequestContext = undefined, Result = {
|
|
219
264
|
[key in keyof Providers]: Prettify<{
|
|
220
265
|
provider: key;
|
|
266
|
+
tenantId?: string;
|
|
267
|
+
context: RequestContext;
|
|
221
268
|
} & (Providers[key] extends Provider<infer T> ? T : {})>;
|
|
222
269
|
}[keyof Providers]> {
|
|
223
270
|
/**
|
|
@@ -492,19 +539,56 @@ export interface IssuerInput<Providers extends Record<string, Provider<any>>, Su
|
|
|
492
539
|
* ```
|
|
493
540
|
*/
|
|
494
541
|
allow?(input: AllowCallbackInput, req: Request): Promise<boolean>;
|
|
542
|
+
/**
|
|
543
|
+
* Cookie configuration options.
|
|
544
|
+
*/
|
|
545
|
+
cookies?: {
|
|
546
|
+
/**
|
|
547
|
+
* Path for cookies. Defaults to the current path.
|
|
548
|
+
* Set to "/" for root-level cookies that work across all paths.
|
|
549
|
+
*/
|
|
550
|
+
path?: string;
|
|
551
|
+
};
|
|
552
|
+
/**
|
|
553
|
+
* Extract custom context from the request. This context is available
|
|
554
|
+
* in providers via `ctx.get("requestContext")` and in callbacks via `input.context`.
|
|
555
|
+
*
|
|
556
|
+
* @example
|
|
557
|
+
* ```ts
|
|
558
|
+
* context: (req) => ({
|
|
559
|
+
* orgSlug: req.header("x-org-slug")!,
|
|
560
|
+
* appSlug: req.header("x-app-slug")!,
|
|
561
|
+
* }),
|
|
562
|
+
* ```
|
|
563
|
+
*/
|
|
564
|
+
context?: (req: Request) => RequestContext;
|
|
565
|
+
/**
|
|
566
|
+
* Base path for all routes. Can be a static string or a function that
|
|
567
|
+
* receives the request and returns the path.
|
|
568
|
+
*
|
|
569
|
+
* @example
|
|
570
|
+
* ```ts
|
|
571
|
+
* basePath: "/auth/acme"
|
|
572
|
+
* // or
|
|
573
|
+
* basePath: (req) => `/auth/${req.headers.get("x-org-slug")}`
|
|
574
|
+
* ```
|
|
575
|
+
*/
|
|
576
|
+
basePath?: string | ((req: Request) => string);
|
|
495
577
|
}
|
|
496
578
|
/**
|
|
497
579
|
* Create an OpenAuth server, a Hono app.
|
|
498
580
|
*/
|
|
499
|
-
export declare function issuer<Providers extends Record<string, Provider<any>>, Subjects extends SubjectSchema, Result = {
|
|
581
|
+
export declare function issuer<Providers extends Record<string, Provider<any>>, Subjects extends SubjectSchema, RequestContext = undefined, Result = {
|
|
500
582
|
[key in keyof Providers]: Prettify<{
|
|
501
583
|
provider: key;
|
|
502
584
|
tenantId?: string;
|
|
585
|
+
context: RequestContext;
|
|
503
586
|
} & (Providers[key] extends Provider<infer T> ? T : {})>;
|
|
504
|
-
}[keyof Providers]>(input: IssuerInput<Providers, Subjects, Result>): import("hono/hono-base").HonoBase<{
|
|
587
|
+
}[keyof Providers]>(input: IssuerInput<Providers, Subjects, RequestContext, Result>): import("hono/hono-base").HonoBase<{
|
|
505
588
|
Variables: {
|
|
506
589
|
authorization: AuthorizationState;
|
|
507
590
|
tenantId: string;
|
|
591
|
+
requestContext: RequestContext;
|
|
508
592
|
};
|
|
509
593
|
}, import("hono/types").BlankSchema, "/", "*">;
|
|
510
594
|
//# sourceMappingURL=issuer.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"issuer.d.ts","sourceRoot":"","sources":["../../src/issuer.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"issuer.d.ts","sourceRoot":"","sources":["../../src/issuer.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4LG;AACH,OAAO,EAAE,QAAQ,EAAmB,MAAM,wBAAwB,CAAA;AAClE,OAAO,EAAE,cAAc,EAAE,aAAa,EAAE,MAAM,cAAc,CAAA;AAG5D,OAAO,EAAE,OAAO,EAAE,MAAM,MAAM,CAAA;AAI9B;;;;;;;;GAQG;AACH,MAAM,WAAW,kBAAkB,CACjC,CAAC,SAAS;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,UAAU,EAAE,GAAG,CAAA;CAAE;IAE3C;;;;;OAKG;IACH,OAAO,CAAC,IAAI,SAAS,CAAC,CAAC,MAAM,CAAC,EAC5B,IAAI,EAAE,IAAI,EACV,UAAU,EAAE,OAAO,CAAC,CAAC,EAAE;QAAE,IAAI,EAAE,IAAI,CAAA;KAAE,CAAC,CAAC,YAAY,CAAC,EACpD,IAAI,CAAC,EAAE;QACL,GAAG,CAAC,EAAE;YACJ,MAAM,CAAC,EAAE,MAAM,CAAA;YACf,OAAO,CAAC,EAAE,MAAM,CAAA;SACjB,CAAA;QACD,OAAO,CAAC,EAAE,MAAM,CAAA;KACjB,GACA,OAAO,CAAC,QAAQ,CAAC,CAAA;CACrB;AAED,MAAM,WAAW,kBAAkB;IACjC,QAAQ,EAAE,MAAM,CAAA;IAChB,WAAW,EAAE,MAAM,CAAA;IACnB,QAAQ,CAAC,EAAE,MAAM,CAAA;CAClB;AAED;;GAEG;AACH,MAAM,WAAW,kBAAkB;IACjC,YAAY,EAAE,MAAM,CAAA;IACpB,aAAa,EAAE,MAAM,CAAA;IACrB,KAAK,EAAE,MAAM,CAAA;IACb,SAAS,EAAE,MAAM,CAAA;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,IAAI,CAAC,EAAE;QACL,SAAS,EAAE,MAAM,CAAA;QACjB,MAAM,EAAE,MAAM,CAAA;KACf,CAAA;IACD,QAAQ,CAAC,EAAE,MAAM,CAAA;CAClB;AAED;;GAEG;AACH,MAAM,MAAM,QAAQ,CAAC,CAAC,IAAI;KACvB,CAAC,IAAI,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;CACrB,GAAG,EAAE,CAAA;AAEN,OAAO,EAIL,iBAAiB,EAClB,MAAM,YAAY,CAAA;AAEnB,OAAO,EAAW,cAAc,EAAE,MAAM,sBAAsB,CAAA;AAI9D,OAAO,EAAY,KAAK,EAAE,MAAM,eAAe,CAAA;AAc/C,gBAAgB;AAChB,eAAO,MAAM,GAAG;;gFAzIP,CAAA;;;;;;;;IAyImB,CAAA;AAE5B,MAAM,WAAW,WAAW,CAC1B,SAAS,SAAS,MAAM,CAAC,MAAM,EAAE,QAAQ,CAAC,GAAG,CAAC,CAAC,EAC/C,QAAQ,SAAS,aAAa,EAC9B,cAAc,GAAG,SAAS,EAC1B,MAAM,GAAG;KACN,GAAG,IAAI,MAAM,SAAS,GAAG,QAAQ,CAChC;QACE,QAAQ,EAAE,GAAG,CAAA;QACb,QAAQ,CAAC,EAAE,MAAM,CAAA;QACjB,OAAO,EAAE,cAAc,CAAA;KACxB,GAAG,CAAC,SAAS,CAAC,GAAG,CAAC,SAAS,QAAQ,CAAC,MAAM,CAAC,CAAC,GAAG,CAAC,GAAG,EAAE,CAAC,CACxD;CACF,CAAC,MAAM,SAAS,CAAC;IAElB;;;;;;;;;;;;;;;;;;OAkBG;IACH,QAAQ,EAAE,QAAQ,CAAA;IAClB;;;;;;;;;;;;OAYG;IACH,OAAO,CAAC,EAAE,cAAc,CAAA;IACxB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OA+CG;IACH,SAAS,EAAE,SAAS,GAAG,CAAC,CAAC,GAAG,EAAE,OAAO,KAAK,OAAO,CAAC,SAAS,CAAC,CAAC,CAAA;IAC7D;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OA8BG;IACH,KAAK,CAAC,EAAE,KAAK,CAAA;IACb;;;;;;;;;;;;OAYG;IACH,GAAG,CAAC,EAAE;QACJ;;;WAGG;QACH,MAAM,CAAC,EAAE,MAAM,CAAA;QACf;;;WAGG;QACH,OAAO,CAAC,EAAE,MAAM,CAAA;QAChB;;;;WAIG;QACH,KAAK,CAAC,EAAE,MAAM,CAAA;QACd;;;WAGG;QACH,SAAS,CAAC,EAAE,MAAM,CAAA;KACnB,CAAA;IACD;;;;;;;;;;;;;;;;;;;OAmBG;IACH,MAAM,CAAC,CAAC,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,EAAE,GAAG,EAAE,OAAO,GAAG,OAAO,CAAC,QAAQ,CAAC,CAAA;IAC3E;;OAEG;IACH,KAAK,CAAC,CAAC,GAAG,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IACnC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OA8BG;IACH,OAAO,CACL,QAAQ,EAAE,kBAAkB,CAAC,cAAc,CAAC,QAAQ,CAAC,CAAC,EACtD,KAAK,EAAE,MAAM,EACb,GAAG,EAAE,OAAO,GACX,OAAO,CAAC,QAAQ,CAAC,CAAA;IACpB;;;;;;;;;;;;;;;;;;;;;OAqBG;IACH,OAAO,CAAC,CACN,QAAQ,EAAE,kBAAkB,CAAC,cAAc,CAAC,QAAQ,CAAC,CAAC,EACtD,KAAK,EAAE;QACL,IAAI,EAAE,MAAM,CAAA;QACZ,UAAU,EAAE,GAAG,CAAA;QACf,OAAO,EAAE,MAAM,CAAA;QACf,QAAQ,EAAE,MAAM,CAAA;KACjB,EACD,GAAG,EAAE,OAAO,GACX,OAAO,CAAC,QAAQ,CAAC,CAAA;IACpB;;OAEG;IACH,KAAK,CAAC,CAAC,KAAK,EAAE,iBAAiB,EAAE,GAAG,EAAE,OAAO,GAAG,OAAO,CAAC,QAAQ,CAAC,CAAA;IACjE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OA8BG;IACH,KAAK,CAAC,CAAC,KAAK,EAAE,kBAAkB,EAAE,GAAG,EAAE,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAAA;IACjE;;OAEG;IACH,OAAO,CAAC,EAAE;QACR;;;WAGG;QACH,IAAI,CAAC,EAAE,MAAM,CAAA;KACd,CAAA;IACD;;;;;;;;;;;OAWG;IACH,OAAO,CAAC,EAAE,CAAC,GAAG,EAAE,OAAO,KAAK,cAAc,CAAA;IAC1C;;;;;;;;;;OAUG;IACH,QAAQ,CAAC,EAAE,MAAM,GAAG,CAAC,CAAC,GAAG,EAAE,OAAO,KAAK,MAAM,CAAC,CAAA;CAC/C;AAED;;GAEG;AACH,wBAAgB,MAAM,CACpB,SAAS,SAAS,MAAM,CAAC,MAAM,EAAE,QAAQ,CAAC,GAAG,CAAC,CAAC,EAC/C,QAAQ,SAAS,aAAa,EAC9B,cAAc,GAAG,SAAS,EAC1B,MAAM,GAAG;KACN,GAAG,IAAI,MAAM,SAAS,GAAG,QAAQ,CAChC;QACE,QAAQ,EAAE,GAAG,CAAA;QACb,QAAQ,CAAC,EAAE,MAAM,CAAA;QACjB,OAAO,EAAE,cAAc,CAAA;KACxB,GAAG,CAAC,SAAS,CAAC,GAAG,CAAC,SAAS,QAAQ,CAAC,MAAM,CAAC,CAAC,GAAG,CAAC,GAAG,EAAE,CAAC,CACxD;CACF,CAAC,MAAM,SAAS,CAAC,EAClB,KAAK,EAAE,WAAW,CAAC,SAAS,EAAE,QAAQ,EAAE,cAAc,EAAE,MAAM,CAAC;eAwSlD;QACT,aAAa,EAAE,kBAAkB,CAAA;QACjC,QAAQ,EAAE,MAAM,CAAA;QAChB,cAAc,EAAE,cAAc,CAAA;KAC/B;+CAgpBJ"}
|
package/package.json
CHANGED
package/src/issuer.ts
CHANGED
|
@@ -109,6 +109,51 @@
|
|
|
109
109
|
* })
|
|
110
110
|
* ```
|
|
111
111
|
*
|
|
112
|
+
* #### Advanced multi-tenant configuration
|
|
113
|
+
*
|
|
114
|
+
* For complex multi-tenant scenarios, OpenAuth provides three additional configuration options:
|
|
115
|
+
*
|
|
116
|
+
* - **`basePath`** - Dynamic URL prefix when the issuer is mounted at a variable path
|
|
117
|
+
* - **`cookies.path`** - Set to `"/"` so cookies work across all paths
|
|
118
|
+
* - **`context`** - Extract custom data from requests, available in providers and callbacks
|
|
119
|
+
*
|
|
120
|
+
* ```ts title="issuer.ts"
|
|
121
|
+
* const app = issuer({
|
|
122
|
+
* storage,
|
|
123
|
+
* subjects,
|
|
124
|
+
* // Dynamic base path based on request headers
|
|
125
|
+
* basePath: (req) => `/auth/${req.headers.get("x-org-slug")}`,
|
|
126
|
+
* // Root-level cookies that work across all paths
|
|
127
|
+
* cookies: { path: "/" },
|
|
128
|
+
* // Extract org/app context from each request
|
|
129
|
+
* context: (req) => ({
|
|
130
|
+
* orgSlug: req.headers.get("x-org-slug")!,
|
|
131
|
+
* appSlug: req.headers.get("x-app-slug")!,
|
|
132
|
+
* }),
|
|
133
|
+
* providers: async (ctx) => {
|
|
134
|
+
* const { orgSlug, appSlug } = ctx.get("requestContext") as { orgSlug: string; appSlug: string }
|
|
135
|
+
* const config = await db.getAppConfig(orgSlug, appSlug)
|
|
136
|
+
* return {
|
|
137
|
+
* github: GithubProvider({
|
|
138
|
+
* clientID: config.githubClientId,
|
|
139
|
+
* clientSecret: config.githubClientSecret,
|
|
140
|
+
* })
|
|
141
|
+
* }
|
|
142
|
+
* },
|
|
143
|
+
* success: async (ctx, value) => {
|
|
144
|
+
* const { orgSlug, appSlug } = value.context
|
|
145
|
+
* return ctx.subject("user", {
|
|
146
|
+
* userID: value.email,
|
|
147
|
+
* orgSlug,
|
|
148
|
+
* appSlug,
|
|
149
|
+
* })
|
|
150
|
+
* },
|
|
151
|
+
* })
|
|
152
|
+
*
|
|
153
|
+
* // Mount the issuer at a dynamic route
|
|
154
|
+
* honoApp.route("/auth/:orgSlug/:appSlug", app)
|
|
155
|
+
* ```
|
|
156
|
+
*
|
|
112
157
|
* #### Deploy
|
|
113
158
|
*
|
|
114
159
|
* Since `issuer` is a Hono app, you can deploy it anywhere Hono supports.
|
|
@@ -241,10 +286,13 @@ export const aws = awsHandle
|
|
|
241
286
|
export interface IssuerInput<
|
|
242
287
|
Providers extends Record<string, Provider<any>>,
|
|
243
288
|
Subjects extends SubjectSchema,
|
|
289
|
+
RequestContext = undefined,
|
|
244
290
|
Result = {
|
|
245
291
|
[key in keyof Providers]: Prettify<
|
|
246
292
|
{
|
|
247
293
|
provider: key
|
|
294
|
+
tenantId?: string
|
|
295
|
+
context: RequestContext
|
|
248
296
|
} & (Providers[key] extends Provider<infer T> ? T : {})
|
|
249
297
|
>
|
|
250
298
|
}[keyof Providers],
|
|
@@ -529,6 +577,41 @@ export interface IssuerInput<
|
|
|
529
577
|
* ```
|
|
530
578
|
*/
|
|
531
579
|
allow?(input: AllowCallbackInput, req: Request): Promise<boolean>
|
|
580
|
+
/**
|
|
581
|
+
* Cookie configuration options.
|
|
582
|
+
*/
|
|
583
|
+
cookies?: {
|
|
584
|
+
/**
|
|
585
|
+
* Path for cookies. Defaults to the current path.
|
|
586
|
+
* Set to "/" for root-level cookies that work across all paths.
|
|
587
|
+
*/
|
|
588
|
+
path?: string
|
|
589
|
+
}
|
|
590
|
+
/**
|
|
591
|
+
* Extract custom context from the request. This context is available
|
|
592
|
+
* in providers via `ctx.get("requestContext")` and in callbacks via `input.context`.
|
|
593
|
+
*
|
|
594
|
+
* @example
|
|
595
|
+
* ```ts
|
|
596
|
+
* context: (req) => ({
|
|
597
|
+
* orgSlug: req.header("x-org-slug")!,
|
|
598
|
+
* appSlug: req.header("x-app-slug")!,
|
|
599
|
+
* }),
|
|
600
|
+
* ```
|
|
601
|
+
*/
|
|
602
|
+
context?: (req: Request) => RequestContext
|
|
603
|
+
/**
|
|
604
|
+
* Base path for all routes. Can be a static string or a function that
|
|
605
|
+
* receives the request and returns the path.
|
|
606
|
+
*
|
|
607
|
+
* @example
|
|
608
|
+
* ```ts
|
|
609
|
+
* basePath: "/auth/acme"
|
|
610
|
+
* // or
|
|
611
|
+
* basePath: (req) => `/auth/${req.headers.get("x-org-slug")}`
|
|
612
|
+
* ```
|
|
613
|
+
*/
|
|
614
|
+
basePath?: string | ((req: Request) => string)
|
|
532
615
|
}
|
|
533
616
|
|
|
534
617
|
/**
|
|
@@ -537,15 +620,17 @@ export interface IssuerInput<
|
|
|
537
620
|
export function issuer<
|
|
538
621
|
Providers extends Record<string, Provider<any>>,
|
|
539
622
|
Subjects extends SubjectSchema,
|
|
623
|
+
RequestContext = undefined,
|
|
540
624
|
Result = {
|
|
541
625
|
[key in keyof Providers]: Prettify<
|
|
542
626
|
{
|
|
543
627
|
provider: key
|
|
544
628
|
tenantId?: string
|
|
629
|
+
context: RequestContext
|
|
545
630
|
} & (Providers[key] extends Provider<infer T> ? T : {})
|
|
546
631
|
>
|
|
547
632
|
}[keyof Providers],
|
|
548
|
-
>(input: IssuerInput<Providers, Subjects, Result>) {
|
|
633
|
+
>(input: IssuerInput<Providers, Subjects, RequestContext, Result>) {
|
|
549
634
|
const error =
|
|
550
635
|
input.error ??
|
|
551
636
|
function (err) {
|
|
@@ -605,6 +690,12 @@ export function issuer<
|
|
|
605
690
|
const signingKey = lazy(() => allSigning().then((all) => all[0]))
|
|
606
691
|
const encryptionKey = lazy(() => allEncryption().then((all) => all[0]))
|
|
607
692
|
|
|
693
|
+
const getBasePath = (req: Request) => {
|
|
694
|
+
if (!input.basePath) return ""
|
|
695
|
+
if (typeof input.basePath === "string") return input.basePath
|
|
696
|
+
return input.basePath(req)
|
|
697
|
+
}
|
|
698
|
+
|
|
608
699
|
const auth: Omit<ProviderOptions<any>, "name"> = {
|
|
609
700
|
async success(ctx: Context, properties: any, successOpts) {
|
|
610
701
|
const authorization = await getAuthorization(ctx)
|
|
@@ -681,6 +772,7 @@ export function issuer<
|
|
|
681
772
|
{
|
|
682
773
|
provider: ctx.get("provider"),
|
|
683
774
|
tenantId: authorization.tenantId,
|
|
775
|
+
context: ctx.get("requestContext"),
|
|
684
776
|
...properties,
|
|
685
777
|
},
|
|
686
778
|
ctx.req.raw,
|
|
@@ -697,6 +789,7 @@ export function issuer<
|
|
|
697
789
|
setCookie(ctx, key, await encrypt(value), {
|
|
698
790
|
maxAge,
|
|
699
791
|
httpOnly: true,
|
|
792
|
+
path: input.cookies?.path,
|
|
700
793
|
...(ctx.req.url.startsWith("https://")
|
|
701
794
|
? { secure: true, sameSite: "None" }
|
|
702
795
|
: {}),
|
|
@@ -710,7 +803,7 @@ export function issuer<
|
|
|
710
803
|
})
|
|
711
804
|
},
|
|
712
805
|
async unset(ctx: Context, key: string) {
|
|
713
|
-
deleteCookie(ctx, key)
|
|
806
|
+
deleteCookie(ctx, key, { path: input.cookies?.path })
|
|
714
807
|
},
|
|
715
808
|
async invalidate(subject: string) {
|
|
716
809
|
// Resolve the scan in case modifications interfere with iteration
|
|
@@ -828,16 +921,26 @@ export function issuer<
|
|
|
828
921
|
}
|
|
829
922
|
|
|
830
923
|
function issuer(ctx: Context) {
|
|
831
|
-
|
|
924
|
+
const base = getBasePath(ctx.req.raw)
|
|
925
|
+
return new URL(getRelativeUrl(ctx, base || "/")).origin + base
|
|
832
926
|
}
|
|
833
927
|
|
|
834
928
|
const app = new Hono<{
|
|
835
929
|
Variables: {
|
|
836
930
|
authorization: AuthorizationState
|
|
837
931
|
tenantId: string
|
|
932
|
+
requestContext: RequestContext
|
|
838
933
|
}
|
|
839
934
|
}>().use(logger())
|
|
840
935
|
|
|
936
|
+
// Extract custom context from request if configured
|
|
937
|
+
app.use(async (c, next) => {
|
|
938
|
+
if (input.context) {
|
|
939
|
+
c.set("requestContext", input.context(c.req.raw))
|
|
940
|
+
}
|
|
941
|
+
await next()
|
|
942
|
+
})
|
|
943
|
+
|
|
841
944
|
const getProviders = async (c: Context): Promise<Providers> => {
|
|
842
945
|
if (typeof input.providers === "function") {
|
|
843
946
|
return input.providers(c)
|
|
@@ -1169,6 +1272,7 @@ export function issuer<
|
|
|
1169
1272
|
},
|
|
1170
1273
|
{
|
|
1171
1274
|
provider: provider.toString(),
|
|
1275
|
+
context: c.get("requestContext"),
|
|
1172
1276
|
...response,
|
|
1173
1277
|
},
|
|
1174
1278
|
c.req.raw,
|
|
@@ -1232,11 +1336,14 @@ export function issuer<
|
|
|
1232
1336
|
throw new UnauthorizedClientError(client_id, redirect_uri)
|
|
1233
1337
|
await auth.set(c, "authorization", 60 * 60 * 24, authorization)
|
|
1234
1338
|
c.set("authorization", authorization)
|
|
1235
|
-
if (provider)
|
|
1339
|
+
if (provider)
|
|
1340
|
+
return c.redirect(`${getBasePath(c.req.raw)}/${provider}/authorize`)
|
|
1236
1341
|
const resolvedProviders = await getProviders(c)
|
|
1237
1342
|
const providerNames = Object.keys(resolvedProviders)
|
|
1238
1343
|
if (providerNames.length === 1)
|
|
1239
|
-
return c.redirect(
|
|
1344
|
+
return c.redirect(
|
|
1345
|
+
`${getBasePath(c.req.raw)}/${providerNames[0]}/authorize`,
|
|
1346
|
+
)
|
|
1240
1347
|
return auth.forward(
|
|
1241
1348
|
c,
|
|
1242
1349
|
await select()(
|
|
@@ -1313,11 +1420,16 @@ export function issuer<
|
|
|
1313
1420
|
throw new UnauthorizedClientError(client_id, redirect_uri)
|
|
1314
1421
|
await auth.set(c, "authorization", 60 * 60 * 24, authorization)
|
|
1315
1422
|
c.set("authorization", authorization)
|
|
1316
|
-
if (provider)
|
|
1423
|
+
if (provider)
|
|
1424
|
+
return c.redirect(
|
|
1425
|
+
`${getBasePath(c.req.raw)}/tenant/${tenantId}/${provider}/authorize`,
|
|
1426
|
+
)
|
|
1317
1427
|
const resolvedProviders = await getProviders(c)
|
|
1318
1428
|
const providerNames = Object.keys(resolvedProviders)
|
|
1319
1429
|
if (providerNames.length === 1)
|
|
1320
|
-
return c.redirect(
|
|
1430
|
+
return c.redirect(
|
|
1431
|
+
`${getBasePath(c.req.raw)}/tenant/${tenantId}/${providerNames[0]}/authorize`,
|
|
1432
|
+
)
|
|
1321
1433
|
return auth.forward(
|
|
1322
1434
|
c,
|
|
1323
1435
|
await select()(
|