@frigear-nu/oauth-server 0.1.1 → 0.1.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.
- package/dist/module.d.mts +27 -3
- package/dist/module.json +1 -1
- package/dist/module.mjs +39 -20
- package/dist/runtime/app/components/OAuthApplicationSlideover.vue +41 -0
- package/dist/runtime/app/components/OAuthCredentialsModal.d.vue.ts +2 -1
- package/dist/runtime/app/components/OAuthCredentialsModal.vue +10 -3
- package/dist/runtime/app/components/OAuthCredentialsModal.vue.d.ts +2 -1
- package/dist/runtime/app/composables/useOAuthApplicationActions.d.ts +3 -0
- package/dist/runtime/app/composables/useOAuthApplicationActions.js +32 -15
- package/dist/runtime/app/composables/useOAuthApplications.d.ts +20 -20
- package/dist/runtime/app/composables/useOAuthApplications.js +35 -18
- package/dist/runtime/app/pages/admin.vue +116 -58
- package/dist/runtime/app/pages/applications.vue +59 -24
- package/dist/runtime/app/pages/authorize.vue +3 -2
- package/dist/runtime/i18n/lang/da.json +21 -4
- package/dist/runtime/i18n/lang/en.json +21 -4
- package/dist/runtime/server/bootstrap/my-applications.d.ts +7 -0
- package/dist/runtime/server/bootstrap/my-applications.js +4 -0
- package/dist/runtime/server/db/schema.postgresql.d.ts +17 -0
- package/dist/runtime/server/db/schema.postgresql.js +7 -1
- package/dist/runtime/server/db/schema.sqlite.d.ts +19 -0
- package/dist/runtime/server/db/schema.sqlite.js +7 -1
- package/dist/runtime/server/middleware/oauth-cors.d.ts +2 -0
- package/dist/runtime/server/middleware/oauth-cors.js +25 -0
- package/dist/runtime/server/routes/api/admin/application-revoke.post.js +5 -3
- package/dist/runtime/server/routes/api/admin/application-secret.post.js +3 -1
- package/dist/runtime/server/routes/api/admin/application.delete.js +4 -3
- package/dist/runtime/server/routes/api/admin/application.get.js +6 -3
- package/dist/runtime/server/routes/api/admin/application.patch.js +4 -3
- package/dist/runtime/server/routes/api/admin/applications.get.js +20 -8
- package/dist/runtime/server/routes/api/admin/applications.post.js +3 -3
- package/dist/runtime/server/routes/api/consent.get.js +4 -3
- package/dist/runtime/server/routes/api/me-applications.get.js +2 -4
- package/dist/runtime/server/routes/oidc/authorize.get.js +5 -6
- package/dist/runtime/server/routes/oidc/authorize.post.js +1 -2
- package/dist/runtime/server/routes/oidc/openid-configuration.get.js +8 -6
- package/dist/runtime/server/routes/oidc/revoke.post.js +19 -7
- package/dist/runtime/server/routes/oidc/token.post.js +24 -9
- package/dist/runtime/server/utils/oauth-access.d.ts +2 -0
- package/dist/runtime/server/utils/oauth-access.js +7 -1
- package/dist/runtime/server/utils/oauth-applications.d.ts +30 -4
- package/dist/runtime/server/utils/oauth-applications.js +77 -17
- package/dist/runtime/server/utils/oauth-authorize.d.ts +8 -4
- package/dist/runtime/server/utils/oauth-authorize.js +11 -9
- package/dist/runtime/server/utils/oauth-config.d.ts +7 -0
- package/dist/runtime/server/utils/oauth-config.js +10 -0
- package/dist/runtime/server/utils/oauth-crypto.d.ts +9 -2
- package/dist/runtime/server/utils/oauth-crypto.js +10 -4
- package/dist/runtime/server/utils/oauth-keys.js +3 -2
- package/dist/runtime/utils/api.d.ts +28 -0
- package/dist/runtime/utils/api.js +27 -0
- package/dist/runtime/utils/index.d.ts +15 -1
- package/dist/runtime/utils/index.js +33 -1
- package/dist/types.d.mts +1 -1
- package/package.json +16 -11
|
@@ -26,15 +26,17 @@ export async function computeCodeChallenge(verifier) {
|
|
|
26
26
|
export async function verifyCodeChallenge(verifier, challenge) {
|
|
27
27
|
return safeEqual(await computeCodeChallenge(verifier), challenge);
|
|
28
28
|
}
|
|
29
|
-
export function
|
|
29
|
+
export function readClientAuth(event, body) {
|
|
30
30
|
const header = getHeader(event, "authorization");
|
|
31
31
|
if (header?.toLowerCase().startsWith("basic ")) {
|
|
32
32
|
try {
|
|
33
33
|
const decoded = atob(header.slice(6).trim());
|
|
34
34
|
const index = decoded.indexOf(":");
|
|
35
35
|
if (index === -1) return null;
|
|
36
|
+
const clientId2 = decodeURIComponent(decoded.slice(0, index).replace(/\+/g, " "));
|
|
37
|
+
if (!clientId2) return null;
|
|
36
38
|
return {
|
|
37
|
-
clientId:
|
|
39
|
+
clientId: clientId2,
|
|
38
40
|
clientSecret: decodeURIComponent(decoded.slice(index + 1).replace(/\+/g, " ")),
|
|
39
41
|
basic: true
|
|
40
42
|
};
|
|
@@ -43,6 +45,10 @@ export function readClientCredentials(event, body) {
|
|
|
43
45
|
}
|
|
44
46
|
}
|
|
45
47
|
const clientId = typeof body?.client_id === "string" ? body.client_id : "";
|
|
46
|
-
|
|
47
|
-
return clientId
|
|
48
|
+
if (!clientId) return null;
|
|
49
|
+
return { clientId, clientSecret: typeof body?.client_secret === "string" ? body.client_secret : "", basic: false };
|
|
50
|
+
}
|
|
51
|
+
export function readClientCredentials(event, body) {
|
|
52
|
+
const auth = readClientAuth(event, body);
|
|
53
|
+
return auth?.clientSecret ? auth : null;
|
|
48
54
|
}
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { exportJWK, importPKCS8, importSPKI } from "jose";
|
|
2
|
+
import { apiError } from "@frigear-nu/module-kit/server";
|
|
2
3
|
export function normalizePemKey(raw) {
|
|
3
4
|
return raw.trim().replace(/^["']|["']$/g, "").replace(/\\n/g, "\n").trim();
|
|
4
5
|
}
|
|
5
|
-
const notConfigured = () =>
|
|
6
|
+
const notConfigured = () => apiError(500, "not_configured", "The OAuth server signing keys are not configured.");
|
|
6
7
|
let cache;
|
|
7
8
|
async function loadKeys() {
|
|
8
9
|
const config = useOAuthConfig();
|
|
@@ -31,7 +32,7 @@ function parsePreviousKeys() {
|
|
|
31
32
|
const parsed = typeof raw === "string" ? JSON.parse(raw) : raw;
|
|
32
33
|
return Object.fromEntries(Object.entries(parsed).map(([kid, pem]) => [kid, normalizePemKey(String(pem))]));
|
|
33
34
|
} catch {
|
|
34
|
-
throw
|
|
35
|
+
throw apiError(500, "not_configured", "The OAuth server previous public keys are not valid JSON.");
|
|
35
36
|
}
|
|
36
37
|
}
|
|
37
38
|
let previousCache;
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/** The module API: the consent data and the caller's applications under `/api/oauth-server`, staff routes under `/api/admin/oauth-server`. */
|
|
2
|
+
export declare const oauthServerApi: {
|
|
3
|
+
/** `GET`: data for the consent screen (the authorization request's parameters as the query). */
|
|
4
|
+
readonly consent: "/api/oauth-server/consent";
|
|
5
|
+
readonly me: {
|
|
6
|
+
/** `GET`: the applications the signed-in user may sign in to. */
|
|
7
|
+
readonly applications: "/api/oauth-server/me/applications";
|
|
8
|
+
};
|
|
9
|
+
readonly admin: {
|
|
10
|
+
/** `GET` (paginated) and `POST` (create, 201). */
|
|
11
|
+
readonly applications: "/api/admin/oauth-server/applications";
|
|
12
|
+
/** `GET`, `PATCH`, `DELETE` (204). */
|
|
13
|
+
readonly application: (id: string) => string;
|
|
14
|
+
/** `POST`: replaces the client secret and answers `{ clientSecret }` once. */
|
|
15
|
+
readonly rotateSecret: (id: string) => string;
|
|
16
|
+
/** `POST`: signs everyone out of the application; answers the application. */
|
|
17
|
+
readonly revoke: (id: string) => string;
|
|
18
|
+
};
|
|
19
|
+
};
|
|
20
|
+
/** The OAuth 2.0 / OpenID Connect endpoints, at their standard paths. */
|
|
21
|
+
export declare const oidc: {
|
|
22
|
+
readonly configuration: "/.well-known/openid-configuration";
|
|
23
|
+
readonly jwks: "/.well-known/jwks.json";
|
|
24
|
+
readonly authorize: "/oauth/authorize";
|
|
25
|
+
readonly token: "/oauth/token";
|
|
26
|
+
readonly revoke: "/oauth/revoke";
|
|
27
|
+
readonly userinfo: "/oauth/userinfo";
|
|
28
|
+
};
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
const enc = encodeURIComponent;
|
|
2
|
+
export const oauthServerApi = {
|
|
3
|
+
/** `GET`: data for the consent screen (the authorization request's parameters as the query). */
|
|
4
|
+
consent: "/api/oauth-server/consent",
|
|
5
|
+
me: {
|
|
6
|
+
/** `GET`: the applications the signed-in user may sign in to. */
|
|
7
|
+
applications: "/api/oauth-server/me/applications"
|
|
8
|
+
},
|
|
9
|
+
admin: {
|
|
10
|
+
/** `GET` (paginated) and `POST` (create, 201). */
|
|
11
|
+
applications: "/api/admin/oauth-server/applications",
|
|
12
|
+
/** `GET`, `PATCH`, `DELETE` (204). */
|
|
13
|
+
application: (id) => `/api/admin/oauth-server/applications/${enc(id)}`,
|
|
14
|
+
/** `POST`: replaces the client secret and answers `{ clientSecret }` once. */
|
|
15
|
+
rotateSecret: (id) => `/api/admin/oauth-server/applications/${enc(id)}/secret`,
|
|
16
|
+
/** `POST`: signs everyone out of the application; answers the application. */
|
|
17
|
+
revoke: (id) => `/api/admin/oauth-server/applications/${enc(id)}/revoke`
|
|
18
|
+
}
|
|
19
|
+
};
|
|
20
|
+
export const oidc = {
|
|
21
|
+
configuration: "/.well-known/openid-configuration",
|
|
22
|
+
jwks: "/.well-known/jwks.json",
|
|
23
|
+
authorize: "/oauth/authorize",
|
|
24
|
+
token: "/oauth/token",
|
|
25
|
+
revoke: "/oauth/revoke",
|
|
26
|
+
userinfo: "/oauth/userinfo"
|
|
27
|
+
};
|
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
export type * from '../../types';
|
|
2
|
+
export { oauthServerApi, oidc } from './api.js';
|
|
2
3
|
export declare const DEFAULT_SCOPES: readonly ["openid", "profile", "email"];
|
|
3
4
|
export declare const DEFAULT_CALLBACK_PATH = "/auth/frigear";
|
|
5
|
+
/** `confidential` clients hold a secret; `public` clients (native and single-page apps) cannot and rely on PKCE alone. */
|
|
6
|
+
export declare const CLIENT_TYPES: readonly ["confidential", "public"];
|
|
4
7
|
/** Whether a user holding `roles` may use an application restricted to `allowedRoles`. Empty = everyone. */
|
|
5
8
|
export declare function matchesAllowedRoles(allowedRoles: readonly string[] | null | undefined, roles: readonly string[]): boolean;
|
|
6
9
|
/** Splits a space separated scope string and drops duplicates and blanks. */
|
|
@@ -9,6 +12,12 @@ export declare function parseScope(scope: string | null | undefined): string[];
|
|
|
9
12
|
export declare function resolveScope(requested: string | null | undefined, supported: readonly string[]): string[];
|
|
10
13
|
/** HTTPS is required, except for `localhost` / loopback development hosts. */
|
|
11
14
|
export declare function isSecureOrigin(url: URL): boolean;
|
|
15
|
+
/**
|
|
16
|
+
* A redirect URI that opens a native app: a private-use scheme such as `com.example.app:/callback` (RFC 8252 §7.1) or a
|
|
17
|
+
* webview origin such as `capacitor://localhost/callback`. No web or dangerous schemes, credentials, fragments or
|
|
18
|
+
* wildcards. Only meaningful for `public` clients, and always matched exactly.
|
|
19
|
+
*/
|
|
20
|
+
export declare function isNativeRedirectUri(value: string | URL): boolean;
|
|
12
21
|
export declare function normalizeWebsiteUrl(value: string): string;
|
|
13
22
|
/** `https://*.vercel.app` -> a regex matching one host label in place of `*`. Everything else is matched literally. */
|
|
14
23
|
export declare function previewPatternToRegExp(pattern: string, callbackPath: string): RegExp;
|
|
@@ -17,8 +26,13 @@ export type RedirectUriRules = {
|
|
|
17
26
|
previewUrlPattern?: string | null;
|
|
18
27
|
redirectUris?: readonly string[] | null;
|
|
19
28
|
callbackPath?: string;
|
|
29
|
+
/** Public clients only: also accept the native redirect URIs listed in `redirectUris`. */
|
|
30
|
+
allowNativeRedirects?: boolean;
|
|
20
31
|
};
|
|
21
|
-
/**
|
|
32
|
+
/**
|
|
33
|
+
* Exact match on `websiteUrl + callbackPath`, an explicit extra redirect URI, or the preview pattern. Native redirect URIs
|
|
34
|
+
* (see `isNativeRedirectUri`) are only accepted for public clients, and only when listed in `redirectUris`.
|
|
35
|
+
*/
|
|
22
36
|
export declare function validateRedirectUri(redirectUri: string, rules: RedirectUriRules): boolean;
|
|
23
37
|
/** Prefixed random id, e.g. `newId('app')` -> `app_3f9c…`. */
|
|
24
38
|
export declare function newId(prefix: string): string;
|
|
@@ -1,5 +1,25 @@
|
|
|
1
|
+
export { oauthServerApi, oidc } from "./api.js";
|
|
1
2
|
export const DEFAULT_SCOPES = ["openid", "profile", "email"];
|
|
2
3
|
export const DEFAULT_CALLBACK_PATH = "/auth/frigear";
|
|
4
|
+
export const CLIENT_TYPES = ["confidential", "public"];
|
|
5
|
+
const NON_NATIVE_SCHEMES = /* @__PURE__ */ new Set([
|
|
6
|
+
"http:",
|
|
7
|
+
"https:",
|
|
8
|
+
"ws:",
|
|
9
|
+
"wss:",
|
|
10
|
+
"ftp:",
|
|
11
|
+
"file:",
|
|
12
|
+
"javascript:",
|
|
13
|
+
"data:",
|
|
14
|
+
"blob:",
|
|
15
|
+
"vbscript:",
|
|
16
|
+
"about:",
|
|
17
|
+
"mailto:",
|
|
18
|
+
"tel:",
|
|
19
|
+
"sms:",
|
|
20
|
+
"chrome:",
|
|
21
|
+
"view-source:"
|
|
22
|
+
]);
|
|
3
23
|
export function matchesAllowedRoles(allowedRoles, roles) {
|
|
4
24
|
if (!allowedRoles || allowedRoles.length === 0) return true;
|
|
5
25
|
return allowedRoles.some((role) => roles.includes(role));
|
|
@@ -16,6 +36,17 @@ export function isSecureOrigin(url) {
|
|
|
16
36
|
if (url.protocol === "https:") return true;
|
|
17
37
|
return url.protocol === "http:" && ["localhost", "127.0.0.1", "[::1]"].includes(url.hostname);
|
|
18
38
|
}
|
|
39
|
+
export function isNativeRedirectUri(value) {
|
|
40
|
+
let url;
|
|
41
|
+
try {
|
|
42
|
+
url = typeof value === "string" ? new URL(value) : value;
|
|
43
|
+
} catch {
|
|
44
|
+
return false;
|
|
45
|
+
}
|
|
46
|
+
if (!/^[a-z][a-z0-9+.-]*:$/.test(url.protocol) || NON_NATIVE_SCHEMES.has(url.protocol)) return false;
|
|
47
|
+
if (url.username || url.password || url.hash) return false;
|
|
48
|
+
return !url.href.includes("*");
|
|
49
|
+
}
|
|
19
50
|
export function normalizeWebsiteUrl(value) {
|
|
20
51
|
let url;
|
|
21
52
|
try {
|
|
@@ -40,7 +71,8 @@ export function validateRedirectUri(redirectUri, rules) {
|
|
|
40
71
|
} catch {
|
|
41
72
|
return false;
|
|
42
73
|
}
|
|
43
|
-
if (url.hash
|
|
74
|
+
if (url.hash) return false;
|
|
75
|
+
if (!isSecureOrigin(url)) return rules.allowNativeRedirects === true && isNativeRedirectUri(url) && !!rules.redirectUris?.includes(redirectUri);
|
|
44
76
|
if (redirectUri === `${rules.websiteUrl.replace(/\/+$/, "")}${callbackPath}`) return true;
|
|
45
77
|
if (rules.redirectUris?.includes(redirectUri)) return true;
|
|
46
78
|
if (rules.previewUrlPattern) return previewPatternToRegExp(rules.previewUrlPattern, callbackPath).test(redirectUri);
|
package/dist/types.d.mts
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
export { default } from './module.mjs'
|
|
2
2
|
|
|
3
|
-
export { type AdminApplicationDto, type ApplicationAccessContext, type ApplicationInput, type AuthorizeContext, type ClaimsContext, type ConsentDto, type CreatedApplicationDto, type ModuleOptions, type OAuthApplication, type OAuthServerRuntimeConfig, type OAuthUser, type PublicModuleOptions, type TokenIssuedContext, type TokenRevokedContext, type TokenType, type UserApplicationDto, type UserRolesContext } from './module.mjs'
|
|
3
|
+
export { type AdminApplicationDto, type AdminApplicationsPage, type ApplicationAccessContext, type ApplicationInput, type AuthorizeContext, type ClaimsContext, type ClientType, type ConsentDto, type CreatedApplicationDto, type ModuleOptions, type OAuthApplication, type OAuthServerRuntimeConfig, type OAuthUser, type PublicModuleOptions, type TokenIssuedContext, type TokenRevokedContext, type TokenType, type UserApplicationDto, type UserRolesContext } from './module.mjs'
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@frigear-nu/oauth-server",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.3",
|
|
4
4
|
"description": "OAuth 2.0 / OpenID Connect server for the frigear ecosystem: register applications that users with certain roles may sign in to.",
|
|
5
5
|
"url": "https://docs.frigear.dev/modules/oauth-server",
|
|
6
6
|
"author": {
|
|
@@ -28,6 +28,11 @@
|
|
|
28
28
|
"types": "./dist/runtime/utils/index.d.ts",
|
|
29
29
|
"import": "./dist/runtime/utils/index.js",
|
|
30
30
|
"default": "./dist/runtime/utils/index.js"
|
|
31
|
+
},
|
|
32
|
+
"./api": {
|
|
33
|
+
"types": "./dist/runtime/utils/api.d.ts",
|
|
34
|
+
"import": "./dist/runtime/utils/api.js",
|
|
35
|
+
"default": "./dist/runtime/utils/api.js"
|
|
31
36
|
}
|
|
32
37
|
},
|
|
33
38
|
"files": [
|
|
@@ -37,12 +42,12 @@
|
|
|
37
42
|
"playground"
|
|
38
43
|
],
|
|
39
44
|
"dependencies": {
|
|
40
|
-
"@frigear-nu/admin": "0.1.
|
|
41
|
-
"@frigear-nu/module-kit": "1.1.
|
|
42
|
-
"@frigear-nu/ui": "0.1.
|
|
43
|
-
"@frigear-nu/auth": "0.1.
|
|
44
|
-
"@frigear-nu/mcp": "0.1.
|
|
45
|
-
"@frigear-nu/users": "1.1.
|
|
45
|
+
"@frigear-nu/admin": "0.1.3",
|
|
46
|
+
"@frigear-nu/module-kit": "1.1.3",
|
|
47
|
+
"@frigear-nu/ui": "0.1.3",
|
|
48
|
+
"@frigear-nu/auth": "0.1.4",
|
|
49
|
+
"@frigear-nu/mcp": "0.1.3",
|
|
50
|
+
"@frigear-nu/users": "1.1.4",
|
|
46
51
|
"@libsql/client": "^0.18.0",
|
|
47
52
|
"@nuxt/kit": "^4.6.1",
|
|
48
53
|
"drizzle-kit": "^0.31.11",
|
|
@@ -52,8 +57,8 @@
|
|
|
52
57
|
"zod": "^4.6.5"
|
|
53
58
|
},
|
|
54
59
|
"peerDependencies": {
|
|
55
|
-
"@frigear-nu/menu": "0.1.
|
|
56
|
-
"@nuxt/ui": "^4.
|
|
60
|
+
"@frigear-nu/menu": "0.1.3",
|
|
61
|
+
"@nuxt/ui": "^4.12.0",
|
|
57
62
|
"@nuxthub/core": "^0.10.8"
|
|
58
63
|
},
|
|
59
64
|
"peerDependenciesMeta": {
|
|
@@ -65,13 +70,13 @@
|
|
|
65
70
|
}
|
|
66
71
|
},
|
|
67
72
|
"devDependencies": {
|
|
68
|
-
"@frigear-nu/menu": "0.1.
|
|
73
|
+
"@frigear-nu/menu": "0.1.3",
|
|
69
74
|
"@nuxt/devtools": "^3.4.2",
|
|
70
75
|
"@nuxt/eslint-config": "^1.17.0",
|
|
71
76
|
"@nuxt/module-builder": "^1.0.3",
|
|
72
77
|
"@nuxt/schema": "^4.6.1",
|
|
73
78
|
"@nuxt/test-utils": "^4.3.2",
|
|
74
|
-
"@nuxt/ui": "^4.
|
|
79
|
+
"@nuxt/ui": "^4.12.0",
|
|
75
80
|
"@nuxthub/core": "^0.10.8",
|
|
76
81
|
"@types/node": "^26.6.5",
|
|
77
82
|
"changelogen": "^0.6.2",
|