@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,127 @@
1
+ /**
2
+ * Get OAuth Client by clientId
3
+ */
4
+ export declare const getClient: import("convex/server").RegisteredQuery<"public", {
5
+ clientId: string;
6
+ }, Promise<{
7
+ _id: import("convex/values").GenericId<"oauthClients">;
8
+ _creationTime: number;
9
+ description?: string | undefined;
10
+ logoUrl?: string | undefined;
11
+ website?: string | undefined;
12
+ tosUrl?: string | undefined;
13
+ policyUrl?: string | undefined;
14
+ clientSecret?: string | undefined;
15
+ isInternal?: boolean | undefined;
16
+ name: string;
17
+ clientId: string;
18
+ type: "public" | "confidential";
19
+ redirectUris: string[];
20
+ allowedScopes: string[];
21
+ createdAt: number;
22
+ } | null>>;
23
+ /**
24
+ * Get Refresh Token
25
+ *
26
+ * Note: Tokens are stored as SHA-256 hashes. This query hashes the input
27
+ * before lookup, with backward compatibility for plaintext tokens.
28
+ */
29
+ export declare const getRefreshToken: import("convex/server").RegisteredQuery<"public", {
30
+ refreshToken: string;
31
+ }, Promise<{
32
+ _id: import("convex/values").GenericId<"oauthTokens">;
33
+ _creationTime: number;
34
+ refreshToken?: string | undefined;
35
+ refreshTokenExpiresAt?: number | undefined;
36
+ authorizationCode?: string | undefined;
37
+ clientId: string;
38
+ userId: string;
39
+ scopes: string[];
40
+ expiresAt: number;
41
+ accessToken: string;
42
+ } | null>>;
43
+ /**
44
+ * List OAuth Clients (for admin)
45
+ */
46
+ export declare const listClients: import("convex/server").RegisteredQuery<"public", {}, Promise<{
47
+ clientSecret: undefined;
48
+ _id: import("convex/values").GenericId<"oauthClients">;
49
+ _creationTime: number;
50
+ description?: string | undefined;
51
+ logoUrl?: string | undefined;
52
+ website?: string | undefined;
53
+ tosUrl?: string | undefined;
54
+ policyUrl?: string | undefined;
55
+ isInternal?: boolean | undefined;
56
+ name: string;
57
+ clientId: string;
58
+ type: "public" | "confidential";
59
+ redirectUris: string[];
60
+ allowedScopes: string[];
61
+ createdAt: number;
62
+ }[]>>;
63
+ /**
64
+ * Get tokens by user ID
65
+ */
66
+ export declare const getTokensByUser: import("convex/server").RegisteredQuery<"public", {
67
+ userId: string;
68
+ }, Promise<{
69
+ _id: import("convex/values").GenericId<"oauthTokens">;
70
+ _creationTime: number;
71
+ refreshToken?: string | undefined;
72
+ refreshTokenExpiresAt?: number | undefined;
73
+ authorizationCode?: string | undefined;
74
+ clientId: string;
75
+ userId: string;
76
+ scopes: string[];
77
+ expiresAt: number;
78
+ accessToken: string;
79
+ }[]>>;
80
+ /**
81
+ * Get authorization for a specific user-client pair
82
+ */
83
+ export declare const getAuthorization: import("convex/server").RegisteredQuery<"public", {
84
+ clientId: string;
85
+ userId: string;
86
+ }, Promise<{
87
+ _id: import("convex/values").GenericId<"oauthAuthorizations">;
88
+ _creationTime: number;
89
+ lastUsedAt?: number | undefined;
90
+ clientId: string;
91
+ userId: string;
92
+ scopes: string[];
93
+ authorizedAt: number;
94
+ } | null>>;
95
+ /**
96
+ * Check if authorization exists (for revocation check)
97
+ * Returns true if authorization is valid, false if revoked or not found
98
+ */
99
+ export declare const hasAuthorization: import("convex/server").RegisteredQuery<"public", {
100
+ clientId: string;
101
+ userId: string;
102
+ }, Promise<boolean>>;
103
+ /**
104
+ * Check if user has any valid authorization (for OAuth token validation)
105
+ * If user has no authorizations, they shouldn't be able to access via OAuth
106
+ */
107
+ export declare const hasAnyAuthorization: import("convex/server").RegisteredQuery<"public", {
108
+ userId: string;
109
+ }, Promise<boolean>>;
110
+ /**
111
+ * List all authorizations for a user (with client info)
112
+ */
113
+ export declare const listUserAuthorizations: import("convex/server").RegisteredQuery<"public", {
114
+ userId: string;
115
+ }, Promise<{
116
+ clientName: string;
117
+ clientLogoUrl: string | undefined;
118
+ clientWebsite: string | undefined;
119
+ _id: import("convex/values").GenericId<"oauthAuthorizations">;
120
+ _creationTime: number;
121
+ lastUsedAt?: number | undefined;
122
+ clientId: string;
123
+ userId: string;
124
+ scopes: string[];
125
+ authorizedAt: number;
126
+ }[]>>;
127
+ //# sourceMappingURL=queries.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"queries.d.ts","sourceRoot":"","sources":["../../src/component/queries.ts"],"names":[],"mappings":"AAIA;;GAEG;AACH,eAAO,MAAM,SAAS;;;;;;;;;;;;;;;;;;UAQpB,CAAC;AAEH;;;;;GAKG;AACH,eAAO,MAAM,eAAe;;;;;;;;;;;;;UAsB1B,CAAC;AAEH;;GAEG;AACH,eAAO,MAAM,WAAW;;;;;;;;;;;;;;;;KAUtB,CAAC;AAEH;;GAEG;AACH,eAAO,MAAM,eAAe;;;;;;;;;;;;;KAQ1B,CAAC;AAMH;;GAEG;AACH,eAAO,MAAM,gBAAgB;;;;;;;;;;;UAa3B,CAAC;AAEH;;;GAGG;AACH,eAAO,MAAM,gBAAgB;;;oBAc3B,CAAC;AAEH;;;GAGG;AACH,eAAO,MAAM,mBAAmB;;oBAW9B,CAAC;AAEH;;GAEG;AACH,eAAO,MAAM,sBAAsB;;;;;;;;;;;;;KA2BjC,CAAC"}
@@ -0,0 +1,145 @@
1
+ import { v } from "convex/values";
2
+ import { query } from "./_generated/server";
3
+ import { hashToken, isHashedToken } from "./token_security";
4
+ /**
5
+ * Get OAuth Client by clientId
6
+ */
7
+ export const getClient = query({
8
+ args: { clientId: v.string() },
9
+ handler: async (ctx, args) => {
10
+ return await ctx.db
11
+ .query("oauthClients")
12
+ .withIndex("by_client_id", (q) => q.eq("clientId", args.clientId))
13
+ .unique();
14
+ },
15
+ });
16
+ /**
17
+ * Get Refresh Token
18
+ *
19
+ * Note: Tokens are stored as SHA-256 hashes. This query hashes the input
20
+ * before lookup, with backward compatibility for plaintext tokens.
21
+ */
22
+ export const getRefreshToken = query({
23
+ args: { refreshToken: v.string() },
24
+ handler: async (ctx, args) => {
25
+ // Hash the token for lookup
26
+ const refreshTokenHash = await hashToken(args.refreshToken);
27
+ // Try hash lookup first
28
+ let token = await ctx.db
29
+ .query("oauthTokens")
30
+ .withIndex("by_refresh_token", (q) => q.eq("refreshToken", refreshTokenHash))
31
+ .unique();
32
+ // Backward compatibility: try plaintext lookup if hash lookup fails
33
+ if (!token && !isHashedToken(args.refreshToken)) {
34
+ token = await ctx.db
35
+ .query("oauthTokens")
36
+ .withIndex("by_refresh_token", (q) => q.eq("refreshToken", args.refreshToken))
37
+ .unique();
38
+ }
39
+ return token;
40
+ },
41
+ });
42
+ /**
43
+ * List OAuth Clients (for admin)
44
+ */
45
+ export const listClients = query({
46
+ args: {},
47
+ handler: async (ctx) => {
48
+ const clients = await ctx.db.query("oauthClients").collect();
49
+ // Don't return secrets
50
+ return clients.map(client => ({
51
+ ...client,
52
+ clientSecret: undefined,
53
+ }));
54
+ },
55
+ });
56
+ /**
57
+ * Get tokens by user ID
58
+ */
59
+ export const getTokensByUser = query({
60
+ args: { userId: v.string() },
61
+ handler: async (ctx, args) => {
62
+ return await ctx.db
63
+ .query("oauthTokens")
64
+ .withIndex("by_user", (q) => q.eq("userId", args.userId))
65
+ .collect();
66
+ },
67
+ });
68
+ // --------------------------------------------------------------------------
69
+ // Authorization Queries
70
+ // --------------------------------------------------------------------------
71
+ /**
72
+ * Get authorization for a specific user-client pair
73
+ */
74
+ export const getAuthorization = query({
75
+ args: {
76
+ userId: v.string(),
77
+ clientId: v.string(),
78
+ },
79
+ handler: async (ctx, args) => {
80
+ return await ctx.db
81
+ .query("oauthAuthorizations")
82
+ .withIndex("by_user_client", (q) => q.eq("userId", args.userId).eq("clientId", args.clientId))
83
+ .unique();
84
+ },
85
+ });
86
+ /**
87
+ * Check if authorization exists (for revocation check)
88
+ * Returns true if authorization is valid, false if revoked or not found
89
+ */
90
+ export const hasAuthorization = query({
91
+ args: {
92
+ userId: v.string(),
93
+ clientId: v.string(),
94
+ },
95
+ handler: async (ctx, args) => {
96
+ const auth = await ctx.db
97
+ .query("oauthAuthorizations")
98
+ .withIndex("by_user_client", (q) => q.eq("userId", args.userId).eq("clientId", args.clientId))
99
+ .unique();
100
+ return auth !== null;
101
+ },
102
+ });
103
+ /**
104
+ * Check if user has any valid authorization (for OAuth token validation)
105
+ * If user has no authorizations, they shouldn't be able to access via OAuth
106
+ */
107
+ export const hasAnyAuthorization = query({
108
+ args: {
109
+ userId: v.string(),
110
+ },
111
+ handler: async (ctx, args) => {
112
+ const auth = await ctx.db
113
+ .query("oauthAuthorizations")
114
+ .withIndex("by_user", (q) => q.eq("userId", args.userId))
115
+ .first();
116
+ return auth !== null;
117
+ },
118
+ });
119
+ /**
120
+ * List all authorizations for a user (with client info)
121
+ */
122
+ export const listUserAuthorizations = query({
123
+ args: { userId: v.string() },
124
+ handler: async (ctx, args) => {
125
+ const authorizations = await ctx.db
126
+ .query("oauthAuthorizations")
127
+ .withIndex("by_user", (q) => q.eq("userId", args.userId))
128
+ .collect();
129
+ // Enrich with client info
130
+ const result = await Promise.all(authorizations.map(async (auth) => {
131
+ const client = await ctx.db
132
+ .query("oauthClients")
133
+ .withIndex("by_client_id", (q) => q.eq("clientId", auth.clientId))
134
+ .unique();
135
+ return {
136
+ ...auth,
137
+ clientName: client?.name ?? "Unknown App",
138
+ clientLogoUrl: client?.logoUrl,
139
+ clientWebsite: client?.website,
140
+ };
141
+ }));
142
+ return result;
143
+ },
144
+ });
145
+ //# sourceMappingURL=queries.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"queries.js","sourceRoot":"","sources":["../../src/component/queries.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,116 @@
1
+ declare const _default: import("convex/server").SchemaDefinition<{
2
+ /**
3
+ * OAuth Clients
4
+ * Registered applications that can request authorization
5
+ */
6
+ oauthClients: import("convex/server").TableDefinition<import("convex/values").VObject<{
7
+ description?: string | undefined;
8
+ logoUrl?: string | undefined;
9
+ website?: string | undefined;
10
+ tosUrl?: string | undefined;
11
+ policyUrl?: string | undefined;
12
+ clientSecret?: string | undefined;
13
+ isInternal?: boolean | undefined;
14
+ name: string;
15
+ clientId: string;
16
+ type: "public" | "confidential";
17
+ redirectUris: string[];
18
+ allowedScopes: string[];
19
+ createdAt: number;
20
+ }, {
21
+ name: import("convex/values").VString<string, "required">;
22
+ description: import("convex/values").VString<string | undefined, "optional">;
23
+ logoUrl: import("convex/values").VString<string | undefined, "optional">;
24
+ website: import("convex/values").VString<string | undefined, "optional">;
25
+ tosUrl: import("convex/values").VString<string | undefined, "optional">;
26
+ policyUrl: import("convex/values").VString<string | undefined, "optional">;
27
+ clientId: import("convex/values").VString<string, "required">;
28
+ clientSecret: import("convex/values").VString<string | undefined, "optional">;
29
+ type: import("convex/values").VUnion<"public" | "confidential", [import("convex/values").VLiteral<"confidential", "required">, import("convex/values").VLiteral<"public", "required">], "required", never>;
30
+ redirectUris: import("convex/values").VArray<string[], import("convex/values").VString<string, "required">, "required">;
31
+ allowedScopes: import("convex/values").VArray<string[], import("convex/values").VString<string, "required">, "required">;
32
+ isInternal: import("convex/values").VBoolean<boolean | undefined, "optional">;
33
+ createdAt: import("convex/values").VFloat64<number, "required">;
34
+ }, "required", "name" | "description" | "logoUrl" | "website" | "tosUrl" | "policyUrl" | "clientId" | "clientSecret" | "type" | "redirectUris" | "allowedScopes" | "isInternal" | "createdAt">, {
35
+ by_client_id: ["clientId", "_creationTime"];
36
+ }, {}, {}>;
37
+ /**
38
+ * OAuth Authorization Codes
39
+ * Short-lived codes for authorization code flow
40
+ */
41
+ oauthCodes: import("convex/server").TableDefinition<import("convex/values").VObject<{
42
+ nonce?: string | undefined;
43
+ usedAt?: number | undefined;
44
+ clientId: string;
45
+ code: string;
46
+ userId: string;
47
+ scopes: string[];
48
+ redirectUri: string;
49
+ codeChallenge: string;
50
+ codeChallengeMethod: string;
51
+ expiresAt: number;
52
+ }, {
53
+ code: import("convex/values").VString<string, "required">;
54
+ clientId: import("convex/values").VString<string, "required">;
55
+ userId: import("convex/values").VString<string, "required">;
56
+ scopes: import("convex/values").VArray<string[], import("convex/values").VString<string, "required">, "required">;
57
+ redirectUri: import("convex/values").VString<string, "required">;
58
+ codeChallenge: import("convex/values").VString<string, "required">;
59
+ codeChallengeMethod: import("convex/values").VString<string, "required">;
60
+ nonce: import("convex/values").VString<string | undefined, "optional">;
61
+ expiresAt: import("convex/values").VFloat64<number, "required">;
62
+ usedAt: import("convex/values").VFloat64<number | undefined, "optional">;
63
+ }, "required", "clientId" | "code" | "userId" | "scopes" | "redirectUri" | "codeChallenge" | "codeChallengeMethod" | "nonce" | "expiresAt" | "usedAt">, {
64
+ by_code: ["code", "_creationTime"];
65
+ }, {}, {}>;
66
+ /**
67
+ * OAuth Tokens
68
+ * Access and Refresh tokens
69
+ */
70
+ oauthTokens: import("convex/server").TableDefinition<import("convex/values").VObject<{
71
+ refreshToken?: string | undefined;
72
+ refreshTokenExpiresAt?: number | undefined;
73
+ authorizationCode?: string | undefined;
74
+ clientId: string;
75
+ userId: string;
76
+ scopes: string[];
77
+ expiresAt: number;
78
+ accessToken: string;
79
+ }, {
80
+ accessToken: import("convex/values").VString<string, "required">;
81
+ refreshToken: import("convex/values").VString<string | undefined, "optional">;
82
+ clientId: import("convex/values").VString<string, "required">;
83
+ userId: import("convex/values").VString<string, "required">;
84
+ scopes: import("convex/values").VArray<string[], import("convex/values").VString<string, "required">, "required">;
85
+ expiresAt: import("convex/values").VFloat64<number, "required">;
86
+ refreshTokenExpiresAt: import("convex/values").VFloat64<number | undefined, "optional">;
87
+ authorizationCode: import("convex/values").VString<string | undefined, "optional">;
88
+ }, "required", "clientId" | "userId" | "scopes" | "expiresAt" | "accessToken" | "refreshToken" | "refreshTokenExpiresAt" | "authorizationCode">, {
89
+ by_access_token: ["accessToken", "_creationTime"];
90
+ by_refresh_token: ["refreshToken", "_creationTime"];
91
+ by_user: ["userId", "_creationTime"];
92
+ by_authorization_code: ["authorizationCode", "_creationTime"];
93
+ }, {}, {}>;
94
+ /**
95
+ * OAuth Authorizations
96
+ * User consent records - persists beyond token expiry
97
+ */
98
+ oauthAuthorizations: import("convex/server").TableDefinition<import("convex/values").VObject<{
99
+ lastUsedAt?: number | undefined;
100
+ clientId: string;
101
+ userId: string;
102
+ scopes: string[];
103
+ authorizedAt: number;
104
+ }, {
105
+ userId: import("convex/values").VString<string, "required">;
106
+ clientId: import("convex/values").VString<string, "required">;
107
+ scopes: import("convex/values").VArray<string[], import("convex/values").VString<string, "required">, "required">;
108
+ authorizedAt: import("convex/values").VFloat64<number, "required">;
109
+ lastUsedAt: import("convex/values").VFloat64<number | undefined, "optional">;
110
+ }, "required", "clientId" | "userId" | "scopes" | "authorizedAt" | "lastUsedAt">, {
111
+ by_user: ["userId", "_creationTime"];
112
+ by_user_client: ["userId", "clientId", "_creationTime"];
113
+ }, {}, {}>;
114
+ }, true>;
115
+ export default _default;
116
+ //# sourceMappingURL=schema.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"schema.d.ts","sourceRoot":"","sources":["../../src/component/schema.ts"],"names":[],"mappings":";IAII;;;OAGG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;IAsBH;;;OAGG;;;;;;;;;;;;;;;;;;;;;;;;;;IAiBH;;;OAGG;;;;;;;;;;;;;;;;;;;;;;;;;IAoBH;;;OAGG;;;;;;;;;;;;;;;;;;AAxEP,wBAwFG"}
@@ -0,0 +1,77 @@
1
+ import { defineSchema, defineTable } from "convex/server";
2
+ import { v } from "convex/values";
3
+ export default defineSchema({
4
+ /**
5
+ * OAuth Clients
6
+ * Registered applications that can request authorization
7
+ */
8
+ oauthClients: defineTable({
9
+ name: v.string(),
10
+ description: v.optional(v.string()),
11
+ logoUrl: v.optional(v.string()),
12
+ website: v.optional(v.string()),
13
+ tosUrl: v.optional(v.string()),
14
+ policyUrl: v.optional(v.string()),
15
+ // Client Credentials
16
+ clientId: v.string(), // Public ID (UUID v4)
17
+ clientSecret: v.optional(v.string()), // Hashed Secret (for confidential clients)
18
+ type: v.union(v.literal("confidential"), v.literal("public")),
19
+ redirectUris: v.array(v.string()), // Must be exact match
20
+ allowedScopes: v.array(v.string()), // e.g. ["openid", "profile", "email"]
21
+ isInternal: v.optional(v.boolean()), // Internal tool flag
22
+ createdAt: v.number(),
23
+ }).index("by_client_id", ["clientId"]),
24
+ /**
25
+ * OAuth Authorization Codes
26
+ * Short-lived codes for authorization code flow
27
+ */
28
+ oauthCodes: defineTable({
29
+ code: v.string(),
30
+ clientId: v.string(),
31
+ userId: v.string(), // Convex users table Id (string, not v.id since component doesn't know about users table)
32
+ scopes: v.array(v.string()),
33
+ redirectUri: v.string(),
34
+ // PKCE
35
+ codeChallenge: v.string(),
36
+ codeChallengeMethod: v.string(), // "S256" or "plain"
37
+ nonce: v.optional(v.string()), // OIDC Nonce
38
+ expiresAt: v.number(), // Usually 10 minutes
39
+ usedAt: v.optional(v.number()), // RFC Line 1136: Track code usage for replay detection
40
+ }).index("by_code", ["code"]),
41
+ /**
42
+ * OAuth Tokens
43
+ * Access and Refresh tokens
44
+ */
45
+ oauthTokens: defineTable({
46
+ accessToken: v.string(),
47
+ refreshToken: v.optional(v.string()),
48
+ clientId: v.string(),
49
+ userId: v.string(), // Convex users table Id (string)
50
+ scopes: v.array(v.string()),
51
+ expiresAt: v.number(), // Access Token Expiry
52
+ refreshTokenExpiresAt: v.optional(v.number()), // Refresh Token Expiry
53
+ // RFC Line 1136: Track which authorization code issued this token for replay detection
54
+ authorizationCode: v.optional(v.string()), // Hashed authorization code
55
+ })
56
+ .index("by_access_token", ["accessToken"])
57
+ .index("by_refresh_token", ["refreshToken"])
58
+ .index("by_user", ["userId"])
59
+ .index("by_authorization_code", ["authorizationCode"]),
60
+ /**
61
+ * OAuth Authorizations
62
+ * User consent records - persists beyond token expiry
63
+ */
64
+ oauthAuthorizations: defineTable({
65
+ userId: v.string(), // Convex users table Id (string)
66
+ clientId: v.string(),
67
+ // Authorized scopes
68
+ scopes: v.array(v.string()),
69
+ // When the user first authorized this client
70
+ authorizedAt: v.number(),
71
+ // Last time a token was issued for this authorization
72
+ lastUsedAt: v.optional(v.number()),
73
+ })
74
+ .index("by_user", ["userId"])
75
+ .index("by_user_client", ["userId", "clientId"]),
76
+ });
77
+ //# sourceMappingURL=schema.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"schema.js","sourceRoot":"","sources":["../../src/component/schema.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,53 @@
1
+ /**
2
+ * Token Security Utilities for OAuth Provider (Web Crypto API)
3
+ *
4
+ * Provides secure token hashing for database storage.
5
+ * Tokens are hashed using SHA-256 before storage - the original token
6
+ * value is never stored, only returned to the client during issuance.
7
+ *
8
+ * This is more secure than encryption because:
9
+ * 1. Even with DB access + encryption key, tokens can't be recovered
10
+ * 2. Token validation only requires hash comparison
11
+ * 3. Clients already have the original token
12
+ *
13
+ * @see https://cheatsheetseries.owasp.org/cheatsheets/Password_Storage_Cheat_Sheet.html
14
+ */
15
+ /**
16
+ * Creates a SHA-256 hash of a token for secure storage.
17
+ *
18
+ * @param token - The plaintext token to hash
19
+ * @returns Hex-encoded hash suitable for database storage and indexing
20
+ *
21
+ * @example
22
+ * ```typescript
23
+ * const accessToken = generateCode(64);
24
+ * const accessTokenHash = await hashToken(accessToken);
25
+ * // Store accessTokenHash in DB, return accessToken to client
26
+ * ```
27
+ */
28
+ export declare function hashToken(token: string): Promise<string>;
29
+ /**
30
+ * Verifies a token against its stored hash.
31
+ *
32
+ * @param token - The plaintext token to verify
33
+ * @param hash - The stored hash to compare against
34
+ * @returns true if the token matches the hash
35
+ */
36
+ export declare function verifyToken(token: string, hash: string): Promise<boolean>;
37
+ /**
38
+ * Checks if a value looks like a SHA-256 hash (64 hex characters).
39
+ * Used for backward compatibility during migration.
40
+ *
41
+ * @param value - The value to check
42
+ * @returns true if the value appears to be a hash
43
+ */
44
+ export declare function isHashedToken(value: string): boolean;
45
+ /**
46
+ * Hash a token if it's not already hashed.
47
+ * Used for backward compatibility during migration.
48
+ *
49
+ * @param token - Token that may or may not be hashed
50
+ * @returns The hash (either computed or passed through if already hashed)
51
+ */
52
+ export declare function ensureHashed(token: string): Promise<string>;
53
+ //# sourceMappingURL=token_security.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"token_security.d.ts","sourceRoot":"","sources":["../../src/component/token_security.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AASH;;;;;;;;;;;;GAYG;AACH,wBAAsB,SAAS,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAU9D;AAiBD;;;;;;GAMG;AACH,wBAAsB,WAAW,CAC7B,KAAK,EAAE,MAAM,EACb,IAAI,EAAE,MAAM,GACb,OAAO,CAAC,OAAO,CAAC,CAIlB;AAED;;;;;;GAMG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAEpD;AAED;;;;;;GAMG;AACH,wBAAsB,YAAY,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAKjE"}
@@ -0,0 +1,91 @@
1
+ /**
2
+ * Token Security Utilities for OAuth Provider (Web Crypto API)
3
+ *
4
+ * Provides secure token hashing for database storage.
5
+ * Tokens are hashed using SHA-256 before storage - the original token
6
+ * value is never stored, only returned to the client during issuance.
7
+ *
8
+ * This is more secure than encryption because:
9
+ * 1. Even with DB access + encryption key, tokens can't be recovered
10
+ * 2. Token validation only requires hash comparison
11
+ * 3. Clients already have the original token
12
+ *
13
+ * @see https://cheatsheetseries.owasp.org/cheatsheets/Password_Storage_Cheat_Sheet.html
14
+ */
15
+ /**
16
+ * Convert string to Uint8Array
17
+ */
18
+ function stringToBytes(str) {
19
+ return new TextEncoder().encode(str);
20
+ }
21
+ /**
22
+ * Creates a SHA-256 hash of a token for secure storage.
23
+ *
24
+ * @param token - The plaintext token to hash
25
+ * @returns Hex-encoded hash suitable for database storage and indexing
26
+ *
27
+ * @example
28
+ * ```typescript
29
+ * const accessToken = generateCode(64);
30
+ * const accessTokenHash = await hashToken(accessToken);
31
+ * // Store accessTokenHash in DB, return accessToken to client
32
+ * ```
33
+ */
34
+ export async function hashToken(token) {
35
+ const tokenBytes = stringToBytes(token);
36
+ const hashBuffer = await crypto.subtle.digest("SHA-256", tokenBytes.buffer);
37
+ const hashArray = new Uint8Array(hashBuffer);
38
+ return Array.from(hashArray)
39
+ .map((b) => b.toString(16).padStart(2, "0"))
40
+ .join("");
41
+ }
42
+ /**
43
+ * Timing-safe string comparison to prevent timing attacks.
44
+ * Compares two strings in constant time regardless of where they differ.
45
+ */
46
+ function timingSafeEqual(a, b) {
47
+ if (a.length !== b.length) {
48
+ return false;
49
+ }
50
+ let result = 0;
51
+ for (let i = 0; i < a.length; i++) {
52
+ result |= a.charCodeAt(i) ^ b.charCodeAt(i);
53
+ }
54
+ return result === 0;
55
+ }
56
+ /**
57
+ * Verifies a token against its stored hash.
58
+ *
59
+ * @param token - The plaintext token to verify
60
+ * @param hash - The stored hash to compare against
61
+ * @returns true if the token matches the hash
62
+ */
63
+ export async function verifyToken(token, hash) {
64
+ const tokenHash = await hashToken(token);
65
+ // Use timing-safe comparison to prevent timing attacks
66
+ return timingSafeEqual(tokenHash, hash);
67
+ }
68
+ /**
69
+ * Checks if a value looks like a SHA-256 hash (64 hex characters).
70
+ * Used for backward compatibility during migration.
71
+ *
72
+ * @param value - The value to check
73
+ * @returns true if the value appears to be a hash
74
+ */
75
+ export function isHashedToken(value) {
76
+ return /^[a-f0-9]{64}$/.test(value);
77
+ }
78
+ /**
79
+ * Hash a token if it's not already hashed.
80
+ * Used for backward compatibility during migration.
81
+ *
82
+ * @param token - Token that may or may not be hashed
83
+ * @returns The hash (either computed or passed through if already hashed)
84
+ */
85
+ export async function ensureHashed(token) {
86
+ if (isHashedToken(token)) {
87
+ return token;
88
+ }
89
+ return hashToken(token);
90
+ }
91
+ //# sourceMappingURL=token_security.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"token_security.js","sourceRoot":"","sources":["../../src/component/token_security.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH;;GAEG;AACH,SAAS,aAAa,CAAC,GAAW;IAC9B,OAAO,IAAI,WAAW,EAAE,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;AACzC,CAAC;AAED;;;;;;;;;;;;GAYG;AACH,MAAM,CAAC,KAAK,UAAU,SAAS,CAAC,KAAa;IACzC,MAAM,UAAU,GAAG,aAAa,CAAC,KAAK,CAAC,CAAC;IACxC,MAAM,UAAU,GAAG,MAAM,MAAM,CAAC,MAAM,CAAC,MAAM,CACzC,SAAS,EACT,UAAU,CAAC,MAAqB,CACnC,CAAC;IACF,MAAM,SAAS,GAAG,IAAI,UAAU,CAAC,UAAU,CAAC,CAAC;IAC7C,OAAO,KAAK,CAAC,IAAI,CAAC,SAAS,CAAC;SACvB,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,QAAQ,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC;SAC3C,IAAI,CAAC,EAAE,CAAC,CAAC;AAClB,CAAC;AAED;;;GAGG;AACH,SAAS,eAAe,CAAC,CAAS,EAAE,CAAS;IACzC,IAAI,CAAC,CAAC,MAAM,KAAK,CAAC,CAAC,MAAM,EAAE,CAAC;QACxB,OAAO,KAAK,CAAC;IACjB,CAAC;IACD,IAAI,MAAM,GAAG,CAAC,CAAC;IACf,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,CAAC,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;QAChC,MAAM,IAAI,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC;IAChD,CAAC;IACD,OAAO,MAAM,KAAK,CAAC,CAAC;AACxB,CAAC;AAED;;;;;;GAMG;AACH,MAAM,CAAC,KAAK,UAAU,WAAW,CAC7B,KAAa,EACb,IAAY;IAEZ,MAAM,SAAS,GAAG,MAAM,SAAS,CAAC,KAAK,CAAC,CAAC;IACzC,uDAAuD;IACvD,OAAO,eAAe,CAAC,SAAS,EAAE,IAAI,CAAC,CAAC;AAC5C,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,aAAa,CAAC,KAAa;IACvC,OAAO,gBAAgB,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;AACxC,CAAC;AAED;;;;;;GAMG;AACH,MAAM,CAAC,KAAK,UAAU,YAAY,CAAC,KAAa;IAC5C,IAAI,aAAa,CAAC,KAAK,CAAC,EAAE,CAAC;QACvB,OAAO,KAAK,CAAC;IACjB,CAAC;IACD,OAAO,SAAS,CAAC,KAAK,CAAC,CAAC;AAC5B,CAAC"}
@@ -0,0 +1,21 @@
1
+ import type { FunctionReference, FunctionVisibility, FunctionArgs, FunctionReturnType } from "convex/server";
2
+ /**
3
+ * Convex component common Context types
4
+ *
5
+ * Unified RunQueryCtx, RunMutationCtx, RunActionCtx that were defined separately in each package.
6
+ *
7
+ * Using generics, infer return value types from FunctionReference.
8
+ * - FunctionVisibility: supports both internal/public functions
9
+ * - FunctionArgs<F>: extracts function argument types
10
+ * - FunctionReturnType<F>: extracts function return value types
11
+ */
12
+ export type RunQueryCtx = {
13
+ runQuery<F extends FunctionReference<"query", FunctionVisibility>>(query: F, args: FunctionArgs<F>): Promise<FunctionReturnType<F>>;
14
+ };
15
+ export type RunMutationCtx = RunQueryCtx & {
16
+ runMutation<F extends FunctionReference<"mutation", FunctionVisibility>>(mutation: F, args: FunctionArgs<F>): Promise<FunctionReturnType<F>>;
17
+ };
18
+ export type RunActionCtx = RunMutationCtx & {
19
+ runAction<F extends FunctionReference<"action", FunctionVisibility>>(action: F, args: FunctionArgs<F>): Promise<FunctionReturnType<F>>;
20
+ };
21
+ //# sourceMappingURL=convex-types.d.ts.map