@_mustachio/openauth 0.6.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/client.js +186 -0
- package/dist/esm/css.d.js +0 -0
- package/dist/esm/error.js +73 -0
- package/dist/esm/index.js +14 -0
- package/dist/esm/issuer.js +558 -0
- package/dist/esm/jwt.js +16 -0
- package/dist/esm/keys.js +113 -0
- package/dist/esm/pkce.js +35 -0
- package/dist/esm/provider/apple.js +28 -0
- package/dist/esm/provider/arctic.js +43 -0
- package/dist/esm/provider/code.js +58 -0
- package/dist/esm/provider/cognito.js +16 -0
- package/dist/esm/provider/discord.js +15 -0
- package/dist/esm/provider/facebook.js +24 -0
- package/dist/esm/provider/github.js +15 -0
- package/dist/esm/provider/google.js +25 -0
- package/dist/esm/provider/index.js +3 -0
- package/dist/esm/provider/jumpcloud.js +15 -0
- package/dist/esm/provider/keycloak.js +15 -0
- package/dist/esm/provider/linkedin.js +15 -0
- package/dist/esm/provider/m2m.js +17 -0
- package/dist/esm/provider/microsoft.js +24 -0
- package/dist/esm/provider/oauth2.js +119 -0
- package/dist/esm/provider/oidc.js +69 -0
- package/dist/esm/provider/passkey.js +315 -0
- package/dist/esm/provider/password.js +306 -0
- package/dist/esm/provider/provider.js +10 -0
- package/dist/esm/provider/slack.js +15 -0
- package/dist/esm/provider/spotify.js +15 -0
- package/dist/esm/provider/twitch.js +15 -0
- package/dist/esm/provider/x.js +16 -0
- package/dist/esm/provider/yahoo.js +15 -0
- package/dist/esm/random.js +27 -0
- package/dist/esm/storage/aws.js +39 -0
- package/dist/esm/storage/cloudflare.js +42 -0
- package/dist/esm/storage/dynamo.js +116 -0
- package/dist/esm/storage/memory.js +88 -0
- package/dist/esm/storage/storage.js +36 -0
- package/dist/esm/subject.js +7 -0
- package/dist/esm/ui/base.js +407 -0
- package/dist/esm/ui/code.js +151 -0
- package/dist/esm/ui/form.js +43 -0
- package/dist/esm/ui/icon.js +92 -0
- package/dist/esm/ui/passkey.js +329 -0
- package/dist/esm/ui/password.js +338 -0
- package/dist/esm/ui/select.js +187 -0
- package/dist/esm/ui/theme.js +115 -0
- package/dist/esm/util.js +54 -0
- package/dist/types/client.d.ts +466 -0
- package/dist/types/client.d.ts.map +1 -0
- package/dist/types/error.d.ts +77 -0
- package/dist/types/error.d.ts.map +1 -0
- package/dist/types/index.d.ts +20 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/issuer.d.ts +465 -0
- package/dist/types/issuer.d.ts.map +1 -0
- package/dist/types/jwt.d.ts +6 -0
- package/dist/types/jwt.d.ts.map +1 -0
- package/dist/types/keys.d.ts +18 -0
- package/dist/types/keys.d.ts.map +1 -0
- package/dist/types/pkce.d.ts +7 -0
- package/dist/types/pkce.d.ts.map +1 -0
- package/dist/types/provider/apple.d.ts +108 -0
- package/dist/types/provider/apple.d.ts.map +1 -0
- package/dist/types/provider/arctic.d.ts +16 -0
- package/dist/types/provider/arctic.d.ts.map +1 -0
- package/dist/types/provider/code.d.ts +74 -0
- package/dist/types/provider/code.d.ts.map +1 -0
- package/dist/types/provider/cognito.d.ts +64 -0
- package/dist/types/provider/cognito.d.ts.map +1 -0
- package/dist/types/provider/discord.d.ts +38 -0
- package/dist/types/provider/discord.d.ts.map +1 -0
- package/dist/types/provider/facebook.d.ts +74 -0
- package/dist/types/provider/facebook.d.ts.map +1 -0
- package/dist/types/provider/github.d.ts +38 -0
- package/dist/types/provider/github.d.ts.map +1 -0
- package/dist/types/provider/google.d.ts +74 -0
- package/dist/types/provider/google.d.ts.map +1 -0
- package/dist/types/provider/index.d.ts +4 -0
- package/dist/types/provider/index.d.ts.map +1 -0
- package/dist/types/provider/jumpcloud.d.ts +38 -0
- package/dist/types/provider/jumpcloud.d.ts.map +1 -0
- package/dist/types/provider/keycloak.d.ts +67 -0
- package/dist/types/provider/keycloak.d.ts.map +1 -0
- package/dist/types/provider/linkedin.d.ts +6 -0
- package/dist/types/provider/linkedin.d.ts.map +1 -0
- package/dist/types/provider/m2m.d.ts +34 -0
- package/dist/types/provider/m2m.d.ts.map +1 -0
- package/dist/types/provider/microsoft.d.ts +89 -0
- package/dist/types/provider/microsoft.d.ts.map +1 -0
- package/dist/types/provider/oauth2.d.ts +133 -0
- package/dist/types/provider/oauth2.d.ts.map +1 -0
- package/dist/types/provider/oidc.d.ts +91 -0
- package/dist/types/provider/oidc.d.ts.map +1 -0
- package/dist/types/provider/passkey.d.ts +143 -0
- package/dist/types/provider/passkey.d.ts.map +1 -0
- package/dist/types/provider/password.d.ts +210 -0
- package/dist/types/provider/password.d.ts.map +1 -0
- package/dist/types/provider/provider.d.ts +29 -0
- package/dist/types/provider/provider.d.ts.map +1 -0
- package/dist/types/provider/slack.d.ts +59 -0
- package/dist/types/provider/slack.d.ts.map +1 -0
- package/dist/types/provider/spotify.d.ts +38 -0
- package/dist/types/provider/spotify.d.ts.map +1 -0
- package/dist/types/provider/twitch.d.ts +38 -0
- package/dist/types/provider/twitch.d.ts.map +1 -0
- package/dist/types/provider/x.d.ts +38 -0
- package/dist/types/provider/x.d.ts.map +1 -0
- package/dist/types/provider/yahoo.d.ts +38 -0
- package/dist/types/provider/yahoo.d.ts.map +1 -0
- package/dist/types/random.d.ts +3 -0
- package/dist/types/random.d.ts.map +1 -0
- package/dist/types/storage/aws.d.ts +4 -0
- package/dist/types/storage/aws.d.ts.map +1 -0
- package/dist/types/storage/cloudflare.d.ts +34 -0
- package/dist/types/storage/cloudflare.d.ts.map +1 -0
- package/dist/types/storage/dynamo.d.ts +65 -0
- package/dist/types/storage/dynamo.d.ts.map +1 -0
- package/dist/types/storage/memory.d.ts +49 -0
- package/dist/types/storage/memory.d.ts.map +1 -0
- package/dist/types/storage/storage.d.ts +15 -0
- package/dist/types/storage/storage.d.ts.map +1 -0
- package/dist/types/subject.d.ts +122 -0
- package/dist/types/subject.d.ts.map +1 -0
- package/dist/types/ui/base.d.ts +5 -0
- package/dist/types/ui/base.d.ts.map +1 -0
- package/dist/types/ui/code.d.ts +104 -0
- package/dist/types/ui/code.d.ts.map +1 -0
- package/dist/types/ui/form.d.ts +6 -0
- package/dist/types/ui/form.d.ts.map +1 -0
- package/dist/types/ui/icon.d.ts +6 -0
- package/dist/types/ui/icon.d.ts.map +1 -0
- package/dist/types/ui/passkey.d.ts +5 -0
- package/dist/types/ui/passkey.d.ts.map +1 -0
- package/dist/types/ui/password.d.ts +139 -0
- package/dist/types/ui/password.d.ts.map +1 -0
- package/dist/types/ui/select.d.ts +55 -0
- package/dist/types/ui/select.d.ts.map +1 -0
- package/dist/types/ui/theme.d.ts +207 -0
- package/dist/types/ui/theme.d.ts.map +1 -0
- package/dist/types/util.d.ts +8 -0
- package/dist/types/util.d.ts.map +1 -0
- package/package.json +51 -0
- package/src/client.ts +749 -0
- package/src/css.d.ts +4 -0
- package/src/error.ts +120 -0
- package/src/index.ts +26 -0
- package/src/issuer.ts +1302 -0
- package/src/jwt.ts +17 -0
- package/src/keys.ts +139 -0
- package/src/pkce.ts +40 -0
- package/src/provider/apple.ts +127 -0
- package/src/provider/arctic.ts +66 -0
- package/src/provider/code.ts +227 -0
- package/src/provider/cognito.ts +74 -0
- package/src/provider/discord.ts +45 -0
- package/src/provider/facebook.ts +84 -0
- package/src/provider/github.ts +45 -0
- package/src/provider/google.ts +85 -0
- package/src/provider/index.ts +3 -0
- package/src/provider/jumpcloud.ts +45 -0
- package/src/provider/keycloak.ts +75 -0
- package/src/provider/linkedin.ts +12 -0
- package/src/provider/m2m.ts +56 -0
- package/src/provider/microsoft.ts +100 -0
- package/src/provider/oauth2.ts +297 -0
- package/src/provider/oidc.ts +179 -0
- package/src/provider/passkey.ts +655 -0
- package/src/provider/password.ts +672 -0
- package/src/provider/provider.ts +33 -0
- package/src/provider/slack.ts +67 -0
- package/src/provider/spotify.ts +45 -0
- package/src/provider/twitch.ts +45 -0
- package/src/provider/x.ts +46 -0
- package/src/provider/yahoo.ts +45 -0
- package/src/random.ts +24 -0
- package/src/storage/aws.ts +59 -0
- package/src/storage/cloudflare.ts +77 -0
- package/src/storage/dynamo.ts +193 -0
- package/src/storage/memory.ts +135 -0
- package/src/storage/storage.ts +46 -0
- package/src/subject.ts +130 -0
- package/src/ui/base.tsx +118 -0
- package/src/ui/code.tsx +215 -0
- package/src/ui/form.tsx +40 -0
- package/src/ui/icon.tsx +95 -0
- package/src/ui/passkey.tsx +321 -0
- package/src/ui/password.tsx +405 -0
- package/src/ui/select.tsx +221 -0
- package/src/ui/theme.ts +319 -0
- package/src/ui/ui.css +252 -0
- package/src/util.ts +58 -0
package/dist/esm/pkce.js
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
// src/pkce.ts
|
|
2
|
+
import { base64url } from "jose";
|
|
3
|
+
function generateVerifier(length) {
|
|
4
|
+
const buffer = new Uint8Array(length);
|
|
5
|
+
crypto.getRandomValues(buffer);
|
|
6
|
+
return base64url.encode(buffer);
|
|
7
|
+
}
|
|
8
|
+
async function generateChallenge(verifier, method) {
|
|
9
|
+
if (method === "plain")
|
|
10
|
+
return verifier;
|
|
11
|
+
const encoder = new TextEncoder;
|
|
12
|
+
const data = encoder.encode(verifier);
|
|
13
|
+
const hash = await crypto.subtle.digest("SHA-256", data);
|
|
14
|
+
return base64url.encode(new Uint8Array(hash));
|
|
15
|
+
}
|
|
16
|
+
async function generatePKCE(length = 64) {
|
|
17
|
+
if (length < 43 || length > 128) {
|
|
18
|
+
throw new Error("Code verifier length must be between 43 and 128 characters");
|
|
19
|
+
}
|
|
20
|
+
const verifier = generateVerifier(length);
|
|
21
|
+
const challenge = await generateChallenge(verifier, "S256");
|
|
22
|
+
return {
|
|
23
|
+
verifier,
|
|
24
|
+
challenge,
|
|
25
|
+
method: "S256"
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
async function validatePKCE(verifier, challenge, method = "S256") {
|
|
29
|
+
const generatedChallenge = await generateChallenge(verifier, method);
|
|
30
|
+
return generatedChallenge === challenge;
|
|
31
|
+
}
|
|
32
|
+
export {
|
|
33
|
+
validatePKCE,
|
|
34
|
+
generatePKCE
|
|
35
|
+
};
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
// src/provider/apple.ts
|
|
2
|
+
import { Oauth2Provider } from "./oauth2.js";
|
|
3
|
+
import { OidcProvider } from "./oidc.js";
|
|
4
|
+
function AppleProvider(config) {
|
|
5
|
+
const { responseMode, ...restConfig } = config;
|
|
6
|
+
const additionalQuery = responseMode === "form_post" ? { response_mode: "form_post", ...config.query } : config.query || {};
|
|
7
|
+
return Oauth2Provider({
|
|
8
|
+
...restConfig,
|
|
9
|
+
type: "apple",
|
|
10
|
+
endpoint: {
|
|
11
|
+
authorization: "https://appleid.apple.com/auth/authorize",
|
|
12
|
+
token: "https://appleid.apple.com/auth/token",
|
|
13
|
+
jwks: "https://appleid.apple.com/auth/keys"
|
|
14
|
+
},
|
|
15
|
+
query: additionalQuery
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
function AppleOidcProvider(config) {
|
|
19
|
+
return OidcProvider({
|
|
20
|
+
...config,
|
|
21
|
+
type: "apple",
|
|
22
|
+
issuer: "https://appleid.apple.com"
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
export {
|
|
26
|
+
AppleProvider,
|
|
27
|
+
AppleOidcProvider
|
|
28
|
+
};
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
// src/provider/arctic.ts
|
|
2
|
+
import { OauthError } from "../error.js";
|
|
3
|
+
import { getRelativeUrl } from "../util.js";
|
|
4
|
+
function ArcticProvider(provider, config) {
|
|
5
|
+
function getClient(c) {
|
|
6
|
+
const callback = new URL(c.req.url);
|
|
7
|
+
const pathname = callback.pathname.replace(/authorize.*$/, "callback");
|
|
8
|
+
const url = getRelativeUrl(c, pathname);
|
|
9
|
+
return new provider(config.clientID, config.clientSecret, url);
|
|
10
|
+
}
|
|
11
|
+
return {
|
|
12
|
+
type: "arctic",
|
|
13
|
+
init(routes, ctx) {
|
|
14
|
+
routes.get("/authorize", async (c) => {
|
|
15
|
+
const client = getClient(c);
|
|
16
|
+
const state = crypto.randomUUID();
|
|
17
|
+
await ctx.set(c, "provider", 60 * 10, {
|
|
18
|
+
state
|
|
19
|
+
});
|
|
20
|
+
return c.redirect(client.createAuthorizationURL(state, config.scopes));
|
|
21
|
+
});
|
|
22
|
+
routes.get("/callback", async (c) => {
|
|
23
|
+
const client = getClient(c);
|
|
24
|
+
const provider2 = await ctx.get(c, "provider");
|
|
25
|
+
if (!provider2)
|
|
26
|
+
return c.redirect("../authorize");
|
|
27
|
+
const code = c.req.query("code");
|
|
28
|
+
const state = c.req.query("state");
|
|
29
|
+
if (!code)
|
|
30
|
+
throw new Error("Missing code");
|
|
31
|
+
if (state !== provider2.state)
|
|
32
|
+
throw new OauthError("invalid_request", "Invalid state");
|
|
33
|
+
const tokens = await client.validateAuthorizationCode(code);
|
|
34
|
+
return ctx.success(c, {
|
|
35
|
+
tokenset: tokens
|
|
36
|
+
});
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
export {
|
|
42
|
+
ArcticProvider
|
|
43
|
+
};
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
// src/provider/code.ts
|
|
2
|
+
import { generateUnbiasedDigits, timingSafeCompare } from "../random.js";
|
|
3
|
+
function CodeProvider(config) {
|
|
4
|
+
const length = config.length || 6;
|
|
5
|
+
function generate() {
|
|
6
|
+
return generateUnbiasedDigits(length);
|
|
7
|
+
}
|
|
8
|
+
return {
|
|
9
|
+
type: "code",
|
|
10
|
+
init(routes, ctx) {
|
|
11
|
+
async function transition(c, next, fd, err) {
|
|
12
|
+
await ctx.set(c, "provider", 60 * 60 * 24, next);
|
|
13
|
+
const resp = ctx.forward(c, await config.request(c.req.raw, next, fd, err));
|
|
14
|
+
return resp;
|
|
15
|
+
}
|
|
16
|
+
routes.get("/authorize", async (c) => {
|
|
17
|
+
const resp = await transition(c, {
|
|
18
|
+
type: "start"
|
|
19
|
+
});
|
|
20
|
+
return resp;
|
|
21
|
+
});
|
|
22
|
+
routes.post("/authorize", async (c) => {
|
|
23
|
+
const code = generate();
|
|
24
|
+
const fd = await c.req.formData();
|
|
25
|
+
const state = await ctx.get(c, "provider");
|
|
26
|
+
const action = fd.get("action")?.toString();
|
|
27
|
+
if (action === "request" || action === "resend") {
|
|
28
|
+
const claims = Object.fromEntries(fd);
|
|
29
|
+
delete claims.action;
|
|
30
|
+
const err = await config.sendCode(claims, code);
|
|
31
|
+
if (err)
|
|
32
|
+
return transition(c, { type: "start" }, fd, err);
|
|
33
|
+
return transition(c, {
|
|
34
|
+
type: "code",
|
|
35
|
+
resend: action === "resend",
|
|
36
|
+
claims,
|
|
37
|
+
code
|
|
38
|
+
}, fd);
|
|
39
|
+
}
|
|
40
|
+
if (fd.get("action")?.toString() === "verify" && state.type === "code") {
|
|
41
|
+
const fd2 = await c.req.formData();
|
|
42
|
+
const compare = fd2.get("code")?.toString();
|
|
43
|
+
if (!state.code || !compare || !timingSafeCompare(state.code, compare)) {
|
|
44
|
+
return transition(c, {
|
|
45
|
+
...state,
|
|
46
|
+
resend: false
|
|
47
|
+
}, fd2, { type: "invalid_code" });
|
|
48
|
+
}
|
|
49
|
+
await ctx.unset(c, "provider");
|
|
50
|
+
return ctx.forward(c, await ctx.success(c, { claims: state.claims }));
|
|
51
|
+
}
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
export {
|
|
57
|
+
CodeProvider
|
|
58
|
+
};
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
// src/provider/cognito.ts
|
|
2
|
+
import { Oauth2Provider } from "./oauth2.js";
|
|
3
|
+
function CognitoProvider(config) {
|
|
4
|
+
const domain = `${config.domain}.auth.${config.region}.amazoncognito.com`;
|
|
5
|
+
return Oauth2Provider({
|
|
6
|
+
type: "cognito",
|
|
7
|
+
...config,
|
|
8
|
+
endpoint: {
|
|
9
|
+
authorization: `https://${domain}/oauth2/authorize`,
|
|
10
|
+
token: `https://${domain}/oauth2/token`
|
|
11
|
+
}
|
|
12
|
+
});
|
|
13
|
+
}
|
|
14
|
+
export {
|
|
15
|
+
CognitoProvider
|
|
16
|
+
};
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
// src/provider/discord.ts
|
|
2
|
+
import { Oauth2Provider } from "./oauth2.js";
|
|
3
|
+
function DiscordProvider(config) {
|
|
4
|
+
return Oauth2Provider({
|
|
5
|
+
type: "discord",
|
|
6
|
+
...config,
|
|
7
|
+
endpoint: {
|
|
8
|
+
authorization: "https://discord.com/oauth2/authorize",
|
|
9
|
+
token: "https://discord.com/api/oauth2/token"
|
|
10
|
+
}
|
|
11
|
+
});
|
|
12
|
+
}
|
|
13
|
+
export {
|
|
14
|
+
DiscordProvider
|
|
15
|
+
};
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
// src/provider/facebook.ts
|
|
2
|
+
import { Oauth2Provider } from "./oauth2.js";
|
|
3
|
+
import { OidcProvider } from "./oidc.js";
|
|
4
|
+
function FacebookProvider(config) {
|
|
5
|
+
return Oauth2Provider({
|
|
6
|
+
...config,
|
|
7
|
+
type: "facebook",
|
|
8
|
+
endpoint: {
|
|
9
|
+
authorization: "https://www.facebook.com/v12.0/dialog/oauth",
|
|
10
|
+
token: "https://graph.facebook.com/v12.0/oauth/access_token"
|
|
11
|
+
}
|
|
12
|
+
});
|
|
13
|
+
}
|
|
14
|
+
function FacebookOidcProvider(config) {
|
|
15
|
+
return OidcProvider({
|
|
16
|
+
...config,
|
|
17
|
+
type: "facebook",
|
|
18
|
+
issuer: "https://graph.facebook.com"
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
export {
|
|
22
|
+
FacebookProvider,
|
|
23
|
+
FacebookOidcProvider
|
|
24
|
+
};
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
// src/provider/github.ts
|
|
2
|
+
import { Oauth2Provider } from "./oauth2.js";
|
|
3
|
+
function GithubProvider(config) {
|
|
4
|
+
return Oauth2Provider({
|
|
5
|
+
...config,
|
|
6
|
+
type: "github",
|
|
7
|
+
endpoint: {
|
|
8
|
+
authorization: "https://github.com/login/oauth/authorize",
|
|
9
|
+
token: "https://github.com/login/oauth/access_token"
|
|
10
|
+
}
|
|
11
|
+
});
|
|
12
|
+
}
|
|
13
|
+
export {
|
|
14
|
+
GithubProvider
|
|
15
|
+
};
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
// src/provider/google.ts
|
|
2
|
+
import { Oauth2Provider } from "./oauth2.js";
|
|
3
|
+
import { OidcProvider } from "./oidc.js";
|
|
4
|
+
function GoogleProvider(config) {
|
|
5
|
+
return Oauth2Provider({
|
|
6
|
+
...config,
|
|
7
|
+
type: "google",
|
|
8
|
+
endpoint: {
|
|
9
|
+
authorization: "https://accounts.google.com/o/oauth2/v2/auth",
|
|
10
|
+
token: "https://oauth2.googleapis.com/token",
|
|
11
|
+
jwks: "https://www.googleapis.com/oauth2/v3/certs"
|
|
12
|
+
}
|
|
13
|
+
});
|
|
14
|
+
}
|
|
15
|
+
function GoogleOidcProvider(config) {
|
|
16
|
+
return OidcProvider({
|
|
17
|
+
...config,
|
|
18
|
+
type: "google",
|
|
19
|
+
issuer: "https://accounts.google.com"
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
export {
|
|
23
|
+
GoogleProvider,
|
|
24
|
+
GoogleOidcProvider
|
|
25
|
+
};
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
// src/provider/jumpcloud.ts
|
|
2
|
+
import { Oauth2Provider } from "./oauth2.js";
|
|
3
|
+
function JumpCloudProvider(config) {
|
|
4
|
+
return Oauth2Provider({
|
|
5
|
+
type: "jumpcloud",
|
|
6
|
+
...config,
|
|
7
|
+
endpoint: {
|
|
8
|
+
authorization: "https://oauth.id.jumpcloud.com/oauth2/auth",
|
|
9
|
+
token: "https://oauth.id.jumpcloud.com/oauth2/token"
|
|
10
|
+
}
|
|
11
|
+
});
|
|
12
|
+
}
|
|
13
|
+
export {
|
|
14
|
+
JumpCloudProvider
|
|
15
|
+
};
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
// src/provider/keycloak.ts
|
|
2
|
+
import { Oauth2Provider } from "./oauth2.js";
|
|
3
|
+
function KeycloakProvider(config) {
|
|
4
|
+
const baseConfig = {
|
|
5
|
+
...config,
|
|
6
|
+
endpoint: {
|
|
7
|
+
authorization: `${config.baseUrl}/realms/${config.realm}/protocol/openid-connect/auth`,
|
|
8
|
+
token: `${config.baseUrl}/realms/${config.realm}/protocol/openid-connect/token`
|
|
9
|
+
}
|
|
10
|
+
};
|
|
11
|
+
return Oauth2Provider(baseConfig);
|
|
12
|
+
}
|
|
13
|
+
export {
|
|
14
|
+
KeycloakProvider
|
|
15
|
+
};
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
// src/provider/linkedin.ts
|
|
2
|
+
import { Oauth2Provider } from "./oauth2.js";
|
|
3
|
+
function LinkedInAdapter(config) {
|
|
4
|
+
return Oauth2Provider({
|
|
5
|
+
...config,
|
|
6
|
+
type: "linkedin",
|
|
7
|
+
endpoint: {
|
|
8
|
+
authorization: "https://www.linkedin.com/oauth/v2/authorization",
|
|
9
|
+
token: "https://www.linkedin.com/oauth/v2/accessToken"
|
|
10
|
+
}
|
|
11
|
+
});
|
|
12
|
+
}
|
|
13
|
+
export {
|
|
14
|
+
LinkedInAdapter
|
|
15
|
+
};
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
// src/provider/m2m.ts
|
|
2
|
+
function M2MProvider(config) {
|
|
3
|
+
return {
|
|
4
|
+
type: "m2m",
|
|
5
|
+
init() {
|
|
6
|
+
},
|
|
7
|
+
async client(input) {
|
|
8
|
+
const result = await config.verify(input.clientID, input.clientSecret, input.params);
|
|
9
|
+
if (!result)
|
|
10
|
+
throw new Error("Invalid client credentials");
|
|
11
|
+
return result;
|
|
12
|
+
}
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
export {
|
|
16
|
+
M2MProvider
|
|
17
|
+
};
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
// src/provider/microsoft.ts
|
|
2
|
+
import { Oauth2Provider } from "./oauth2.js";
|
|
3
|
+
import { OidcProvider } from "./oidc.js";
|
|
4
|
+
function MicrosoftProvider(config) {
|
|
5
|
+
return Oauth2Provider({
|
|
6
|
+
...config,
|
|
7
|
+
type: "microsoft",
|
|
8
|
+
endpoint: {
|
|
9
|
+
authorization: `https://login.microsoftonline.com/${config?.tenant}/oauth2/v2.0/authorize`,
|
|
10
|
+
token: `https://login.microsoftonline.com/${config?.tenant}/oauth2/v2.0/token`
|
|
11
|
+
}
|
|
12
|
+
});
|
|
13
|
+
}
|
|
14
|
+
function MicrosoftOidcProvider(config) {
|
|
15
|
+
return OidcProvider({
|
|
16
|
+
...config,
|
|
17
|
+
type: "microsoft",
|
|
18
|
+
issuer: "https://graph.microsoft.com/oidc/userinfo"
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
export {
|
|
22
|
+
MicrosoftProvider,
|
|
23
|
+
MicrosoftOidcProvider
|
|
24
|
+
};
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
// src/provider/oauth2.ts
|
|
2
|
+
import { createRemoteJWKSet, jwtVerify } from "jose";
|
|
3
|
+
import { OauthError } from "../error.js";
|
|
4
|
+
import { generatePKCE } from "../pkce.js";
|
|
5
|
+
import { getRelativeUrl } from "../util.js";
|
|
6
|
+
function Oauth2Provider(config) {
|
|
7
|
+
const query = config.query || {};
|
|
8
|
+
async function handleCallbackLogic(c, ctx, provider, code) {
|
|
9
|
+
if (!provider || !code) {
|
|
10
|
+
return c.redirect(getRelativeUrl(c, "./authorize"));
|
|
11
|
+
}
|
|
12
|
+
const body = new URLSearchParams({
|
|
13
|
+
client_id: config.clientID,
|
|
14
|
+
client_secret: config.clientSecret,
|
|
15
|
+
code,
|
|
16
|
+
grant_type: "authorization_code",
|
|
17
|
+
redirect_uri: provider.redirect,
|
|
18
|
+
...provider.codeVerifier ? { code_verifier: provider.codeVerifier } : {}
|
|
19
|
+
});
|
|
20
|
+
const json = await fetch(config.endpoint.token, {
|
|
21
|
+
method: "POST",
|
|
22
|
+
headers: {
|
|
23
|
+
"Content-Type": "application/x-www-form-urlencoded",
|
|
24
|
+
Accept: "application/json"
|
|
25
|
+
},
|
|
26
|
+
body: body.toString()
|
|
27
|
+
}).then((r) => r.json());
|
|
28
|
+
if ("error" in json) {
|
|
29
|
+
throw new OauthError(json.error, json.error_description);
|
|
30
|
+
}
|
|
31
|
+
let idTokenPayload = null;
|
|
32
|
+
if (config.endpoint.jwks) {
|
|
33
|
+
const jwksEndpoint = new URL(config.endpoint.jwks);
|
|
34
|
+
const jwks = createRemoteJWKSet(jwksEndpoint);
|
|
35
|
+
const { payload } = await jwtVerify(json.id_token, jwks, {
|
|
36
|
+
audience: config.clientID
|
|
37
|
+
});
|
|
38
|
+
idTokenPayload = payload;
|
|
39
|
+
}
|
|
40
|
+
return ctx.success(c, {
|
|
41
|
+
clientID: config.clientID,
|
|
42
|
+
tokenset: {
|
|
43
|
+
get access() {
|
|
44
|
+
return json.access_token;
|
|
45
|
+
},
|
|
46
|
+
get refresh() {
|
|
47
|
+
return json.refresh_token;
|
|
48
|
+
},
|
|
49
|
+
get expiry() {
|
|
50
|
+
return json.expires_in;
|
|
51
|
+
},
|
|
52
|
+
get id() {
|
|
53
|
+
if (!idTokenPayload)
|
|
54
|
+
return null;
|
|
55
|
+
return idTokenPayload;
|
|
56
|
+
},
|
|
57
|
+
get raw() {
|
|
58
|
+
return json;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
return {
|
|
64
|
+
type: config.type || "oauth2",
|
|
65
|
+
init(routes, ctx) {
|
|
66
|
+
routes.get("/authorize", async (c) => {
|
|
67
|
+
const state = crypto.randomUUID();
|
|
68
|
+
const pkce = config.pkce ? await generatePKCE() : undefined;
|
|
69
|
+
await ctx.set(c, "provider", 60 * 10, {
|
|
70
|
+
state,
|
|
71
|
+
redirect: getRelativeUrl(c, "./callback"),
|
|
72
|
+
codeVerifier: pkce?.verifier
|
|
73
|
+
});
|
|
74
|
+
const authorization = new URL(config.endpoint.authorization);
|
|
75
|
+
authorization.searchParams.set("client_id", config.clientID);
|
|
76
|
+
authorization.searchParams.set("redirect_uri", getRelativeUrl(c, "./callback"));
|
|
77
|
+
authorization.searchParams.set("response_type", "code");
|
|
78
|
+
authorization.searchParams.set("state", state);
|
|
79
|
+
authorization.searchParams.set("scope", config.scopes.join(" "));
|
|
80
|
+
if (pkce) {
|
|
81
|
+
authorization.searchParams.set("code_challenge", pkce.challenge);
|
|
82
|
+
authorization.searchParams.set("code_challenge_method", pkce.method);
|
|
83
|
+
}
|
|
84
|
+
for (const [key, value] of Object.entries(query)) {
|
|
85
|
+
authorization.searchParams.set(key, value);
|
|
86
|
+
}
|
|
87
|
+
return c.redirect(authorization.toString());
|
|
88
|
+
});
|
|
89
|
+
routes.get("/callback", async (c) => {
|
|
90
|
+
const provider = await ctx.get(c, "provider");
|
|
91
|
+
const code = c.req.query("code");
|
|
92
|
+
const state = c.req.query("state");
|
|
93
|
+
const error = c.req.query("error");
|
|
94
|
+
if (error)
|
|
95
|
+
throw new OauthError(error.toString(), c.req.query("error_description")?.toString() || "");
|
|
96
|
+
if (!provider || !code || provider.state && state !== provider.state) {
|
|
97
|
+
return c.redirect(getRelativeUrl(c, "./authorize"));
|
|
98
|
+
}
|
|
99
|
+
return handleCallbackLogic(c, ctx, provider, code);
|
|
100
|
+
});
|
|
101
|
+
routes.post("/callback", async (c) => {
|
|
102
|
+
const provider = await ctx.get(c, "provider");
|
|
103
|
+
const formData = await c.req.formData();
|
|
104
|
+
const code = formData.get("code")?.toString();
|
|
105
|
+
const state = formData.get("state")?.toString();
|
|
106
|
+
const error = formData.get("error")?.toString();
|
|
107
|
+
if (error)
|
|
108
|
+
throw new OauthError(error, formData.get("error_description")?.toString() || "");
|
|
109
|
+
if (!provider || !code || provider.state && state !== provider.state) {
|
|
110
|
+
return c.redirect(getRelativeUrl(c, "./authorize"));
|
|
111
|
+
}
|
|
112
|
+
return handleCallbackLogic(c, ctx, provider, code);
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
export {
|
|
118
|
+
Oauth2Provider
|
|
119
|
+
};
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
// src/provider/oidc.ts
|
|
2
|
+
import { createLocalJWKSet, jwtVerify } from "jose";
|
|
3
|
+
import { OauthError } from "../error.js";
|
|
4
|
+
import { getRelativeUrl, lazy } from "../util.js";
|
|
5
|
+
function OidcProvider(config) {
|
|
6
|
+
const query = config.query || {};
|
|
7
|
+
const scopes = config.scopes || [];
|
|
8
|
+
const wk = lazy(() => fetch(config.issuer + "/.well-known/openid-configuration").then(async (r) => {
|
|
9
|
+
if (!r.ok)
|
|
10
|
+
throw new Error(await r.text());
|
|
11
|
+
return r.json();
|
|
12
|
+
}));
|
|
13
|
+
const jwks = lazy(() => wk().then((r) => r.jwks_uri).then(async (uri) => {
|
|
14
|
+
const r = await fetch(uri);
|
|
15
|
+
if (!r.ok)
|
|
16
|
+
throw new Error(await r.text());
|
|
17
|
+
return createLocalJWKSet(await r.json());
|
|
18
|
+
}));
|
|
19
|
+
return {
|
|
20
|
+
type: config.type || "oidc",
|
|
21
|
+
init(routes, ctx) {
|
|
22
|
+
routes.get("/authorize", async (c) => {
|
|
23
|
+
const provider = {
|
|
24
|
+
state: crypto.randomUUID(),
|
|
25
|
+
nonce: crypto.randomUUID(),
|
|
26
|
+
redirect: getRelativeUrl(c, "./callback")
|
|
27
|
+
};
|
|
28
|
+
await ctx.set(c, "provider", 60 * 10, provider);
|
|
29
|
+
const authorization = new URL(await wk().then((r) => r.authorization_endpoint));
|
|
30
|
+
authorization.searchParams.set("client_id", config.clientID);
|
|
31
|
+
authorization.searchParams.set("response_type", "id_token");
|
|
32
|
+
authorization.searchParams.set("response_mode", "form_post");
|
|
33
|
+
authorization.searchParams.set("state", provider.state);
|
|
34
|
+
authorization.searchParams.set("nonce", provider.nonce);
|
|
35
|
+
authorization.searchParams.set("redirect_uri", provider.redirect);
|
|
36
|
+
authorization.searchParams.set("scope", ["openid", ...scopes].join(" "));
|
|
37
|
+
for (const [key, value] of Object.entries(query)) {
|
|
38
|
+
authorization.searchParams.set(key, value);
|
|
39
|
+
}
|
|
40
|
+
return c.redirect(authorization.toString());
|
|
41
|
+
});
|
|
42
|
+
routes.post("/callback", async (c) => {
|
|
43
|
+
const provider = await ctx.get(c, "provider");
|
|
44
|
+
if (!provider)
|
|
45
|
+
return c.redirect(getRelativeUrl(c, "./authorize"));
|
|
46
|
+
const body = await c.req.formData();
|
|
47
|
+
const error = body.get("error");
|
|
48
|
+
if (error)
|
|
49
|
+
throw new OauthError(error.toString(), body.get("error_description")?.toString() || "");
|
|
50
|
+
const idToken = body.get("id_token");
|
|
51
|
+
if (!idToken)
|
|
52
|
+
throw new OauthError("invalid_request", "Missing id_token");
|
|
53
|
+
const result = await jwtVerify(idToken.toString(), await jwks(), {
|
|
54
|
+
audience: config.clientID
|
|
55
|
+
});
|
|
56
|
+
if (result.payload.nonce !== provider.nonce) {
|
|
57
|
+
throw new OauthError("invalid_request", "Invalid nonce");
|
|
58
|
+
}
|
|
59
|
+
return ctx.success(c, {
|
|
60
|
+
id: result.payload,
|
|
61
|
+
clientID: config.clientID
|
|
62
|
+
});
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
export {
|
|
68
|
+
OidcProvider
|
|
69
|
+
};
|