@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,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
|