@codelitdev/oauth-server-kit 0.1.0-alpha.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/LICENSE +21 -0
- package/README.md +98 -0
- package/dist/better-auth.d.mts +40 -0
- package/dist/better-auth.d.ts +40 -0
- package/dist/better-auth.js +138 -0
- package/dist/better-auth.mjs +120 -0
- package/dist/chunk-I4E63NIC.mjs +21 -0
- package/dist/chunk-RJ4ABQXG.mjs +86 -0
- package/dist/express.d.mts +36 -0
- package/dist/express.d.ts +36 -0
- package/dist/express.js +366 -0
- package/dist/express.mjs +262 -0
- package/dist/index.d.mts +17 -0
- package/dist/index.d.ts +17 -0
- package/dist/index.js +106 -0
- package/dist/index.mjs +2 -0
- package/dist/mcp.d.mts +24 -0
- package/dist/mcp.d.ts +24 -0
- package/dist/mcp.js +228 -0
- package/dist/mcp.mjs +208 -0
- package/dist/types-DJxCmhYu.d.mts +33 -0
- package/dist/types-DJxCmhYu.d.ts +33 -0
- package/package.json +101 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 CodeLit
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
# @codelitdev/oauth-server-kit
|
|
2
|
+
|
|
3
|
+
A lean Better Auth authentication library for CodeLit products. It provides
|
|
4
|
+
hosted login/consent pages, neutral session and OAuth identities, Express
|
|
5
|
+
bearer middleware, and MCP OAuth discovery.
|
|
6
|
+
|
|
7
|
+
The library authenticates people and OAuth clients. Each product continues to
|
|
8
|
+
own its users-to-actor mapping, organizations, teams, memberships, roles,
|
|
9
|
+
permissions, API keys, and credential precedence.
|
|
10
|
+
|
|
11
|
+
## Install
|
|
12
|
+
|
|
13
|
+
The package is currently released through npm's `alpha` channel:
|
|
14
|
+
|
|
15
|
+
```sh
|
|
16
|
+
pnpm add @codelitdev/oauth-server-kit@alpha
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
It has a pre-1.0 public API. Pin an explicit version for production consumers
|
|
20
|
+
and upgrade deliberately.
|
|
21
|
+
|
|
22
|
+
## Supported surfaces
|
|
23
|
+
|
|
24
|
+
- Web apps use product-local Better Auth sessions.
|
|
25
|
+
- Mobile apps use Authorization Code with S256 PKCE and a statically
|
|
26
|
+
registered public client.
|
|
27
|
+
- REST APIs use OAuth bearer tokens through neutral `req.auth`.
|
|
28
|
+
- MCP servers use the same bearer verification plus RFC 9728 discovery and a
|
|
29
|
+
standard `WWW-Authenticate` challenge.
|
|
30
|
+
|
|
31
|
+
Every product deploys its own Better Auth issuer, user store, sessions, OAuth
|
|
32
|
+
clients, signing keys, and provider configuration. The package is not a shared
|
|
33
|
+
CodeLit SSO service.
|
|
34
|
+
|
|
35
|
+
## Entry points
|
|
36
|
+
|
|
37
|
+
```ts
|
|
38
|
+
import {
|
|
39
|
+
verifyOAuthAccessToken,
|
|
40
|
+
type AuthenticatedIdentity,
|
|
41
|
+
} from "@codelitdev/oauth-server-kit";
|
|
42
|
+
|
|
43
|
+
import {
|
|
44
|
+
createOAuthProviderOptions,
|
|
45
|
+
resolveBetterAuthSession,
|
|
46
|
+
} from "@codelitdev/oauth-server-kit/better-auth";
|
|
47
|
+
|
|
48
|
+
import {
|
|
49
|
+
createOAuthBearerMiddleware,
|
|
50
|
+
createOAuthPagesRouter,
|
|
51
|
+
} from "@codelitdev/oauth-server-kit/express";
|
|
52
|
+
|
|
53
|
+
import { createMcpOAuthDiscoveryRoutes } from "@codelitdev/oauth-server-kit/mcp";
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
The root entry point does not load Express, a database library, or product
|
|
57
|
+
modules. Better Auth persistence and schema ownership remain in the consuming
|
|
58
|
+
application.
|
|
59
|
+
|
|
60
|
+
## Secure defaults
|
|
61
|
+
|
|
62
|
+
- OAuth Dynamic Client Registration is disabled unless explicitly enabled.
|
|
63
|
+
- Unauthenticated DCR requires a separate explicit opt-in.
|
|
64
|
+
- Issuer and audience/resource are verified for every bearer token.
|
|
65
|
+
- Invalid explicit bearer tokens fail closed.
|
|
66
|
+
- Ordinary-login redirects are restricted to configured origins.
|
|
67
|
+
- Hosted pages reject framing and clear configured stale host-only cookies at
|
|
68
|
+
both `/login` and `/oauth/login`.
|
|
69
|
+
- Product claims such as `team_id`, roles, or permissions are not returned as
|
|
70
|
+
trusted package identity fields.
|
|
71
|
+
|
|
72
|
+
See [docs/integration.md](docs/integration.md) for full web, mobile, REST, and
|
|
73
|
+
MCP setup, and [docs/architecture.md](docs/architecture.md) for the normative
|
|
74
|
+
product requirements and public contract.
|
|
75
|
+
|
|
76
|
+
## Development gates
|
|
77
|
+
|
|
78
|
+
```bash
|
|
79
|
+
pnpm check-types
|
|
80
|
+
pnpm test
|
|
81
|
+
pnpm test:coverage
|
|
82
|
+
pnpm build
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
The coverage release gate is 90% statements, lines, and functions and 80%
|
|
86
|
+
branches. The suite includes a real Better Auth OAuth Provider flow with a
|
|
87
|
+
public client, S256 PKCE, refresh, JWKS verification, and MCP discovery.
|
|
88
|
+
|
|
89
|
+
## Releasing an alpha
|
|
90
|
+
|
|
91
|
+
After updating the package version, run:
|
|
92
|
+
|
|
93
|
+
```sh
|
|
94
|
+
npm run release:alpha -- --otp=<npm-otp>
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
The command validates formatting, linting, types, tests, coverage, and the
|
|
98
|
+
build before publishing to npm with the `alpha` dist-tag.
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { OAuthOptions } from '@better-auth/oauth-provider';
|
|
2
|
+
import { A as AuthenticationResult } from './types-DJxCmhYu.mjs';
|
|
3
|
+
import '@better-auth/oauth-provider/resource-client';
|
|
4
|
+
|
|
5
|
+
type BetterAuthSession = {
|
|
6
|
+
user: {
|
|
7
|
+
id: string;
|
|
8
|
+
email: string;
|
|
9
|
+
name?: string | null;
|
|
10
|
+
};
|
|
11
|
+
session: {
|
|
12
|
+
id: string;
|
|
13
|
+
expiresAt: Date;
|
|
14
|
+
};
|
|
15
|
+
};
|
|
16
|
+
interface BetterAuthSessionApi {
|
|
17
|
+
api: {
|
|
18
|
+
getSession(input: {
|
|
19
|
+
headers: Headers;
|
|
20
|
+
}): Promise<BetterAuthSession | null>;
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
interface ResolveBetterAuthSessionOptions {
|
|
24
|
+
auth: BetterAuthSessionApi;
|
|
25
|
+
issuer: string;
|
|
26
|
+
}
|
|
27
|
+
declare function resolveBetterAuthSession(options: ResolveBetterAuthSessionOptions, headers: Headers): Promise<AuthenticationResult>;
|
|
28
|
+
interface CreateOAuthProviderOptionsInput {
|
|
29
|
+
loginPage: string;
|
|
30
|
+
consentPage: string;
|
|
31
|
+
scopes: readonly string[];
|
|
32
|
+
validAudiences: readonly string[];
|
|
33
|
+
allowDynamicClientRegistration?: boolean;
|
|
34
|
+
allowUnauthenticatedDynamicClientRegistration?: boolean;
|
|
35
|
+
clientRegistrationDefaultScopes?: readonly string[];
|
|
36
|
+
clientRegistrationAllowedScopes?: readonly string[];
|
|
37
|
+
}
|
|
38
|
+
declare function createOAuthProviderOptions(input: CreateOAuthProviderOptionsInput): OAuthOptions<string[]>;
|
|
39
|
+
|
|
40
|
+
export { type BetterAuthSession, type BetterAuthSessionApi, type CreateOAuthProviderOptionsInput, type ResolveBetterAuthSessionOptions, createOAuthProviderOptions, resolveBetterAuthSession };
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { OAuthOptions } from '@better-auth/oauth-provider';
|
|
2
|
+
import { A as AuthenticationResult } from './types-DJxCmhYu.js';
|
|
3
|
+
import '@better-auth/oauth-provider/resource-client';
|
|
4
|
+
|
|
5
|
+
type BetterAuthSession = {
|
|
6
|
+
user: {
|
|
7
|
+
id: string;
|
|
8
|
+
email: string;
|
|
9
|
+
name?: string | null;
|
|
10
|
+
};
|
|
11
|
+
session: {
|
|
12
|
+
id: string;
|
|
13
|
+
expiresAt: Date;
|
|
14
|
+
};
|
|
15
|
+
};
|
|
16
|
+
interface BetterAuthSessionApi {
|
|
17
|
+
api: {
|
|
18
|
+
getSession(input: {
|
|
19
|
+
headers: Headers;
|
|
20
|
+
}): Promise<BetterAuthSession | null>;
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
interface ResolveBetterAuthSessionOptions {
|
|
24
|
+
auth: BetterAuthSessionApi;
|
|
25
|
+
issuer: string;
|
|
26
|
+
}
|
|
27
|
+
declare function resolveBetterAuthSession(options: ResolveBetterAuthSessionOptions, headers: Headers): Promise<AuthenticationResult>;
|
|
28
|
+
interface CreateOAuthProviderOptionsInput {
|
|
29
|
+
loginPage: string;
|
|
30
|
+
consentPage: string;
|
|
31
|
+
scopes: readonly string[];
|
|
32
|
+
validAudiences: readonly string[];
|
|
33
|
+
allowDynamicClientRegistration?: boolean;
|
|
34
|
+
allowUnauthenticatedDynamicClientRegistration?: boolean;
|
|
35
|
+
clientRegistrationDefaultScopes?: readonly string[];
|
|
36
|
+
clientRegistrationAllowedScopes?: readonly string[];
|
|
37
|
+
}
|
|
38
|
+
declare function createOAuthProviderOptions(input: CreateOAuthProviderOptionsInput): OAuthOptions<string[]>;
|
|
39
|
+
|
|
40
|
+
export { type BetterAuthSession, type BetterAuthSessionApi, type CreateOAuthProviderOptionsInput, type ResolveBetterAuthSessionOptions, createOAuthProviderOptions, resolveBetterAuthSession };
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropSymbols = Object.getOwnPropertySymbols;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __propIsEnum = Object.prototype.propertyIsEnumerable;
|
|
7
|
+
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
8
|
+
var __spreadValues = (a, b) => {
|
|
9
|
+
for (var prop in b || (b = {}))
|
|
10
|
+
if (__hasOwnProp.call(b, prop))
|
|
11
|
+
__defNormalProp(a, prop, b[prop]);
|
|
12
|
+
if (__getOwnPropSymbols)
|
|
13
|
+
for (var prop of __getOwnPropSymbols(b)) {
|
|
14
|
+
if (__propIsEnum.call(b, prop))
|
|
15
|
+
__defNormalProp(a, prop, b[prop]);
|
|
16
|
+
}
|
|
17
|
+
return a;
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
// src/better-auth.ts
|
|
21
|
+
function normalizeIssuer(value) {
|
|
22
|
+
let url;
|
|
23
|
+
try {
|
|
24
|
+
url = new URL(value);
|
|
25
|
+
} catch (e) {
|
|
26
|
+
throw new Error("issuer must be an absolute URL");
|
|
27
|
+
}
|
|
28
|
+
if (!["http:", "https:"].includes(url.protocol)) {
|
|
29
|
+
throw new Error("issuer must use http or https");
|
|
30
|
+
}
|
|
31
|
+
if (process.env.NODE_ENV === "production" && url.protocol !== "https:") {
|
|
32
|
+
throw new Error("issuer must use https in production");
|
|
33
|
+
}
|
|
34
|
+
return url.toString().replace(/\/$/, "");
|
|
35
|
+
}
|
|
36
|
+
async function resolveBetterAuthSession(options, headers) {
|
|
37
|
+
var _a;
|
|
38
|
+
const issuer = normalizeIssuer(options.issuer);
|
|
39
|
+
try {
|
|
40
|
+
const session = await options.auth.api.getSession({ headers });
|
|
41
|
+
if (!((_a = session == null ? void 0 : session.user) == null ? void 0 : _a.id) || !session.user.email) {
|
|
42
|
+
return { status: "missing" };
|
|
43
|
+
}
|
|
44
|
+
return {
|
|
45
|
+
status: "authenticated",
|
|
46
|
+
identity: {
|
|
47
|
+
method: "session",
|
|
48
|
+
issuer,
|
|
49
|
+
subject: session.user.id,
|
|
50
|
+
email: session.user.email,
|
|
51
|
+
name: session.user.name,
|
|
52
|
+
scopes: []
|
|
53
|
+
}
|
|
54
|
+
};
|
|
55
|
+
} catch (cause) {
|
|
56
|
+
return { status: "unavailable", cause };
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
function uniqueRequired(values, field) {
|
|
60
|
+
const result = [
|
|
61
|
+
...new Set(values.map((value) => value.trim()).filter(Boolean))
|
|
62
|
+
];
|
|
63
|
+
if (result.length === 0) {
|
|
64
|
+
throw new Error(`${field} must contain at least one value`);
|
|
65
|
+
}
|
|
66
|
+
return result;
|
|
67
|
+
}
|
|
68
|
+
function assertPageLocation(value, field) {
|
|
69
|
+
if (value.startsWith("/") && !value.startsWith("//")) {
|
|
70
|
+
if (/\s/.test(value)) {
|
|
71
|
+
throw new Error(
|
|
72
|
+
`${field} must be an absolute URL or absolute path`
|
|
73
|
+
);
|
|
74
|
+
}
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
let url;
|
|
78
|
+
try {
|
|
79
|
+
url = new URL(value);
|
|
80
|
+
} catch (e) {
|
|
81
|
+
throw new Error(`${field} must be an absolute URL or absolute path`);
|
|
82
|
+
}
|
|
83
|
+
if (!["http:", "https:"].includes(url.protocol)) {
|
|
84
|
+
throw new Error(`${field} must use http or https`);
|
|
85
|
+
}
|
|
86
|
+
if (process.env.NODE_ENV === "production" && url.protocol !== "https:") {
|
|
87
|
+
throw new Error(`${field} must use https in production`);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
function assertScopeSubset(values, scopes, field) {
|
|
91
|
+
if (!values) return void 0;
|
|
92
|
+
const normalized = [
|
|
93
|
+
...new Set(values.map((value) => value.trim()).filter(Boolean))
|
|
94
|
+
];
|
|
95
|
+
const invalid = normalized.filter((scope) => !scopes.includes(scope));
|
|
96
|
+
if (invalid.length > 0) {
|
|
97
|
+
throw new Error(
|
|
98
|
+
`${field} contains unsupported scopes: ${invalid.join(", ")}`
|
|
99
|
+
);
|
|
100
|
+
}
|
|
101
|
+
return normalized;
|
|
102
|
+
}
|
|
103
|
+
function createOAuthProviderOptions(input) {
|
|
104
|
+
var _a, _b;
|
|
105
|
+
assertPageLocation(input.loginPage, "loginPage");
|
|
106
|
+
assertPageLocation(input.consentPage, "consentPage");
|
|
107
|
+
const scopes = uniqueRequired(input.scopes, "scopes");
|
|
108
|
+
const validAudiences = uniqueRequired(
|
|
109
|
+
input.validAudiences,
|
|
110
|
+
"validAudiences"
|
|
111
|
+
);
|
|
112
|
+
if (input.allowUnauthenticatedDynamicClientRegistration && !input.allowDynamicClientRegistration) {
|
|
113
|
+
throw new Error(
|
|
114
|
+
"unauthenticated dynamic client registration requires dynamic client registration"
|
|
115
|
+
);
|
|
116
|
+
}
|
|
117
|
+
const clientRegistrationDefaultScopes = assertScopeSubset(
|
|
118
|
+
input.clientRegistrationDefaultScopes,
|
|
119
|
+
scopes,
|
|
120
|
+
"clientRegistrationDefaultScopes"
|
|
121
|
+
);
|
|
122
|
+
const clientRegistrationAllowedScopes = assertScopeSubset(
|
|
123
|
+
input.clientRegistrationAllowedScopes,
|
|
124
|
+
scopes,
|
|
125
|
+
"clientRegistrationAllowedScopes"
|
|
126
|
+
);
|
|
127
|
+
return __spreadValues(__spreadValues({
|
|
128
|
+
loginPage: input.loginPage,
|
|
129
|
+
consentPage: input.consentPage,
|
|
130
|
+
scopes,
|
|
131
|
+
validAudiences,
|
|
132
|
+
allowDynamicClientRegistration: (_a = input.allowDynamicClientRegistration) != null ? _a : false,
|
|
133
|
+
allowUnauthenticatedClientRegistration: (_b = input.allowUnauthenticatedDynamicClientRegistration) != null ? _b : false
|
|
134
|
+
}, clientRegistrationDefaultScopes ? { clientRegistrationDefaultScopes } : {}), clientRegistrationAllowedScopes ? { clientRegistrationAllowedScopes } : {});
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
exports.createOAuthProviderOptions = createOAuthProviderOptions;
|
|
138
|
+
exports.resolveBetterAuthSession = resolveBetterAuthSession;
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import { __spreadValues } from './chunk-I4E63NIC.mjs';
|
|
2
|
+
|
|
3
|
+
// src/better-auth.ts
|
|
4
|
+
function normalizeIssuer(value) {
|
|
5
|
+
let url;
|
|
6
|
+
try {
|
|
7
|
+
url = new URL(value);
|
|
8
|
+
} catch (e) {
|
|
9
|
+
throw new Error("issuer must be an absolute URL");
|
|
10
|
+
}
|
|
11
|
+
if (!["http:", "https:"].includes(url.protocol)) {
|
|
12
|
+
throw new Error("issuer must use http or https");
|
|
13
|
+
}
|
|
14
|
+
if (process.env.NODE_ENV === "production" && url.protocol !== "https:") {
|
|
15
|
+
throw new Error("issuer must use https in production");
|
|
16
|
+
}
|
|
17
|
+
return url.toString().replace(/\/$/, "");
|
|
18
|
+
}
|
|
19
|
+
async function resolveBetterAuthSession(options, headers) {
|
|
20
|
+
var _a;
|
|
21
|
+
const issuer = normalizeIssuer(options.issuer);
|
|
22
|
+
try {
|
|
23
|
+
const session = await options.auth.api.getSession({ headers });
|
|
24
|
+
if (!((_a = session == null ? void 0 : session.user) == null ? void 0 : _a.id) || !session.user.email) {
|
|
25
|
+
return { status: "missing" };
|
|
26
|
+
}
|
|
27
|
+
return {
|
|
28
|
+
status: "authenticated",
|
|
29
|
+
identity: {
|
|
30
|
+
method: "session",
|
|
31
|
+
issuer,
|
|
32
|
+
subject: session.user.id,
|
|
33
|
+
email: session.user.email,
|
|
34
|
+
name: session.user.name,
|
|
35
|
+
scopes: []
|
|
36
|
+
}
|
|
37
|
+
};
|
|
38
|
+
} catch (cause) {
|
|
39
|
+
return { status: "unavailable", cause };
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
function uniqueRequired(values, field) {
|
|
43
|
+
const result = [
|
|
44
|
+
...new Set(values.map((value) => value.trim()).filter(Boolean))
|
|
45
|
+
];
|
|
46
|
+
if (result.length === 0) {
|
|
47
|
+
throw new Error(`${field} must contain at least one value`);
|
|
48
|
+
}
|
|
49
|
+
return result;
|
|
50
|
+
}
|
|
51
|
+
function assertPageLocation(value, field) {
|
|
52
|
+
if (value.startsWith("/") && !value.startsWith("//")) {
|
|
53
|
+
if (/\s/.test(value)) {
|
|
54
|
+
throw new Error(
|
|
55
|
+
`${field} must be an absolute URL or absolute path`
|
|
56
|
+
);
|
|
57
|
+
}
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
let url;
|
|
61
|
+
try {
|
|
62
|
+
url = new URL(value);
|
|
63
|
+
} catch (e) {
|
|
64
|
+
throw new Error(`${field} must be an absolute URL or absolute path`);
|
|
65
|
+
}
|
|
66
|
+
if (!["http:", "https:"].includes(url.protocol)) {
|
|
67
|
+
throw new Error(`${field} must use http or https`);
|
|
68
|
+
}
|
|
69
|
+
if (process.env.NODE_ENV === "production" && url.protocol !== "https:") {
|
|
70
|
+
throw new Error(`${field} must use https in production`);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
function assertScopeSubset(values, scopes, field) {
|
|
74
|
+
if (!values) return void 0;
|
|
75
|
+
const normalized = [
|
|
76
|
+
...new Set(values.map((value) => value.trim()).filter(Boolean))
|
|
77
|
+
];
|
|
78
|
+
const invalid = normalized.filter((scope) => !scopes.includes(scope));
|
|
79
|
+
if (invalid.length > 0) {
|
|
80
|
+
throw new Error(
|
|
81
|
+
`${field} contains unsupported scopes: ${invalid.join(", ")}`
|
|
82
|
+
);
|
|
83
|
+
}
|
|
84
|
+
return normalized;
|
|
85
|
+
}
|
|
86
|
+
function createOAuthProviderOptions(input) {
|
|
87
|
+
var _a, _b;
|
|
88
|
+
assertPageLocation(input.loginPage, "loginPage");
|
|
89
|
+
assertPageLocation(input.consentPage, "consentPage");
|
|
90
|
+
const scopes = uniqueRequired(input.scopes, "scopes");
|
|
91
|
+
const validAudiences = uniqueRequired(
|
|
92
|
+
input.validAudiences,
|
|
93
|
+
"validAudiences"
|
|
94
|
+
);
|
|
95
|
+
if (input.allowUnauthenticatedDynamicClientRegistration && !input.allowDynamicClientRegistration) {
|
|
96
|
+
throw new Error(
|
|
97
|
+
"unauthenticated dynamic client registration requires dynamic client registration"
|
|
98
|
+
);
|
|
99
|
+
}
|
|
100
|
+
const clientRegistrationDefaultScopes = assertScopeSubset(
|
|
101
|
+
input.clientRegistrationDefaultScopes,
|
|
102
|
+
scopes,
|
|
103
|
+
"clientRegistrationDefaultScopes"
|
|
104
|
+
);
|
|
105
|
+
const clientRegistrationAllowedScopes = assertScopeSubset(
|
|
106
|
+
input.clientRegistrationAllowedScopes,
|
|
107
|
+
scopes,
|
|
108
|
+
"clientRegistrationAllowedScopes"
|
|
109
|
+
);
|
|
110
|
+
return __spreadValues(__spreadValues({
|
|
111
|
+
loginPage: input.loginPage,
|
|
112
|
+
consentPage: input.consentPage,
|
|
113
|
+
scopes,
|
|
114
|
+
validAudiences,
|
|
115
|
+
allowDynamicClientRegistration: (_a = input.allowDynamicClientRegistration) != null ? _a : false,
|
|
116
|
+
allowUnauthenticatedClientRegistration: (_b = input.allowUnauthenticatedDynamicClientRegistration) != null ? _b : false
|
|
117
|
+
}, clientRegistrationDefaultScopes ? { clientRegistrationDefaultScopes } : {}), clientRegistrationAllowedScopes ? { clientRegistrationAllowedScopes } : {});
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
export { createOAuthProviderOptions, resolveBetterAuthSession };
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
var __defProp = Object.defineProperty;
|
|
2
|
+
var __defProps = Object.defineProperties;
|
|
3
|
+
var __getOwnPropDescs = Object.getOwnPropertyDescriptors;
|
|
4
|
+
var __getOwnPropSymbols = Object.getOwnPropertySymbols;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __propIsEnum = Object.prototype.propertyIsEnumerable;
|
|
7
|
+
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
8
|
+
var __spreadValues = (a, b) => {
|
|
9
|
+
for (var prop in b || (b = {}))
|
|
10
|
+
if (__hasOwnProp.call(b, prop))
|
|
11
|
+
__defNormalProp(a, prop, b[prop]);
|
|
12
|
+
if (__getOwnPropSymbols)
|
|
13
|
+
for (var prop of __getOwnPropSymbols(b)) {
|
|
14
|
+
if (__propIsEnum.call(b, prop))
|
|
15
|
+
__defNormalProp(a, prop, b[prop]);
|
|
16
|
+
}
|
|
17
|
+
return a;
|
|
18
|
+
};
|
|
19
|
+
var __spreadProps = (a, b) => __defProps(a, __getOwnPropDescs(b));
|
|
20
|
+
|
|
21
|
+
export { __spreadProps, __spreadValues };
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { __spreadProps, __spreadValues } from './chunk-I4E63NIC.mjs';
|
|
2
|
+
|
|
3
|
+
// src/verify-access-token.ts
|
|
4
|
+
function requireAbsoluteUrl(value, field) {
|
|
5
|
+
let url;
|
|
6
|
+
try {
|
|
7
|
+
url = new URL(value);
|
|
8
|
+
} catch (e) {
|
|
9
|
+
throw new Error(`${field} must be an absolute URL`);
|
|
10
|
+
}
|
|
11
|
+
if (!["http:", "https:"].includes(url.protocol)) {
|
|
12
|
+
throw new Error(`${field} must use http or https`);
|
|
13
|
+
}
|
|
14
|
+
if (process.env.NODE_ENV === "production" && url.protocol !== "https:") {
|
|
15
|
+
throw new Error(`${field} must use https in production`);
|
|
16
|
+
}
|
|
17
|
+
return url.toString().replace(/\/$/, "");
|
|
18
|
+
}
|
|
19
|
+
function normalizedUniqueStrings(values) {
|
|
20
|
+
return [...new Set(values.map((value) => value.trim()).filter(Boolean))];
|
|
21
|
+
}
|
|
22
|
+
function claimStrings(value) {
|
|
23
|
+
if (typeof value === "string") return [value];
|
|
24
|
+
if (!Array.isArray(value)) return [];
|
|
25
|
+
return value.filter((item) => typeof item === "string");
|
|
26
|
+
}
|
|
27
|
+
function optionalString(value) {
|
|
28
|
+
return typeof value === "string" && value.length > 0 ? value : void 0;
|
|
29
|
+
}
|
|
30
|
+
function validateOAuthVerificationOptions(options) {
|
|
31
|
+
var _a;
|
|
32
|
+
requireAbsoluteUrl(options.issuer, "issuer");
|
|
33
|
+
if (normalizedUniqueStrings(options.audiences).length === 0) {
|
|
34
|
+
throw new Error("audiences must contain at least one value");
|
|
35
|
+
}
|
|
36
|
+
for (const value of Object.values((_a = options.resourceMetadataMappings) != null ? _a : {})) {
|
|
37
|
+
requireAbsoluteUrl(value, "resourceMetadataMappings");
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
async function verifyOAuthAccessToken(options, token) {
|
|
41
|
+
var _a, _b;
|
|
42
|
+
validateOAuthVerificationOptions(options);
|
|
43
|
+
const issuer = requireAbsoluteUrl(options.issuer, "issuer");
|
|
44
|
+
const audiences = normalizedUniqueStrings(options.audiences);
|
|
45
|
+
if (!token.trim()) return { status: "invalid_token" };
|
|
46
|
+
try {
|
|
47
|
+
const claims = await options.oauthResourceClient.getActions().verifyAccessToken(token, {
|
|
48
|
+
verifyOptions: { issuer, audience: audiences },
|
|
49
|
+
resourceMetadataMappings: (_a = options.resourceMetadataMappings) != null ? _a : {}
|
|
50
|
+
});
|
|
51
|
+
const subject = optionalString(claims.sub);
|
|
52
|
+
const claimIssuer = optionalString(claims.iss);
|
|
53
|
+
const clientId = (_b = optionalString(claims.azp)) != null ? _b : optionalString(claims.client_id);
|
|
54
|
+
const tokenAudiences = normalizedUniqueStrings(
|
|
55
|
+
claimStrings(claims.aud)
|
|
56
|
+
);
|
|
57
|
+
const validatedAudiences = tokenAudiences.filter(
|
|
58
|
+
(audience) => audiences.includes(audience)
|
|
59
|
+
);
|
|
60
|
+
if (!subject || (claimIssuer == null ? void 0 : claimIssuer.replace(/\/$/, "")) !== issuer || !clientId || validatedAudiences.length === 0) {
|
|
61
|
+
return { status: "invalid_token" };
|
|
62
|
+
}
|
|
63
|
+
const scopeValue = claims.scope;
|
|
64
|
+
const scopes = normalizedUniqueStrings(
|
|
65
|
+
Array.isArray(scopeValue) ? claimStrings(scopeValue) : typeof scopeValue === "string" ? scopeValue.split(/\s+/) : []
|
|
66
|
+
);
|
|
67
|
+
const email = optionalString(claims.email);
|
|
68
|
+
const name = optionalString(claims.name);
|
|
69
|
+
return {
|
|
70
|
+
status: "authenticated",
|
|
71
|
+
identity: __spreadProps(__spreadValues(__spreadValues({
|
|
72
|
+
method: "oauth",
|
|
73
|
+
issuer,
|
|
74
|
+
subject
|
|
75
|
+
}, email ? { email } : {}), name ? { name } : {}), {
|
|
76
|
+
clientId,
|
|
77
|
+
scopes,
|
|
78
|
+
audiences: validatedAudiences
|
|
79
|
+
})
|
|
80
|
+
};
|
|
81
|
+
} catch (e) {
|
|
82
|
+
return { status: "invalid_token" };
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export { validateOAuthVerificationOptions, verifyOAuthAccessToken };
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { RequestHandler, Router } from 'express';
|
|
2
|
+
import { a as AuthenticatedIdentity } from './types-DJxCmhYu.mjs';
|
|
3
|
+
import { VerifyOAuthAccessTokenOptions } from './index.mjs';
|
|
4
|
+
import '@better-auth/oauth-provider/resource-client';
|
|
5
|
+
|
|
6
|
+
declare global {
|
|
7
|
+
namespace Express {
|
|
8
|
+
interface Request {
|
|
9
|
+
auth?: AuthenticatedIdentity;
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
interface CreateOAuthBearerMiddlewareOptions extends VerifyOAuthAccessTokenOptions {
|
|
14
|
+
resourceMetadataUrl?: string;
|
|
15
|
+
}
|
|
16
|
+
declare function createOAuthBearerMiddleware(options: CreateOAuthBearerMiddlewareOptions): RequestHandler;
|
|
17
|
+
|
|
18
|
+
type HostedLoginMethod = {
|
|
19
|
+
type: "email-otp";
|
|
20
|
+
label?: string;
|
|
21
|
+
} | {
|
|
22
|
+
type: "social";
|
|
23
|
+
providerId: string;
|
|
24
|
+
label: string;
|
|
25
|
+
};
|
|
26
|
+
interface CreateOAuthPagesRouterOptions {
|
|
27
|
+
appName: string;
|
|
28
|
+
authBasePath: string;
|
|
29
|
+
allowedRedirectOrigins: readonly string[];
|
|
30
|
+
defaultRedirectUrl: string;
|
|
31
|
+
loginMethods: readonly HostedLoginMethod[];
|
|
32
|
+
legacyHostOnlySessionCookieNames?: readonly string[];
|
|
33
|
+
}
|
|
34
|
+
declare function createOAuthPagesRouter(rawOptions: CreateOAuthPagesRouterOptions): Router;
|
|
35
|
+
|
|
36
|
+
export { type CreateOAuthBearerMiddlewareOptions, type CreateOAuthPagesRouterOptions, type HostedLoginMethod, createOAuthBearerMiddleware, createOAuthPagesRouter };
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { RequestHandler, Router } from 'express';
|
|
2
|
+
import { a as AuthenticatedIdentity } from './types-DJxCmhYu.js';
|
|
3
|
+
import { VerifyOAuthAccessTokenOptions } from './index.js';
|
|
4
|
+
import '@better-auth/oauth-provider/resource-client';
|
|
5
|
+
|
|
6
|
+
declare global {
|
|
7
|
+
namespace Express {
|
|
8
|
+
interface Request {
|
|
9
|
+
auth?: AuthenticatedIdentity;
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
interface CreateOAuthBearerMiddlewareOptions extends VerifyOAuthAccessTokenOptions {
|
|
14
|
+
resourceMetadataUrl?: string;
|
|
15
|
+
}
|
|
16
|
+
declare function createOAuthBearerMiddleware(options: CreateOAuthBearerMiddlewareOptions): RequestHandler;
|
|
17
|
+
|
|
18
|
+
type HostedLoginMethod = {
|
|
19
|
+
type: "email-otp";
|
|
20
|
+
label?: string;
|
|
21
|
+
} | {
|
|
22
|
+
type: "social";
|
|
23
|
+
providerId: string;
|
|
24
|
+
label: string;
|
|
25
|
+
};
|
|
26
|
+
interface CreateOAuthPagesRouterOptions {
|
|
27
|
+
appName: string;
|
|
28
|
+
authBasePath: string;
|
|
29
|
+
allowedRedirectOrigins: readonly string[];
|
|
30
|
+
defaultRedirectUrl: string;
|
|
31
|
+
loginMethods: readonly HostedLoginMethod[];
|
|
32
|
+
legacyHostOnlySessionCookieNames?: readonly string[];
|
|
33
|
+
}
|
|
34
|
+
declare function createOAuthPagesRouter(rawOptions: CreateOAuthPagesRouterOptions): Router;
|
|
35
|
+
|
|
36
|
+
export { type CreateOAuthBearerMiddlewareOptions, type CreateOAuthPagesRouterOptions, type HostedLoginMethod, createOAuthBearerMiddleware, createOAuthPagesRouter };
|