@frigear-nu/oauth-server 0.1.2 → 0.2.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/module.d.mts +8 -1
- package/dist/module.json +1 -1
- package/dist/module.mjs +24 -20
- package/dist/runtime/app/components/OAuthApplicationSlideover.vue +1 -0
- package/dist/runtime/app/components/OAuthCredentialsModal.vue +1 -0
- package/dist/runtime/app/composables/useOAuthApplicationActions.d.ts +3 -0
- package/dist/runtime/app/composables/useOAuthApplicationActions.js +30 -13
- package/dist/runtime/app/composables/useOAuthApplications.d.ts +34 -13
- package/dist/runtime/app/composables/useOAuthApplications.js +26 -14
- package/dist/runtime/app/pages/admin.vue +114 -62
- package/dist/runtime/app/pages/applications.vue +52 -34
- package/dist/runtime/app/pages/authorize.vue +4 -2
- package/dist/runtime/i18n/lang/da.json +6 -1
- package/dist/runtime/i18n/lang/en.json +6 -1
- package/dist/runtime/menus.js +1 -0
- package/dist/runtime/server/middleware/oauth-cors.js +6 -5
- 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/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 +6 -5
- package/dist/runtime/server/routes/oidc/revoke.post.js +15 -3
- package/dist/runtime/server/routes/oidc/token.post.js +20 -5
- 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 +7 -2
- package/dist/runtime/server/utils/oauth-applications.js +39 -12
- package/dist/runtime/server/utils/oauth-authorize.d.ts +8 -4
- package/dist/runtime/server/utils/oauth-authorize.js +10 -8
- package/dist/runtime/server/utils/oauth-config.d.ts +5 -0
- package/dist/runtime/server/utils/oauth-config.js +5 -0
- 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 +1 -0
- package/dist/runtime/utils/index.js +1 -0
- package/dist/types.d.mts +1 -1
- package/package.json +14 -9
|
@@ -14,11 +14,15 @@ export declare const authorizeRequestSchema: z.ZodObject<{
|
|
|
14
14
|
}, z.core.$strip>;
|
|
15
15
|
export type AuthorizeRequest = z.infer<typeof authorizeRequestSchema>;
|
|
16
16
|
/**
|
|
17
|
-
* Parses
|
|
18
|
-
*
|
|
19
|
-
* be trusted to carry them yet.
|
|
17
|
+
* Parses the authorization request parameters (a query or a body). Failures are a
|
|
18
|
+
* plain 400 `bad_request` naming the bad parameters, because the redirect URI cannot be trusted to carry them yet.
|
|
20
19
|
*/
|
|
21
|
-
export declare function
|
|
20
|
+
export declare function parseAuthorizeRequest(input: unknown): AuthorizeRequest;
|
|
21
|
+
/**
|
|
22
|
+
* Validates a parsed authorization request: known, active client, an allowed redirect URI and the scopes (PKCE with
|
|
23
|
+
* S256 is enforced by the schema). Failures are plain HTTP errors because the redirect URI cannot be trusted yet.
|
|
24
|
+
*/
|
|
25
|
+
export declare function validateAuthorizeRequest(request: AuthorizeRequest): Promise<{
|
|
22
26
|
request: AuthorizeRequest;
|
|
23
27
|
application: OAuthApplication;
|
|
24
28
|
scope: string[];
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
|
+
import { badRequest, forbidden } from "@frigear-nu/module-kit/server";
|
|
2
3
|
import { resolveScope, validateRedirectUri } from "../../utils/index.js";
|
|
3
4
|
export const authorizeRequestSchema = z.object({
|
|
4
5
|
client_id: z.string().min(1),
|
|
@@ -10,22 +11,23 @@ export const authorizeRequestSchema = z.object({
|
|
|
10
11
|
scope: z.string().max(1024).optional(),
|
|
11
12
|
nonce: z.string().max(512).optional()
|
|
12
13
|
});
|
|
13
|
-
|
|
14
|
-
export async function validateAuthorizeRequest(input) {
|
|
14
|
+
export function parseAuthorizeRequest(input) {
|
|
15
15
|
const parsed = authorizeRequestSchema.safeParse(input);
|
|
16
|
-
if (!parsed.success) throw
|
|
17
|
-
|
|
16
|
+
if (!parsed.success) throw badRequest(`Invalid authorization request: ${parsed.error.issues.map((issue) => `${issue.path.join(".")} ${issue.message}`).join(", ")}`);
|
|
17
|
+
return parsed.data;
|
|
18
|
+
}
|
|
19
|
+
export async function validateAuthorizeRequest(request) {
|
|
18
20
|
const application = await getApplication(request.client_id);
|
|
19
|
-
if (!application || !application.isActive) throw
|
|
21
|
+
if (!application || !application.isActive) throw badRequest("Unknown or disabled application.");
|
|
20
22
|
const valid = validateRedirectUri(request.redirect_uri, { ...application, callbackPath: useOAuthConfig().callbackPath, allowNativeRedirects: application.clientType === "public" });
|
|
21
|
-
if (!valid) throw
|
|
23
|
+
if (!valid) throw badRequest("The redirect_uri is not allowed for this application.");
|
|
22
24
|
const scope = resolveScope(request.scope, getSupportedScopes());
|
|
23
|
-
if (scope.length === 0) throw
|
|
25
|
+
if (scope.length === 0) throw badRequest("None of the requested scopes is supported.");
|
|
24
26
|
return { request, application, scope };
|
|
25
27
|
}
|
|
26
28
|
export async function assertApplicationAccess(event, user, application) {
|
|
27
29
|
const access = await checkApplicationAccess(user, application, event);
|
|
28
|
-
if (!access.allowed) throw
|
|
30
|
+
if (!access.allowed) throw forbidden(access.reason ?? "You do not have access to this application.");
|
|
29
31
|
}
|
|
30
32
|
export function getSignInPath() {
|
|
31
33
|
const config = useRuntimeConfig();
|
|
@@ -19,3 +19,8 @@ export declare function oauthError(event: H3Event, statusCode: number, error: st
|
|
|
19
19
|
error_description?: string | undefined;
|
|
20
20
|
error: string;
|
|
21
21
|
};
|
|
22
|
+
/**
|
|
23
|
+
* The `error_description` for a token or revocation request whose body failed `readValidatedBody` (h3's 400 with the
|
|
24
|
+
* zod issues in `data`, or unparsable JSON), so the route can still answer the RFC 6749 `invalid_request` body.
|
|
25
|
+
*/
|
|
26
|
+
export declare function invalidRequestDescription(error: unknown): string;
|
|
@@ -24,3 +24,8 @@ export function oauthError(event, statusCode, error, description) {
|
|
|
24
24
|
setResponseHeader(event, "Cache-Control", "no-store");
|
|
25
25
|
return { error, ...description ? { error_description: description } : {} };
|
|
26
26
|
}
|
|
27
|
+
export function invalidRequestDescription(error) {
|
|
28
|
+
const issues = error?.data?.issues;
|
|
29
|
+
if (!Array.isArray(issues) || !issues.length) return "The request body is malformed.";
|
|
30
|
+
return `Invalid request: ${issues.map((issue) => [issue.path?.map(String).join("."), issue.message].filter(Boolean).join(" ")).join(", ")}`;
|
|
31
|
+
}
|
|
@@ -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,4 +1,5 @@
|
|
|
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";
|
|
4
5
|
/** `confidential` clients hold a secret; `public` clients (native and single-page apps) cannot and rely on PKCE alone. */
|
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 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'
|
|
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.
|
|
3
|
+
"version": "0.2.0",
|
|
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.
|
|
41
|
-
"@frigear-nu/module-kit": "1.
|
|
42
|
-
"@frigear-nu/ui": "0.
|
|
43
|
-
"@frigear-nu/auth": "0.
|
|
44
|
-
"@frigear-nu/mcp": "0.
|
|
45
|
-
"@frigear-nu/users": "1.
|
|
45
|
+
"@frigear-nu/admin": "0.2.0",
|
|
46
|
+
"@frigear-nu/module-kit": "1.2.0",
|
|
47
|
+
"@frigear-nu/ui": "0.2.0",
|
|
48
|
+
"@frigear-nu/auth": "0.2.0",
|
|
49
|
+
"@frigear-nu/mcp": "0.2.0",
|
|
50
|
+
"@frigear-nu/users": "1.2.0",
|
|
46
51
|
"@libsql/client": "^0.18.0",
|
|
47
52
|
"@nuxt/kit": "^4.6.1",
|
|
48
53
|
"drizzle-kit": "^0.31.11",
|
|
@@ -52,7 +57,7 @@
|
|
|
52
57
|
"zod": "^4.6.5"
|
|
53
58
|
},
|
|
54
59
|
"peerDependencies": {
|
|
55
|
-
"@frigear-nu/menu": "0.
|
|
60
|
+
"@frigear-nu/menu": "0.2.0",
|
|
56
61
|
"@nuxt/ui": "^4.12.0",
|
|
57
62
|
"@nuxthub/core": "^0.10.8"
|
|
58
63
|
},
|
|
@@ -65,7 +70,7 @@
|
|
|
65
70
|
}
|
|
66
71
|
},
|
|
67
72
|
"devDependencies": {
|
|
68
|
-
"@frigear-nu/menu": "0.
|
|
73
|
+
"@frigear-nu/menu": "0.2.0",
|
|
69
74
|
"@nuxt/devtools": "^3.4.2",
|
|
70
75
|
"@nuxt/eslint-config": "^1.17.0",
|
|
71
76
|
"@nuxt/module-builder": "^1.0.3",
|