@codefox-inc/oauth-provider 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/LICENSE +201 -0
- package/README.md +572 -0
- package/dist/client/_generated/_ignore.d.ts +1 -0
- package/dist/client/_generated/_ignore.d.ts.map +1 -0
- package/dist/client/_generated/_ignore.js +3 -0
- package/dist/client/_generated/_ignore.js.map +1 -0
- package/dist/client/auth-config.d.ts +85 -0
- package/dist/client/auth-config.d.ts.map +1 -0
- package/dist/client/auth-config.js +81 -0
- package/dist/client/auth-config.js.map +1 -0
- package/dist/client/auth-helper.d.ts +81 -0
- package/dist/client/auth-helper.d.ts.map +1 -0
- package/dist/client/auth-helper.js +97 -0
- package/dist/client/auth-helper.js.map +1 -0
- package/dist/client/index.d.ts +189 -0
- package/dist/client/index.d.ts.map +1 -0
- package/dist/client/index.js +230 -0
- package/dist/client/index.js.map +1 -0
- package/dist/client/routes.d.ts +94 -0
- package/dist/client/routes.d.ts.map +1 -0
- package/dist/client/routes.js +113 -0
- package/dist/client/routes.js.map +1 -0
- package/dist/component/_generated/api.d.ts +44 -0
- package/dist/component/_generated/api.d.ts.map +1 -0
- package/dist/component/_generated/api.js +31 -0
- package/dist/component/_generated/api.js.map +1 -0
- package/dist/component/_generated/component.d.ts +123 -0
- package/dist/component/_generated/component.d.ts.map +1 -0
- package/dist/component/_generated/component.js +11 -0
- package/dist/component/_generated/component.js.map +1 -0
- package/dist/component/_generated/dataModel.d.ts +46 -0
- package/dist/component/_generated/dataModel.d.ts.map +1 -0
- package/dist/component/_generated/dataModel.js +11 -0
- package/dist/component/_generated/dataModel.js.map +1 -0
- package/dist/component/_generated/server.d.ts +121 -0
- package/dist/component/_generated/server.d.ts.map +1 -0
- package/dist/component/_generated/server.js +78 -0
- package/dist/component/_generated/server.js.map +1 -0
- package/dist/component/clientManagement.d.ts +39 -0
- package/dist/component/clientManagement.d.ts.map +1 -0
- package/dist/component/clientManagement.js +169 -0
- package/dist/component/clientManagement.js.map +1 -0
- package/dist/component/constants.d.ts +31 -0
- package/dist/component/constants.d.ts.map +1 -0
- package/dist/component/constants.js +36 -0
- package/dist/component/constants.js.map +1 -0
- package/dist/component/convex.config.d.ts +3 -0
- package/dist/component/convex.config.d.ts.map +1 -0
- package/dist/component/convex.config.js +3 -0
- package/dist/component/convex.config.js.map +1 -0
- package/dist/component/handlers.d.ts +143 -0
- package/dist/component/handlers.d.ts.map +1 -0
- package/dist/component/handlers.js +624 -0
- package/dist/component/handlers.js.map +1 -0
- package/dist/component/mutations.d.ts +111 -0
- package/dist/component/mutations.d.ts.map +1 -0
- package/dist/component/mutations.js +459 -0
- package/dist/component/mutations.js.map +1 -0
- package/dist/component/queries.d.ts +127 -0
- package/dist/component/queries.d.ts.map +1 -0
- package/dist/component/queries.js +145 -0
- package/dist/component/queries.js.map +1 -0
- package/dist/component/schema.d.ts +116 -0
- package/dist/component/schema.d.ts.map +1 -0
- package/dist/component/schema.js +77 -0
- package/dist/component/schema.js.map +1 -0
- package/dist/component/token_security.d.ts +53 -0
- package/dist/component/token_security.d.ts.map +1 -0
- package/dist/component/token_security.js +91 -0
- package/dist/component/token_security.js.map +1 -0
- package/dist/lib/convex-types.d.ts +21 -0
- package/dist/lib/convex-types.d.ts.map +1 -0
- package/dist/lib/convex-types.js +2 -0
- package/dist/lib/convex-types.js.map +1 -0
- package/dist/lib/oauth.d.ts +123 -0
- package/dist/lib/oauth.d.ts.map +1 -0
- package/dist/lib/oauth.js +295 -0
- package/dist/lib/oauth.js.map +1 -0
- package/dist/react/index.d.ts +2 -0
- package/dist/react/index.d.ts.map +1 -0
- package/dist/react/index.js +6 -0
- package/dist/react/index.js.map +1 -0
- package/package.json +121 -0
- package/src/client/__tests__/auth-config.test.ts +244 -0
- package/src/client/__tests__/auth-helper.test.ts +273 -0
- package/src/client/__tests__/oauth-provider.test.ts +418 -0
- package/src/client/__tests__/routes.test.ts +428 -0
- package/src/client/_generated/_ignore.ts +1 -0
- package/src/client/auth-config.ts +157 -0
- package/src/client/auth-helper.ts +201 -0
- package/src/client/index.ts +326 -0
- package/src/client/routes.ts +251 -0
- package/src/component/__tests__/oauth.test.ts +3310 -0
- package/src/component/__tests__/rfc-compliance.test.ts +788 -0
- package/src/component/__tests__/token-security.test.ts +133 -0
- package/src/component/_generated/api.ts +60 -0
- package/src/component/_generated/component.ts +201 -0
- package/src/component/_generated/dataModel.ts +60 -0
- package/src/component/_generated/server.ts +156 -0
- package/src/component/clientManagement.ts +189 -0
- package/src/component/constants.ts +40 -0
- package/src/component/convex.config.ts +3 -0
- package/src/component/handlers.ts +964 -0
- package/src/component/mutations.ts +531 -0
- package/src/component/queries.ts +165 -0
- package/src/component/schema.ts +92 -0
- package/src/component/token_security.ts +102 -0
- package/src/lib/__tests__/oauth-helpers.test.ts +143 -0
- package/src/lib/__tests__/oauth-jwt.test.ts +405 -0
- package/src/lib/convex-types.ts +37 -0
- package/src/lib/oauth.ts +412 -0
- package/src/react/index.ts +7 -0
- package/src/test.ts +21 -0
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OAuth Route Registration Helper
|
|
3
|
+
*
|
|
4
|
+
* Simplifies registering all OAuth endpoints in http.ts
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type { OAuthProvider } from "./index.js";
|
|
8
|
+
import { normalizePrefix } from "../lib/oauth.js";
|
|
9
|
+
import type { UserProfile } from "../lib/oauth.js";
|
|
10
|
+
import type { RunActionCtx, RunQueryCtx } from "../lib/convex-types.js";
|
|
11
|
+
import type { Auth } from "convex/server";
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* HTTP Router interface (compatible with Convex httpRouter)
|
|
15
|
+
*/
|
|
16
|
+
interface HttpRouter {
|
|
17
|
+
route: (config: {
|
|
18
|
+
path: string;
|
|
19
|
+
method: "GET" | "POST" | "PUT" | "PATCH" | "DELETE" | "OPTIONS";
|
|
20
|
+
|
|
21
|
+
handler: any;
|
|
22
|
+
}) => void;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* HTTP Action creator function
|
|
27
|
+
* Note: Actual Convex ActionCtx extends RunActionCtx with additional HTTP-specific properties
|
|
28
|
+
*/
|
|
29
|
+
type HttpActionCreator = (handler: (ctx: RunActionCtx & { auth: Auth }, request: Request) => Promise<Response>) => unknown;
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Options for registering OAuth routes
|
|
33
|
+
*/
|
|
34
|
+
export interface RegisterOAuthRoutesOptions {
|
|
35
|
+
/**
|
|
36
|
+
* URL prefix for OAuth endpoints
|
|
37
|
+
* @default "/oauth"
|
|
38
|
+
*/
|
|
39
|
+
prefix?: string;
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Callback to get user profile for UserInfo endpoint
|
|
43
|
+
* Receives ctx for DB access (e.g., ctx.runQuery)
|
|
44
|
+
* If not provided, UserInfo endpoint returns only { sub: userId }
|
|
45
|
+
*/
|
|
46
|
+
getUserProfile?: (ctx: RunQueryCtx, userId: string) => Promise<UserProfile | null>;
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Custom authorize handler for authentication check before consent
|
|
50
|
+
* If not provided, simply redirects to consent page
|
|
51
|
+
*
|
|
52
|
+
* @example
|
|
53
|
+
* ```typescript
|
|
54
|
+
* authorizeHandler: async (ctx, request, defaultRedirect) => {
|
|
55
|
+
* const identity = await ctx.auth.getUserIdentity();
|
|
56
|
+
* if (!identity) {
|
|
57
|
+
* const loginUrl = new URL(`${siteUrl}/login`);
|
|
58
|
+
* loginUrl.searchParams.set("returnTo", request.url);
|
|
59
|
+
* return Response.redirect(loginUrl.toString());
|
|
60
|
+
* }
|
|
61
|
+
* return defaultRedirect();
|
|
62
|
+
* }
|
|
63
|
+
* ```
|
|
64
|
+
*/
|
|
65
|
+
authorizeHandler?: (
|
|
66
|
+
ctx: RunActionCtx & { auth: Auth },
|
|
67
|
+
request: Request,
|
|
68
|
+
defaultAuthorize: () => Promise<Response>
|
|
69
|
+
) => Promise<Response>;
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* SITE_URL for authorize redirect
|
|
73
|
+
*/
|
|
74
|
+
siteUrl?: string;
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Also register routes without /oauth prefix for RFC 8414 compatibility
|
|
78
|
+
* @default true
|
|
79
|
+
*/
|
|
80
|
+
registerRootWellKnown?: boolean;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Register all OAuth routes on an HTTP router
|
|
85
|
+
*
|
|
86
|
+
* @example
|
|
87
|
+
* ```typescript
|
|
88
|
+
* import { httpRouter } from "convex/server";
|
|
89
|
+
* import { httpAction } from "./_generated/server";
|
|
90
|
+
* import { OAuthProvider, registerOAuthRoutes } from "@codefox-inc/oauth-provider";
|
|
91
|
+
*
|
|
92
|
+
* const http = httpRouter();
|
|
93
|
+
* const oauthProvider = new OAuthProvider(components.oauthProvider, config);
|
|
94
|
+
*
|
|
95
|
+
* registerOAuthRoutes(http, httpAction, oauthProvider, {
|
|
96
|
+
* siteUrl: process.env.SITE_URL,
|
|
97
|
+
* getUserProfile: async (userId) => ({ sub: userId, name: "User" }),
|
|
98
|
+
* });
|
|
99
|
+
*
|
|
100
|
+
* export default http;
|
|
101
|
+
* ```
|
|
102
|
+
*/
|
|
103
|
+
export function registerOAuthRoutes(
|
|
104
|
+
http: HttpRouter,
|
|
105
|
+
httpAction: HttpActionCreator,
|
|
106
|
+
oauthProvider: OAuthProvider,
|
|
107
|
+
options: RegisterOAuthRoutesOptions = {}
|
|
108
|
+
): void {
|
|
109
|
+
const baseConfig = oauthProvider.getConfig?.();
|
|
110
|
+
const prefix = normalizePrefix(options.prefix ?? baseConfig?.prefix);
|
|
111
|
+
const {
|
|
112
|
+
getUserProfile,
|
|
113
|
+
authorizeHandler,
|
|
114
|
+
siteUrl: _siteUrl = "http://localhost:5173",
|
|
115
|
+
registerRootWellKnown = true,
|
|
116
|
+
} = options;
|
|
117
|
+
|
|
118
|
+
const handlers = oauthProvider.handlers;
|
|
119
|
+
|
|
120
|
+
// Helper to register GET + OPTIONS for a path
|
|
121
|
+
const registerGetEndpoint = (
|
|
122
|
+
path: string,
|
|
123
|
+
handler: (ctx: RunActionCtx, req: Request) => Promise<Response>
|
|
124
|
+
) => {
|
|
125
|
+
http.route({
|
|
126
|
+
path,
|
|
127
|
+
method: "GET",
|
|
128
|
+
handler: httpAction((ctx, req) => handler(ctx, req)),
|
|
129
|
+
});
|
|
130
|
+
http.route({
|
|
131
|
+
path,
|
|
132
|
+
method: "OPTIONS",
|
|
133
|
+
handler: httpAction((ctx, req) => handler(ctx, req)),
|
|
134
|
+
});
|
|
135
|
+
};
|
|
136
|
+
|
|
137
|
+
// Helper to register POST + OPTIONS for a path
|
|
138
|
+
const registerPostEndpoint = (
|
|
139
|
+
path: string,
|
|
140
|
+
handler: (ctx: RunActionCtx, req: Request) => Promise<Response>
|
|
141
|
+
) => {
|
|
142
|
+
http.route({
|
|
143
|
+
path,
|
|
144
|
+
method: "POST",
|
|
145
|
+
handler: httpAction((ctx, req) => handler(ctx, req)),
|
|
146
|
+
});
|
|
147
|
+
http.route({
|
|
148
|
+
path,
|
|
149
|
+
method: "OPTIONS",
|
|
150
|
+
handler: httpAction((ctx, req) => handler(ctx, req)),
|
|
151
|
+
});
|
|
152
|
+
};
|
|
153
|
+
|
|
154
|
+
// 1. OpenID Configuration
|
|
155
|
+
registerGetEndpoint(
|
|
156
|
+
`${prefix}/.well-known/openid-configuration`,
|
|
157
|
+
(ctx, req) => handlers.openIdConfiguration(ctx, req)
|
|
158
|
+
);
|
|
159
|
+
|
|
160
|
+
// 2. OAuth Authorization Server Metadata (RFC 8414)
|
|
161
|
+
registerGetEndpoint(
|
|
162
|
+
`${prefix}/.well-known/oauth-authorization-server`,
|
|
163
|
+
(ctx, req) => handlers.openIdConfiguration(ctx, req)
|
|
164
|
+
);
|
|
165
|
+
|
|
166
|
+
// 3. JWKS
|
|
167
|
+
registerGetEndpoint(
|
|
168
|
+
`${prefix}/.well-known/jwks.json`,
|
|
169
|
+
(ctx, req) => handlers.jwks(ctx, req)
|
|
170
|
+
);
|
|
171
|
+
|
|
172
|
+
// 4. Protected Resource Metadata (RFC 9728)
|
|
173
|
+
registerGetEndpoint(
|
|
174
|
+
`${prefix}/.well-known/oauth-protected-resource`,
|
|
175
|
+
(ctx, req) => handlers.protectedResource(ctx, req)
|
|
176
|
+
);
|
|
177
|
+
|
|
178
|
+
// 5. Authorization Endpoint (redirect to frontend)
|
|
179
|
+
const authorizeEndpoint = async (ctx: RunActionCtx, request: Request) => {
|
|
180
|
+
const defaultAuthorize = () => handlers.authorize(ctx, request);
|
|
181
|
+
|
|
182
|
+
if (authorizeHandler) {
|
|
183
|
+
return authorizeHandler(ctx as RunActionCtx & { auth: Auth }, request, defaultAuthorize);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
return defaultAuthorize();
|
|
187
|
+
};
|
|
188
|
+
registerGetEndpoint(
|
|
189
|
+
`${prefix}/authorize`,
|
|
190
|
+
(ctx, req) => authorizeEndpoint(ctx, req)
|
|
191
|
+
);
|
|
192
|
+
|
|
193
|
+
// 6. Token Endpoint
|
|
194
|
+
registerPostEndpoint(
|
|
195
|
+
`${prefix}/token`,
|
|
196
|
+
(ctx, req) => handlers.token(ctx, req)
|
|
197
|
+
);
|
|
198
|
+
|
|
199
|
+
// 7. UserInfo Endpoint
|
|
200
|
+
// Wrap getUserProfile to pass ctx for DB access
|
|
201
|
+
const userInfoHandler = getUserProfile
|
|
202
|
+
? (ctx: RunActionCtx, req: Request) => handlers.userInfo(ctx, req, (userId) => getUserProfile(ctx, userId))
|
|
203
|
+
: (ctx: RunActionCtx, req: Request) => handlers.userInfo(ctx, req, async (userId) => ({ sub: userId }));
|
|
204
|
+
|
|
205
|
+
http.route({
|
|
206
|
+
path: `${prefix}/userinfo`,
|
|
207
|
+
method: "GET",
|
|
208
|
+
handler: httpAction(userInfoHandler),
|
|
209
|
+
});
|
|
210
|
+
http.route({
|
|
211
|
+
path: `${prefix}/userinfo`,
|
|
212
|
+
method: "POST",
|
|
213
|
+
handler: httpAction(userInfoHandler),
|
|
214
|
+
});
|
|
215
|
+
http.route({
|
|
216
|
+
path: `${prefix}/userinfo`,
|
|
217
|
+
method: "OPTIONS",
|
|
218
|
+
handler: httpAction(userInfoHandler),
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
// 8. Dynamic Client Registration
|
|
222
|
+
registerPostEndpoint(
|
|
223
|
+
`${prefix}/register`,
|
|
224
|
+
(ctx, req) => handlers.register(ctx, req)
|
|
225
|
+
);
|
|
226
|
+
|
|
227
|
+
// Root well-known paths (RFC 8414 compatibility)
|
|
228
|
+
if (registerRootWellKnown) {
|
|
229
|
+
// /.well-known/oauth-authorization-server
|
|
230
|
+
registerGetEndpoint(
|
|
231
|
+
"/.well-known/oauth-authorization-server",
|
|
232
|
+
(ctx, req) => handlers.openIdConfiguration(ctx, req)
|
|
233
|
+
);
|
|
234
|
+
|
|
235
|
+
// /.well-known/oauth-authorization-server{prefix} (for issuer with custom prefix)
|
|
236
|
+
// RFC 8414: If issuer is https://example.com/oauth, well-known is /.well-known/oauth-authorization-server/oauth
|
|
237
|
+
// Only register if prefix is non-empty to avoid duplicate route registration
|
|
238
|
+
if (prefix && prefix !== "/") {
|
|
239
|
+
registerGetEndpoint(
|
|
240
|
+
`/.well-known/oauth-authorization-server${prefix}`,
|
|
241
|
+
(ctx, req) => handlers.openIdConfiguration(ctx, req)
|
|
242
|
+
);
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
// /.well-known/oauth-protected-resource
|
|
246
|
+
registerGetEndpoint(
|
|
247
|
+
"/.well-known/oauth-protected-resource",
|
|
248
|
+
(ctx, req) => handlers.protectedResource(ctx, req)
|
|
249
|
+
);
|
|
250
|
+
}
|
|
251
|
+
}
|