@revojs/auth 0.2.3 → 0.2.5
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/index.d.ts +8 -3
- package/dist/index.js +73 -26
- package/dist/module/index.d.ts +3 -0
- package/dist/routes/auth/provider/apple/get.js +1 -0
- package/dist/routes/auth/provider/github/get.js +1 -0
- package/dist/routes/auth/provider/google/get.js +1 -0
- package/dist/routes/auth/provider/microsoft/get.js +1 -0
- package/dist/routes/auth/sessions/post.js +4 -3
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { type Route } from "@revojs/server";
|
|
2
2
|
import { type Scope } from "revojs";
|
|
3
|
-
import type
|
|
3
|
+
import { type Awaitable } from "revojs/shared";
|
|
4
4
|
import { type InferInput } from "valibot";
|
|
5
5
|
export type Principal = InferInput<typeof principalSchema>;
|
|
6
6
|
export type PasswordMethod = InferInput<typeof passwordMethodSchema>;
|
|
@@ -39,6 +39,7 @@ export interface ProviderUserInfo {
|
|
|
39
39
|
export interface AuthProviderOptions {
|
|
40
40
|
issuer: string;
|
|
41
41
|
scope: string;
|
|
42
|
+
pkce: boolean;
|
|
42
43
|
clientKey: string;
|
|
43
44
|
clientSecret: string;
|
|
44
45
|
authorize: ProviderEndpoint;
|
|
@@ -60,16 +61,18 @@ export declare function getSession<T>(scope: Scope): Promise<T | undefined>;
|
|
|
60
61
|
export declare function setSession<T>(scope: Scope, session: T): Promise<void>;
|
|
61
62
|
export declare function clearSession(scope: Scope): void;
|
|
62
63
|
export declare function defineAuthProvider(provider: AuthProvider): Route;
|
|
64
|
+
export declare const AUTHORIZE_HOOK: import("revojs").Descriptor<(method: Method) => Awaitable<boolean>>;
|
|
63
65
|
export declare const GET_PRINCIPAL_HOOK: import("revojs").Descriptor<(method: Method) => Awaitable<Principal | undefined>>;
|
|
64
66
|
export declare const CREATE_PRINCIPAL_HOOK: import("revojs").Descriptor<(method: Method) => Awaitable<Principal | undefined>>;
|
|
65
67
|
export declare const CREATE_SESSION_HOOK: import("revojs").Descriptor<(principal: Principal) => Awaitable<unknown>>;
|
|
66
68
|
export declare const DELETE_SESSION_HOOK: import("revojs").Descriptor<(session: any) => Awaitable<unknown>>;
|
|
67
|
-
export declare const registerAuthHooks: (scope: Scope, hooks: import("revojs").Hooks<{
|
|
69
|
+
export declare const registerAuthHooks: (scope: Scope, hooks: Partial<import("revojs").Hooks<{
|
|
70
|
+
authorize: import("revojs").Descriptor<(method: Method) => Awaitable<boolean>>;
|
|
68
71
|
getPrincipal: import("revojs").Descriptor<(method: Method) => Awaitable<Principal | undefined>>;
|
|
69
72
|
createPrincipal: import("revojs").Descriptor<(method: Method) => Awaitable<Principal | undefined>>;
|
|
70
73
|
createSession: import("revojs").Descriptor<(principal: Principal) => Awaitable<unknown>>;
|
|
71
74
|
deleteSession: import("revojs").Descriptor<(session: any) => Awaitable<unknown>>;
|
|
72
|
-
}
|
|
75
|
+
}>>) => void;
|
|
73
76
|
export declare const principalSchema: import("valibot").ObjectSchema<{
|
|
74
77
|
readonly sub: import("valibot").StringSchema<undefined>;
|
|
75
78
|
readonly email: import("valibot").OptionalSchema<import("valibot").StringSchema<undefined>, undefined>;
|
|
@@ -91,6 +94,7 @@ export declare const passkeyMethodSchema: import("valibot").ObjectSchema<{
|
|
|
91
94
|
export declare const providerMethodSchema: import("valibot").ObjectSchema<{
|
|
92
95
|
readonly type: import("valibot").LiteralSchema<"PROVIDER", undefined>;
|
|
93
96
|
readonly issuer: import("valibot").StringSchema<undefined>;
|
|
97
|
+
readonly accessToken: import("valibot").StringSchema<undefined>;
|
|
94
98
|
readonly principal: import("valibot").ObjectSchema<{
|
|
95
99
|
readonly sub: import("valibot").StringSchema<undefined>;
|
|
96
100
|
readonly email: import("valibot").OptionalSchema<import("valibot").StringSchema<undefined>, undefined>;
|
|
@@ -111,6 +115,7 @@ export declare const methodSchema: import("valibot").UnionSchema<[import("valibo
|
|
|
111
115
|
}, undefined>, import("valibot").ObjectSchema<{
|
|
112
116
|
readonly type: import("valibot").LiteralSchema<"PROVIDER", undefined>;
|
|
113
117
|
readonly issuer: import("valibot").StringSchema<undefined>;
|
|
118
|
+
readonly accessToken: import("valibot").StringSchema<undefined>;
|
|
114
119
|
readonly principal: import("valibot").ObjectSchema<{
|
|
115
120
|
readonly sub: import("valibot").StringSchema<undefined>;
|
|
116
121
|
readonly email: import("valibot").OptionalSchema<import("valibot").StringSchema<undefined>, undefined>;
|
package/dist/index.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
import
|
|
1
|
+
import config from "#config/auth";
|
|
2
2
|
import { isClient, isServer } from "#variables";
|
|
3
|
-
import { deleteCookie, fetch, sendRedirect, setCookie, useCookies, useHeaders, useQuery, useUrl, withQuery } from "@revojs/server";
|
|
4
|
-
import { createRegisterHooks, defineHook, dispatchHookSingle, useVariables } from "revojs";
|
|
3
|
+
import { deleteCookie, fetch, sendForbidden, sendRedirect, setCookie, useCookies, useHeaders, useQuery, useUrl, withQuery } from "@revojs/server";
|
|
4
|
+
import { createRegisterHooks, defineHook, dispatchHook, dispatchHookSingle, useVariables } from "revojs";
|
|
5
|
+
import { mergeObjects } from "revojs/shared";
|
|
5
6
|
import { boolean, literal, nullish, number, object, optional, parse, pipe, string, transform, union } from "valibot";
|
|
6
7
|
//#region src/index.ts
|
|
7
8
|
function useAuth(scope) {
|
|
@@ -48,7 +49,7 @@ async function getCryptoKey(scope) {
|
|
|
48
49
|
}, false, ["sign", "verify"]);
|
|
49
50
|
}
|
|
50
51
|
async function getSession(scope) {
|
|
51
|
-
const session = useCookies(scope)[
|
|
52
|
+
const session = useCookies(scope)[config.session.name];
|
|
52
53
|
if (session) {
|
|
53
54
|
const index = session.lastIndexOf(".");
|
|
54
55
|
if (index > 0) try {
|
|
@@ -60,67 +61,107 @@ async function getSession(scope) {
|
|
|
60
61
|
async function setSession(scope, session) {
|
|
61
62
|
const value = encoder.encode(JSON.stringify(session));
|
|
62
63
|
const cookie = value.toBase64(BASE64_SIGN) + "." + new Uint8Array(await crypto.subtle.sign("HMAC", await getCryptoKey(scope), value)).toBase64(BASE64_SIGN);
|
|
63
|
-
setCookie(scope,
|
|
64
|
+
setCookie(scope, config.session.name, cookie, config.session.cookie);
|
|
64
65
|
}
|
|
65
66
|
function clearSession(scope) {
|
|
66
|
-
deleteCookie(scope,
|
|
67
|
+
deleteCookie(scope, config.session.name, config.session.cookie);
|
|
67
68
|
}
|
|
68
69
|
function defineAuthProvider(provider) {
|
|
69
70
|
return { async fetch(scope) {
|
|
70
|
-
const
|
|
71
|
-
const
|
|
72
|
-
const {
|
|
73
|
-
const
|
|
71
|
+
const defaults = provider.setup(scope);
|
|
72
|
+
const options = mergeObjects(config.providers?.[defaults.issuer] ?? {}, defaults);
|
|
73
|
+
const { origin, protocol } = useUrl(scope);
|
|
74
|
+
const { code, state, redirect, error } = useQuery(scope);
|
|
75
|
+
const secure = protocol === "https:";
|
|
76
|
+
const cookieName = `${secure ? "__Host-revojs-oauth" : "revojs-oauth"}-${options.issuer}`;
|
|
74
77
|
const redirectUri = `${origin}/auth/provider/${options.issuer}`;
|
|
75
|
-
|
|
76
|
-
|
|
78
|
+
if (code || state || error) {
|
|
79
|
+
const cookie = useCookies(scope)[cookieName];
|
|
80
|
+
deleteCookie(scope, cookieName, {
|
|
81
|
+
path: "/",
|
|
82
|
+
secure
|
|
83
|
+
});
|
|
84
|
+
let flow;
|
|
85
|
+
try {
|
|
86
|
+
flow = parse(oauthFlowSchema, JSON.parse(cookie));
|
|
87
|
+
} catch {
|
|
88
|
+
return sendForbidden(scope);
|
|
89
|
+
}
|
|
90
|
+
if (error || !code || !state || state !== flow.state || !flow.redirect.startsWith("/") || !URL.canParse(flow.redirect, origin) || new URL(flow.redirect, origin).origin !== origin || options.pkce && !flow.verifier) return sendForbidden(scope);
|
|
91
|
+
const body = new URLSearchParams({
|
|
92
|
+
grant_type: "authorization_code",
|
|
93
|
+
client_id: options.clientKey,
|
|
94
|
+
client_secret: options.clientSecret,
|
|
95
|
+
redirect_uri: redirectUri,
|
|
96
|
+
code,
|
|
97
|
+
...options.token.parameters
|
|
98
|
+
});
|
|
99
|
+
if (options.pkce) body.set("code_verifier", flow.verifier);
|
|
77
100
|
const token = await fetch(scope, options.token.url, {
|
|
78
101
|
method: "POST",
|
|
79
102
|
headers: {
|
|
80
103
|
"Content-Type": "application/x-www-form-urlencoded",
|
|
81
104
|
...options.token.headers
|
|
82
105
|
},
|
|
83
|
-
body
|
|
84
|
-
grant_type: "authorization_code",
|
|
85
|
-
client_id: options.clientKey,
|
|
86
|
-
client_secret: options.clientSecret,
|
|
87
|
-
redirect_uri: redirectUri,
|
|
88
|
-
code,
|
|
89
|
-
...options.token.parameters
|
|
90
|
-
})
|
|
106
|
+
body
|
|
91
107
|
});
|
|
92
108
|
const method = {
|
|
93
109
|
type: "PROVIDER",
|
|
94
110
|
issuer: options.issuer,
|
|
111
|
+
accessToken: token.access_token,
|
|
95
112
|
principal: (options.toPrincipal ?? ((user) => parse(userInfoSchema, user)))(await fetch(scope, options.userInfo.url, { headers: {
|
|
96
113
|
Authorization: `Bearer ${token.access_token}`,
|
|
97
114
|
...options.userInfo.headers
|
|
98
115
|
} }))
|
|
99
116
|
};
|
|
117
|
+
if ((await dispatchHook(scope, AUTHORIZE_HOOK, method)).includes(false)) return sendForbidden(scope);
|
|
100
118
|
const principal = await dispatchHookSingle(scope, GET_PRINCIPAL_HOOK, method) ?? await dispatchHookSingle(scope, CREATE_PRINCIPAL_HOOK, method);
|
|
101
119
|
if (principal) {
|
|
102
120
|
const session = await dispatchHookSingle(scope, CREATE_SESSION_HOOK, principal);
|
|
103
121
|
if (session) {
|
|
104
122
|
await setSession(scope, session);
|
|
105
|
-
return sendRedirect(scope,
|
|
123
|
+
return sendRedirect(scope, flow.redirect);
|
|
106
124
|
}
|
|
107
125
|
}
|
|
126
|
+
return sendForbidden(scope);
|
|
108
127
|
}
|
|
109
|
-
|
|
128
|
+
const verifier = options.pkce ? crypto.getRandomValues(/* @__PURE__ */ new Uint8Array(32)).toBase64(BASE64_SIGN) : "";
|
|
129
|
+
const redirectUrl = redirect && URL.canParse(redirect, origin) ? new URL(redirect, origin) : null;
|
|
130
|
+
const flow = {
|
|
131
|
+
state: crypto.randomUUID(),
|
|
132
|
+
redirect: redirect?.startsWith("/") && redirectUrl?.origin === origin ? redirect : "/",
|
|
133
|
+
verifier
|
|
134
|
+
};
|
|
135
|
+
const parameters = new URLSearchParams({
|
|
110
136
|
response_type: "code",
|
|
111
137
|
client_id: options.clientKey,
|
|
112
138
|
redirect_uri: redirectUri,
|
|
113
139
|
scope: options.scope,
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
})
|
|
140
|
+
...options.authorize.parameters,
|
|
141
|
+
state: flow.state
|
|
142
|
+
});
|
|
143
|
+
if (verifier) {
|
|
144
|
+
const digest = await crypto.subtle.digest("SHA-256", encoder.encode(verifier));
|
|
145
|
+
parameters.set("code_challenge", new Uint8Array(digest).toBase64(BASE64_SIGN));
|
|
146
|
+
parameters.set("code_challenge_method", "S256");
|
|
147
|
+
}
|
|
148
|
+
setCookie(scope, cookieName, JSON.stringify(flow), {
|
|
149
|
+
path: "/",
|
|
150
|
+
secure,
|
|
151
|
+
httpOnly: true,
|
|
152
|
+
sameSite: "Lax",
|
|
153
|
+
maxAge: 600
|
|
154
|
+
});
|
|
155
|
+
return sendRedirect(scope, withQuery(scope, options.authorize.url, parameters));
|
|
117
156
|
} };
|
|
118
157
|
}
|
|
158
|
+
var AUTHORIZE_HOOK = defineHook("AUTHORIZE_HOOK");
|
|
119
159
|
var GET_PRINCIPAL_HOOK = defineHook("GET_PRINCIPAL_HOOK");
|
|
120
160
|
var CREATE_PRINCIPAL_HOOK = defineHook("CREATE_PRINCIPAL_HOOK");
|
|
121
161
|
var CREATE_SESSION_HOOK = defineHook("CREATE_SESSION_HOOK");
|
|
122
162
|
var DELETE_SESSION_HOOK = defineHook("DELETE_SESSION_HOOK");
|
|
123
163
|
var registerAuthHooks = createRegisterHooks({
|
|
164
|
+
authorize: AUTHORIZE_HOOK,
|
|
124
165
|
getPrincipal: GET_PRINCIPAL_HOOK,
|
|
125
166
|
createPrincipal: CREATE_PRINCIPAL_HOOK,
|
|
126
167
|
createSession: CREATE_SESSION_HOOK,
|
|
@@ -145,6 +186,7 @@ var passkeyMethodSchema = object({ type: literal("PASSKEY") });
|
|
|
145
186
|
var providerMethodSchema = object({
|
|
146
187
|
type: literal("PROVIDER"),
|
|
147
188
|
issuer: string(),
|
|
189
|
+
accessToken: string(),
|
|
148
190
|
principal: principalSchema
|
|
149
191
|
});
|
|
150
192
|
var methodSchema = union([
|
|
@@ -155,6 +197,11 @@ var methodSchema = union([
|
|
|
155
197
|
var nullableStringSchema = nullish(string());
|
|
156
198
|
var nullableIdentifierSchema = nullish(union([string(), number()]));
|
|
157
199
|
var nullableBooleanSchema = nullish(boolean());
|
|
200
|
+
var oauthFlowSchema = object({
|
|
201
|
+
state: string(),
|
|
202
|
+
redirect: string(),
|
|
203
|
+
verifier: string()
|
|
204
|
+
});
|
|
158
205
|
var userInfoSchema = pipe(object({
|
|
159
206
|
sub: nullableIdentifierSchema,
|
|
160
207
|
id: nullableIdentifierSchema,
|
|
@@ -187,4 +234,4 @@ var BASE64_VERIFY = { alphabet: "base64url" };
|
|
|
187
234
|
var encoder = new TextEncoder();
|
|
188
235
|
var decoder = new TextDecoder();
|
|
189
236
|
//#endregion
|
|
190
|
-
export { CREATE_PRINCIPAL_HOOK, CREATE_SESSION_HOOK, DELETE_SESSION_HOOK, GET_PRINCIPAL_HOOK, clearSession, defineAuthProvider, getCryptoKey, getSession, methodSchema, passkeyMethodSchema, passwordMethodSchema, principalSchema, providerMethodSchema, registerAuthHooks, requireSession, setSession, useAuth };
|
|
237
|
+
export { AUTHORIZE_HOOK, CREATE_PRINCIPAL_HOOK, CREATE_SESSION_HOOK, DELETE_SESSION_HOOK, GET_PRINCIPAL_HOOK, clearSession, defineAuthProvider, getCryptoKey, getSession, methodSchema, passkeyMethodSchema, passwordMethodSchema, principalSchema, providerMethodSchema, registerAuthHooks, requireSession, setSession, useAuth };
|
package/dist/module/index.d.ts
CHANGED
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
import type { CookieOptions } from "@revojs/server";
|
|
2
|
+
import type { AuthProviderOptions, Providers } from "@revojs/auth";
|
|
3
|
+
import type { Mergeable } from "revojs/shared";
|
|
2
4
|
export interface SessionOptions {
|
|
3
5
|
name: string;
|
|
4
6
|
cookie: CookieOptions;
|
|
5
7
|
}
|
|
6
8
|
export interface AuthConfig {
|
|
7
9
|
session: SessionOptions;
|
|
10
|
+
providers?: Partial<Record<Providers | (string & {}), Mergeable<Pick<AuthProviderOptions, "scope" | "pkce" | "authorize" | "token" | "userInfo">>>>;
|
|
8
11
|
}
|
|
9
12
|
declare const _default: import("revojs/module").Module<"auth", AuthConfig, []>;
|
|
10
13
|
export default _default;
|
|
@@ -8,6 +8,7 @@ var get_default = defineAuthProvider({ setup(scope) {
|
|
|
8
8
|
return {
|
|
9
9
|
issuer: "github",
|
|
10
10
|
scope: "read:user user:email",
|
|
11
|
+
pkce: true,
|
|
11
12
|
clientKey: githubClientKey,
|
|
12
13
|
clientSecret: githubClientSecret,
|
|
13
14
|
authorize: { url: "https://github.com/login/oauth/authorize" },
|
|
@@ -8,6 +8,7 @@ var get_default = defineAuthProvider({ setup(scope) {
|
|
|
8
8
|
return {
|
|
9
9
|
issuer: "microsoft",
|
|
10
10
|
scope: "openid email profile",
|
|
11
|
+
pkce: true,
|
|
11
12
|
clientKey: microsoftClientKey,
|
|
12
13
|
clientSecret: microsoftClientSecret,
|
|
13
14
|
authorize: { url: `https://login.microsoftonline.com/${microsoftTenant ?? "common"}/oauth2/v2.0/authorize` },
|
|
@@ -1,10 +1,11 @@
|
|
|
1
|
-
import { CREATE_PRINCIPAL_HOOK, CREATE_SESSION_HOOK, GET_PRINCIPAL_HOOK, passkeyMethodSchema, passwordMethodSchema, setSession } from "../../../index.js";
|
|
2
|
-
import { defineRoute, sendNotFound, sendOk, useBody } from "@revojs/server";
|
|
3
|
-
import { dispatchHookSingle } from "revojs";
|
|
1
|
+
import { AUTHORIZE_HOOK, CREATE_PRINCIPAL_HOOK, CREATE_SESSION_HOOK, GET_PRINCIPAL_HOOK, passkeyMethodSchema, passwordMethodSchema, setSession } from "../../../index.js";
|
|
2
|
+
import { defineRoute, sendForbidden, sendNotFound, sendOk, useBody } from "@revojs/server";
|
|
3
|
+
import { dispatchHook, dispatchHookSingle } from "revojs";
|
|
4
4
|
import { union } from "valibot";
|
|
5
5
|
//#region src/routes/auth/sessions/post.ts
|
|
6
6
|
var post_default = defineRoute({ async fetch(scope) {
|
|
7
7
|
const method = await useBody(scope, union([passwordMethodSchema, passkeyMethodSchema]));
|
|
8
|
+
if ((await dispatchHook(scope, AUTHORIZE_HOOK, method)).includes(false)) return sendForbidden(scope);
|
|
8
9
|
const principal = await dispatchHookSingle(scope, GET_PRINCIPAL_HOOK, method) ?? (method.type !== "PASSWORD" || method.register ? await dispatchHookSingle(scope, CREATE_PRINCIPAL_HOOK, method) : void 0);
|
|
9
10
|
if (principal) {
|
|
10
11
|
const session = await dispatchHookSingle(scope, CREATE_SESSION_HOOK, principal);
|