@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,189 @@
1
+ import { v } from "convex/values";
2
+ import { mutation } from "./_generated/server";
3
+ import * as bcrypt from "bcryptjs";
4
+ import { generateClientSecret } from "../lib/oauth.js";
5
+ import { OAUTH_CONSTANTS } from "./constants";
6
+
7
+ /**
8
+ * OAuth Client Management Mutations
9
+ *
10
+ * Handles client registration, verification, and deletion.
11
+ * Uses bcryptjs (pure JavaScript implementation) for secure client secret hashing.
12
+ */
13
+
14
+ function isValidRedirectUri(uri: string): boolean {
15
+ let parsed: URL;
16
+ try {
17
+ parsed = new URL(uri);
18
+ } catch {
19
+ return false;
20
+ }
21
+
22
+ if (parsed.hash) return false;
23
+
24
+ const host = parsed.hostname.toLowerCase();
25
+ const isLoopback =
26
+ host === "localhost" ||
27
+ host === "127.0.0.1" ||
28
+ host === "::1";
29
+
30
+ if (parsed.protocol === "https:") return true;
31
+ if (parsed.protocol === "http:" && isLoopback) return true;
32
+
33
+ return false;
34
+ }
35
+
36
+ /**
37
+ * Register OAuth Client
38
+ */
39
+ export const registerClient = mutation({
40
+ args: {
41
+ name: v.string(),
42
+ redirectUris: v.array(v.string()),
43
+ scopes: v.array(v.string()),
44
+ type: v.union(v.literal("confidential"), v.literal("public")),
45
+ // metadata
46
+ description: v.optional(v.string()),
47
+ website: v.optional(v.string()),
48
+ logoUrl: v.optional(v.string()),
49
+ tosUrl: v.optional(v.string()),
50
+ policyUrl: v.optional(v.string()),
51
+ isInternal: v.optional(v.boolean()),
52
+ },
53
+ handler: async (ctx, args) => {
54
+ if (args.redirectUris.length === 0) {
55
+ throw new Error("redirect_uris required");
56
+ }
57
+ const invalidRedirect = args.redirectUris.find((uri) => !isValidRedirectUri(uri));
58
+ if (invalidRedirect) {
59
+ throw new Error(`Invalid redirect_uri: ${invalidRedirect}`);
60
+ }
61
+
62
+ const clientId = crypto.randomUUID();
63
+
64
+ // Generate secret only if confidential
65
+ if (args.type === "confidential") {
66
+ // Generate plain secret using CSPrng
67
+ const clientSecret = generateClientSecret(OAUTH_CONSTANTS.CLIENT_SECRET_LENGTH);
68
+
69
+ // Hash the secret
70
+ const clientSecretHash = await bcrypt.hash(clientSecret, 10);
71
+
72
+ // Store the HASH, return the PLAIN secret once
73
+ await ctx.db.insert("oauthClients", {
74
+ name: args.name,
75
+ clientId,
76
+ clientSecret: clientSecretHash, // Store Hash!
77
+ type: args.type,
78
+ redirectUris: args.redirectUris,
79
+ allowedScopes: args.scopes,
80
+ createdAt: Date.now(),
81
+ description: args.description,
82
+ website: args.website,
83
+ logoUrl: args.logoUrl,
84
+ tosUrl: args.tosUrl,
85
+ policyUrl: args.policyUrl,
86
+ isInternal: args.isInternal,
87
+ });
88
+
89
+ return {
90
+ clientId,
91
+ clientSecret, // Return Plain!
92
+ clientIdIssuedAt: Math.floor(Date.now() / 1000),
93
+ };
94
+ }
95
+
96
+ // Public client (no secret)
97
+ await ctx.db.insert("oauthClients", {
98
+ name: args.name,
99
+ clientId,
100
+ clientSecret: undefined,
101
+ type: args.type,
102
+ redirectUris: args.redirectUris,
103
+ allowedScopes: args.scopes,
104
+ createdAt: Date.now(),
105
+ description: args.description,
106
+ website: args.website,
107
+ logoUrl: args.logoUrl,
108
+ tosUrl: args.tosUrl,
109
+ policyUrl: args.policyUrl,
110
+ isInternal: args.isInternal,
111
+ });
112
+
113
+ return {
114
+ clientId,
115
+ clientIdIssuedAt: Math.floor(Date.now() / 1000),
116
+ };
117
+ },
118
+ });
119
+
120
+ /**
121
+ * Verify Client Secret
122
+ */
123
+ export const verifyClientSecret = mutation({
124
+ args: {
125
+ clientId: v.string(),
126
+ clientSecret: v.string(),
127
+ },
128
+ handler: async (ctx, args) => {
129
+ const client = await ctx.db
130
+ .query("oauthClients")
131
+ .withIndex("by_client_id", (q) => q.eq("clientId", args.clientId))
132
+ .unique();
133
+
134
+ if (!client || !client.clientSecret) {
135
+ return false;
136
+ }
137
+
138
+ try {
139
+ return await bcrypt.compare(args.clientSecret, client.clientSecret);
140
+ } catch (e) {
141
+ console.error("Client Secret Verification Failed:", e);
142
+ return false;
143
+ }
144
+ },
145
+ });
146
+
147
+ /**
148
+ * Delete OAuth Client
149
+ */
150
+ export const deleteClient = mutation({
151
+ args: {
152
+ clientId: v.string(),
153
+ },
154
+ handler: async (ctx, args) => {
155
+ const client = await ctx.db
156
+ .query("oauthClients")
157
+ .withIndex("by_client_id", (q) => q.eq("clientId", args.clientId))
158
+ .unique();
159
+
160
+ if (!client) {
161
+ throw new Error("Client not found");
162
+ }
163
+
164
+ // Delete all tokens for this client
165
+ const tokens = await ctx.db
166
+ .query("oauthTokens")
167
+ .filter(q => q.eq(q.field("clientId"), args.clientId))
168
+ .collect();
169
+
170
+ for (const token of tokens) {
171
+ await ctx.db.delete(token._id);
172
+ }
173
+
174
+ // Delete all codes for this client
175
+ const codes = await ctx.db
176
+ .query("oauthCodes")
177
+ .filter(q => q.eq(q.field("clientId"), args.clientId))
178
+ .collect();
179
+
180
+ for (const code of codes) {
181
+ await ctx.db.delete(code._id);
182
+ }
183
+
184
+ // Delete the client
185
+ await ctx.db.delete(client._id);
186
+
187
+ return { success: true };
188
+ },
189
+ });
@@ -0,0 +1,40 @@
1
+ /**
2
+ * OAuth 2.1 Provider Constants
3
+ */
4
+ export const OAUTH_CONSTANTS = {
5
+ // Code & Token Expiry
6
+ CODE_EXPIRY_MS: 10 * 60 * 1000, // 10 minutes
7
+ ACCESS_TOKEN_EXPIRY_SECONDS: 3600, // 1 hour
8
+ ACCESS_TOKEN_EXPIRY: "1h",
9
+ ID_TOKEN_EXPIRY: "1h",
10
+ REFRESH_TOKEN_EXPIRY_MS: 30 * 24 * 60 * 60 * 1000, // 30 days
11
+
12
+ // Code Generation
13
+ AUTH_CODE_LENGTH: 32,
14
+ CLIENT_SECRET_LENGTH: 64,
15
+
16
+ // Supported Values
17
+ SUPPORTED_SCOPES: ["openid", "profile", "email", "offline_access"],
18
+ SUPPORTED_GRANT_TYPES: ["authorization_code", "refresh_token"],
19
+ SUPPORTED_RESPONSE_TYPES: ["code"],
20
+ SUPPORTED_CODE_CHALLENGE_METHODS: ["S256"],
21
+
22
+ // Keys
23
+ DEFAULT_KEY_ID: "default-key",
24
+
25
+ // CORS
26
+ CORS_MAX_AGE: "3600", // 1 hour
27
+ } as const;
28
+
29
+ /**
30
+ * OAuth Error Codes (RFC 6749)
31
+ */
32
+ export const OAUTH_ERROR_CODES = {
33
+ INVALID_REQUEST: "invalid_request",
34
+ INVALID_CLIENT: "invalid_client",
35
+ INVALID_GRANT: "invalid_grant",
36
+ UNAUTHORIZED_CLIENT: "unauthorized_client",
37
+ UNSUPPORTED_GRANT_TYPE: "unsupported_grant_type",
38
+ INVALID_SCOPE: "invalid_scope",
39
+ SERVER_ERROR: "server_error",
40
+ } as const;
@@ -0,0 +1,3 @@
1
+ import { defineComponent } from "convex/server";
2
+
3
+ export default defineComponent("oauthProvider");