@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.
Files changed (113) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +572 -0
  3. package/dist/client/_generated/_ignore.d.ts +1 -0
  4. package/dist/client/_generated/_ignore.d.ts.map +1 -0
  5. package/dist/client/_generated/_ignore.js +3 -0
  6. package/dist/client/_generated/_ignore.js.map +1 -0
  7. package/dist/client/auth-config.d.ts +85 -0
  8. package/dist/client/auth-config.d.ts.map +1 -0
  9. package/dist/client/auth-config.js +81 -0
  10. package/dist/client/auth-config.js.map +1 -0
  11. package/dist/client/auth-helper.d.ts +81 -0
  12. package/dist/client/auth-helper.d.ts.map +1 -0
  13. package/dist/client/auth-helper.js +97 -0
  14. package/dist/client/auth-helper.js.map +1 -0
  15. package/dist/client/index.d.ts +189 -0
  16. package/dist/client/index.d.ts.map +1 -0
  17. package/dist/client/index.js +230 -0
  18. package/dist/client/index.js.map +1 -0
  19. package/dist/client/routes.d.ts +94 -0
  20. package/dist/client/routes.d.ts.map +1 -0
  21. package/dist/client/routes.js +113 -0
  22. package/dist/client/routes.js.map +1 -0
  23. package/dist/component/_generated/api.d.ts +44 -0
  24. package/dist/component/_generated/api.d.ts.map +1 -0
  25. package/dist/component/_generated/api.js +31 -0
  26. package/dist/component/_generated/api.js.map +1 -0
  27. package/dist/component/_generated/component.d.ts +123 -0
  28. package/dist/component/_generated/component.d.ts.map +1 -0
  29. package/dist/component/_generated/component.js +11 -0
  30. package/dist/component/_generated/component.js.map +1 -0
  31. package/dist/component/_generated/dataModel.d.ts +46 -0
  32. package/dist/component/_generated/dataModel.d.ts.map +1 -0
  33. package/dist/component/_generated/dataModel.js +11 -0
  34. package/dist/component/_generated/dataModel.js.map +1 -0
  35. package/dist/component/_generated/server.d.ts +121 -0
  36. package/dist/component/_generated/server.d.ts.map +1 -0
  37. package/dist/component/_generated/server.js +78 -0
  38. package/dist/component/_generated/server.js.map +1 -0
  39. package/dist/component/clientManagement.d.ts +39 -0
  40. package/dist/component/clientManagement.d.ts.map +1 -0
  41. package/dist/component/clientManagement.js +169 -0
  42. package/dist/component/clientManagement.js.map +1 -0
  43. package/dist/component/constants.d.ts +31 -0
  44. package/dist/component/constants.d.ts.map +1 -0
  45. package/dist/component/constants.js +36 -0
  46. package/dist/component/constants.js.map +1 -0
  47. package/dist/component/convex.config.d.ts +3 -0
  48. package/dist/component/convex.config.d.ts.map +1 -0
  49. package/dist/component/convex.config.js +3 -0
  50. package/dist/component/convex.config.js.map +1 -0
  51. package/dist/component/handlers.d.ts +143 -0
  52. package/dist/component/handlers.d.ts.map +1 -0
  53. package/dist/component/handlers.js +624 -0
  54. package/dist/component/handlers.js.map +1 -0
  55. package/dist/component/mutations.d.ts +111 -0
  56. package/dist/component/mutations.d.ts.map +1 -0
  57. package/dist/component/mutations.js +459 -0
  58. package/dist/component/mutations.js.map +1 -0
  59. package/dist/component/queries.d.ts +127 -0
  60. package/dist/component/queries.d.ts.map +1 -0
  61. package/dist/component/queries.js +145 -0
  62. package/dist/component/queries.js.map +1 -0
  63. package/dist/component/schema.d.ts +116 -0
  64. package/dist/component/schema.d.ts.map +1 -0
  65. package/dist/component/schema.js +77 -0
  66. package/dist/component/schema.js.map +1 -0
  67. package/dist/component/token_security.d.ts +53 -0
  68. package/dist/component/token_security.d.ts.map +1 -0
  69. package/dist/component/token_security.js +91 -0
  70. package/dist/component/token_security.js.map +1 -0
  71. package/dist/lib/convex-types.d.ts +21 -0
  72. package/dist/lib/convex-types.d.ts.map +1 -0
  73. package/dist/lib/convex-types.js +2 -0
  74. package/dist/lib/convex-types.js.map +1 -0
  75. package/dist/lib/oauth.d.ts +123 -0
  76. package/dist/lib/oauth.d.ts.map +1 -0
  77. package/dist/lib/oauth.js +295 -0
  78. package/dist/lib/oauth.js.map +1 -0
  79. package/dist/react/index.d.ts +2 -0
  80. package/dist/react/index.d.ts.map +1 -0
  81. package/dist/react/index.js +6 -0
  82. package/dist/react/index.js.map +1 -0
  83. package/package.json +121 -0
  84. package/src/client/__tests__/auth-config.test.ts +244 -0
  85. package/src/client/__tests__/auth-helper.test.ts +273 -0
  86. package/src/client/__tests__/oauth-provider.test.ts +418 -0
  87. package/src/client/__tests__/routes.test.ts +428 -0
  88. package/src/client/_generated/_ignore.ts +1 -0
  89. package/src/client/auth-config.ts +157 -0
  90. package/src/client/auth-helper.ts +201 -0
  91. package/src/client/index.ts +326 -0
  92. package/src/client/routes.ts +251 -0
  93. package/src/component/__tests__/oauth.test.ts +3310 -0
  94. package/src/component/__tests__/rfc-compliance.test.ts +788 -0
  95. package/src/component/__tests__/token-security.test.ts +133 -0
  96. package/src/component/_generated/api.ts +60 -0
  97. package/src/component/_generated/component.ts +201 -0
  98. package/src/component/_generated/dataModel.ts +60 -0
  99. package/src/component/_generated/server.ts +156 -0
  100. package/src/component/clientManagement.ts +189 -0
  101. package/src/component/constants.ts +40 -0
  102. package/src/component/convex.config.ts +3 -0
  103. package/src/component/handlers.ts +964 -0
  104. package/src/component/mutations.ts +531 -0
  105. package/src/component/queries.ts +165 -0
  106. package/src/component/schema.ts +92 -0
  107. package/src/component/token_security.ts +102 -0
  108. package/src/lib/__tests__/oauth-helpers.test.ts +143 -0
  109. package/src/lib/__tests__/oauth-jwt.test.ts +405 -0
  110. package/src/lib/convex-types.ts +37 -0
  111. package/src/lib/oauth.ts +412 -0
  112. package/src/react/index.ts +7 -0
  113. 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
+ }