@fragno-dev/project-connector-fragment 0.0.0-stage → 0.0.1
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.md +16 -0
- package/README.md +285 -2
- package/bin/run.js +4 -0
- package/dist/browser/client/react.d.ts +174 -0
- package/dist/browser/client/react.d.ts.map +1 -0
- package/dist/browser/client/react.js +195 -0
- package/dist/browser/client/react.js.map +1 -0
- package/dist/browser/client/solid.d.ts +177 -0
- package/dist/browser/client/solid.d.ts.map +1 -0
- package/dist/browser/client/solid.js +130 -0
- package/dist/browser/client/solid.js.map +1 -0
- package/dist/browser/client/svelte.d.ts +177 -0
- package/dist/browser/client/svelte.d.ts.map +1 -0
- package/dist/browser/client/svelte.js +132 -0
- package/dist/browser/client/svelte.js.map +1 -0
- package/dist/browser/client/vanilla.d.ts +204 -0
- package/dist/browser/client/vanilla.d.ts.map +1 -0
- package/dist/browser/client/vanilla.js +160 -0
- package/dist/browser/client/vanilla.js.map +1 -0
- package/dist/browser/client/vue.d.ts +177 -0
- package/dist/browser/client/vue.d.ts.map +1 -0
- package/dist/browser/client/vue.js +128 -0
- package/dist/browser/client/vue.js.map +1 -0
- package/dist/browser/client-BCzNDSlW.d.ts +711 -0
- package/dist/browser/client-BCzNDSlW.d.ts.map +1 -0
- package/dist/browser/index.d.ts +504 -0
- package/dist/browser/index.d.ts.map +1 -0
- package/dist/browser/index.js +2 -0
- package/dist/browser/src-CSPQuoKz.js +2286 -0
- package/dist/browser/src-CSPQuoKz.js.map +1 -0
- package/dist/cli/authorization-browser.js +20 -0
- package/dist/cli/authorization-browser.js.map +1 -0
- package/dist/cli/cli.d.ts +6 -0
- package/dist/cli/cli.d.ts.map +1 -0
- package/dist/cli/cli.js +258 -0
- package/dist/cli/cli.js.map +1 -0
- package/dist/cli/definition.js +13 -0
- package/dist/cli/definition.js.map +1 -0
- package/dist/cli/index.js +14 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/cli/project-connector-client.js +235 -0
- package/dist/cli/project-connector-client.js.map +1 -0
- package/dist/cli/project-connector-contracts.js +125 -0
- package/dist/cli/project-connector-contracts.js.map +1 -0
- package/dist/cli/routes.js +449 -0
- package/dist/cli/routes.js.map +1 -0
- package/dist/cli/schema.js +18 -0
- package/dist/cli/schema.js.map +1 -0
- package/dist/node/definition.d.ts +170 -0
- package/dist/node/definition.d.ts.map +1 -0
- package/dist/node/definition.js +13 -0
- package/dist/node/definition.js.map +1 -0
- package/dist/node/index.d.ts +456 -0
- package/dist/node/index.d.ts.map +1 -0
- package/dist/node/index.js +36 -0
- package/dist/node/index.js.map +1 -0
- package/dist/node/project-connector-client.d.ts +13 -0
- package/dist/node/project-connector-client.d.ts.map +1 -0
- package/dist/node/project-connector-client.js +235 -0
- package/dist/node/project-connector-client.js.map +1 -0
- package/dist/node/project-connector-contracts.d.ts +179 -0
- package/dist/node/project-connector-contracts.d.ts.map +1 -0
- package/dist/node/project-connector-contracts.js +125 -0
- package/dist/node/project-connector-contracts.js.map +1 -0
- package/dist/node/routes.d.ts +307 -0
- package/dist/node/routes.d.ts.map +1 -0
- package/dist/node/routes.js +449 -0
- package/dist/node/routes.js.map +1 -0
- package/dist/node/schema.d.ts +49 -0
- package/dist/node/schema.d.ts.map +1 -0
- package/dist/node/schema.js +18 -0
- package/dist/node/schema.js.map +1 -0
- package/dist/node/testing/project-connector-test-gateway.d.ts +63 -0
- package/dist/node/testing/project-connector-test-gateway.d.ts.map +1 -0
- package/dist/node/testing/project-connector-test-gateway.js +370 -0
- package/dist/node/testing/project-connector-test-gateway.js.map +1 -0
- package/package.json +111 -3
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
//#region src/project-connector-contracts.ts
|
|
3
|
+
/** Only HTTP(S) URLs can be used as OAuth return destinations. */
|
|
4
|
+
const projectConnectorHttpUrlSchema = z.url().refine((value) => {
|
|
5
|
+
const url = new URL(value);
|
|
6
|
+
return (url.protocol === "https:" || url.protocol === "http:") && !url.username && !url.password;
|
|
7
|
+
}, "Expected an HTTP(S) URL without embedded credentials");
|
|
8
|
+
/** A connection becomes usable only after the gateway confirms its account ID. */
|
|
9
|
+
const projectConnectorConnectionStateSchema = z.discriminatedUnion("status", [
|
|
10
|
+
z.object({ status: z.literal("initiated") }),
|
|
11
|
+
z.object({
|
|
12
|
+
status: z.literal("connected"),
|
|
13
|
+
connectedAccountId: z.string().min(1)
|
|
14
|
+
}),
|
|
15
|
+
z.object({
|
|
16
|
+
status: z.literal("failed"),
|
|
17
|
+
errorCode: z.string().nullable(),
|
|
18
|
+
errorMessage: z.string().nullable()
|
|
19
|
+
}),
|
|
20
|
+
z.object({ status: z.literal("expired") })
|
|
21
|
+
]);
|
|
22
|
+
/** Mirrors the gateway's alias rule so invalid names fail here with a usable message. */
|
|
23
|
+
const projectConnectorConnectionNameSchema = z.string().regex(/^[a-z0-9][a-z0-9_-]*$/, "Connection names may only contain lowercase letters, digits, underscores, and hyphens, and must start with a letter or digit").describe("Lowercase letters, digits, underscores, and hyphens; starts with a letter or digit.");
|
|
24
|
+
/** Named selectors preserve exact source identity; user ownership comes only from authentication. */
|
|
25
|
+
const projectConnectorNamedConnectionSchema = z.strictObject({
|
|
26
|
+
projectId: z.string().min(1),
|
|
27
|
+
providerConfigId: z.string().min(1),
|
|
28
|
+
connectionName: projectConnectorConnectionNameSchema
|
|
29
|
+
});
|
|
30
|
+
/** Select exactly one provider and name every connection explicitly. */
|
|
31
|
+
const projectConnectorConnectInputSchema = z.union([z.strictObject({
|
|
32
|
+
service: z.string().regex(/^[a-z0-9_-]+$/),
|
|
33
|
+
connectionName: projectConnectorNamedConnectionSchema.shape.connectionName,
|
|
34
|
+
returnUri: projectConnectorHttpUrlSchema
|
|
35
|
+
}), z.strictObject({
|
|
36
|
+
providerConfigId: projectConnectorNamedConnectionSchema.shape.providerConfigId,
|
|
37
|
+
connectionName: projectConnectorNamedConnectionSchema.shape.connectionName,
|
|
38
|
+
returnUri: projectConnectorHttpUrlSchema
|
|
39
|
+
})]);
|
|
40
|
+
/** Public connection request projection, including its verified lifecycle. */
|
|
41
|
+
const projectConnectorConnectionSchema = z.object({
|
|
42
|
+
id: z.string(),
|
|
43
|
+
projectId: z.string(),
|
|
44
|
+
providerConfigId: z.string(),
|
|
45
|
+
externalUserId: z.string(),
|
|
46
|
+
service: z.string(),
|
|
47
|
+
connectionName: projectConnectorNamedConnectionSchema.shape.connectionName,
|
|
48
|
+
authorizationUrl: projectConnectorHttpUrlSchema,
|
|
49
|
+
expiresAt: z.string(),
|
|
50
|
+
state: projectConnectorConnectionStateSchema
|
|
51
|
+
});
|
|
52
|
+
/** Stored account selectors bind actions to a specific product user and provider. */
|
|
53
|
+
const projectConnectorAccountSchema = z.object({
|
|
54
|
+
id: z.string(),
|
|
55
|
+
projectId: z.string(),
|
|
56
|
+
providerConfigId: z.string(),
|
|
57
|
+
externalUserId: z.string(),
|
|
58
|
+
service: z.string(),
|
|
59
|
+
connectionName: projectConnectorNamedConnectionSchema.shape.connectionName
|
|
60
|
+
});
|
|
61
|
+
/** Missing named requests remain JSON results rather than empty HTTP responses. */
|
|
62
|
+
const projectConnectorNamedRequestSchema = z.strictObject({ request: projectConnectorConnectionSchema.nullable() });
|
|
63
|
+
/** Missing named accounts remain JSON results rather than empty HTTP responses. */
|
|
64
|
+
const projectConnectorNamedAccountSchema = z.strictObject({ account: projectConnectorAccountSchema.nullable() });
|
|
65
|
+
/** Account pagination is owned by the fragment rather than the provider gateway. */
|
|
66
|
+
const projectConnectorAccountsSchema = z.object({
|
|
67
|
+
accounts: z.array(projectConnectorAccountSchema),
|
|
68
|
+
cursor: z.string().nullable(),
|
|
69
|
+
hasNextPage: z.boolean()
|
|
70
|
+
});
|
|
71
|
+
/** Project-scoped OAuth provider overview excludes action IDs and credentials. */
|
|
72
|
+
const projectConnectorProviderConfigsSchema = z.object({
|
|
73
|
+
projectId: z.string().min(1),
|
|
74
|
+
providerConfigs: z.array(z.object({
|
|
75
|
+
id: z.string().min(1),
|
|
76
|
+
service: z.string().min(1),
|
|
77
|
+
displayName: z.string(),
|
|
78
|
+
callbackUrl: projectConnectorHttpUrlSchema,
|
|
79
|
+
effectiveScopes: z.array(z.string().min(1)),
|
|
80
|
+
proxyAvailable: z.boolean()
|
|
81
|
+
}))
|
|
82
|
+
}).refine(({ providerConfigs }) => new Set(providerConfigs.map((config) => config.id)).size === providerConfigs.length, "Provider configuration IDs must be unique");
|
|
83
|
+
/** Authoritative catalog contracts describe action values, not a provider's execution permissions. */
|
|
84
|
+
const projectConnectorActionSchema = z.object({
|
|
85
|
+
id: z.string().min(1),
|
|
86
|
+
service: z.string().min(1),
|
|
87
|
+
name: z.string(),
|
|
88
|
+
description: z.string().nullable(),
|
|
89
|
+
inputSchema: z.record(z.string(), z.unknown()),
|
|
90
|
+
outputSchema: z.record(z.string(), z.unknown())
|
|
91
|
+
});
|
|
92
|
+
/** Only actions allowed by this exact project OAuth configuration are included. */
|
|
93
|
+
const projectConnectorProviderActionsSchema = z.object({
|
|
94
|
+
projectId: projectConnectorProviderConfigsSchema.shape.projectId,
|
|
95
|
+
providerConfigId: projectConnectorProviderConfigsSchema.shape.providerConfigs.element.shape.id,
|
|
96
|
+
actions: z.array(projectConnectorActionSchema)
|
|
97
|
+
});
|
|
98
|
+
/** Project authentication does not imply that an individual provider account is usable. */
|
|
99
|
+
const projectConnectorStatusSchema = z.object({ authenticated: z.literal(true) });
|
|
100
|
+
/** Read-only provider identity associated with one verified account. */
|
|
101
|
+
const projectConnectorProfileSchema = z.object({
|
|
102
|
+
connectedAccountId: z.string(),
|
|
103
|
+
externalUserId: z.string(),
|
|
104
|
+
service: z.string(),
|
|
105
|
+
profile: z.object({
|
|
106
|
+
id: z.string(),
|
|
107
|
+
kind: z.string(),
|
|
108
|
+
username: z.string().nullable(),
|
|
109
|
+
displayName: z.string().nullable(),
|
|
110
|
+
avatarUrl: z.string().nullable(),
|
|
111
|
+
email: z.string().nullable(),
|
|
112
|
+
metadata: z.record(z.string(), z.unknown())
|
|
113
|
+
}),
|
|
114
|
+
fetchedAt: z.number()
|
|
115
|
+
});
|
|
116
|
+
/** Action output stays opaque; execution IDs support upstream troubleshooting. */
|
|
117
|
+
const projectConnectorExecutionSchema = z.object({
|
|
118
|
+
executionId: z.string(),
|
|
119
|
+
actionId: z.string(),
|
|
120
|
+
output: z.unknown().refine((value) => value !== void 0, "Expected action output")
|
|
121
|
+
});
|
|
122
|
+
//#endregion
|
|
123
|
+
export { projectConnectorAccountSchema, projectConnectorAccountsSchema, projectConnectorActionSchema, projectConnectorConnectInputSchema, projectConnectorConnectionSchema, projectConnectorConnectionStateSchema, projectConnectorExecutionSchema, projectConnectorHttpUrlSchema, projectConnectorNamedAccountSchema, projectConnectorNamedConnectionSchema, projectConnectorNamedRequestSchema, projectConnectorProfileSchema, projectConnectorProviderActionsSchema, projectConnectorProviderConfigsSchema, projectConnectorStatusSchema };
|
|
124
|
+
|
|
125
|
+
//# sourceMappingURL=project-connector-contracts.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"project-connector-contracts.js","names":[],"sources":["../../src/project-connector-contracts.ts"],"sourcesContent":["import { z } from \"zod\";\n\n/** Only HTTP(S) URLs can be used as OAuth return destinations. */\nexport const projectConnectorHttpUrlSchema = z.url().refine((value) => {\n const url = new URL(value);\n return (url.protocol === \"https:\" || url.protocol === \"http:\") && !url.username && !url.password;\n}, \"Expected an HTTP(S) URL without embedded credentials\");\n\n/** A connection becomes usable only after the gateway confirms its account ID. */\nexport const projectConnectorConnectionStateSchema = z.discriminatedUnion(\"status\", [\n z.object({ status: z.literal(\"initiated\") }),\n z.object({ status: z.literal(\"connected\"), connectedAccountId: z.string().min(1) }),\n z.object({\n status: z.literal(\"failed\"),\n errorCode: z.string().nullable(),\n errorMessage: z.string().nullable(),\n }),\n z.object({ status: z.literal(\"expired\") }),\n]);\n\n/** Persisted connection lifecycle; provider credentials never enter this fragment. */\nexport type ProjectConnectorConnectionState = z.infer<typeof projectConnectorConnectionStateSchema>;\n\n/** Mirrors the gateway's alias rule so invalid names fail here with a usable message. */\nconst projectConnectorConnectionNameSchema = z\n .string()\n .regex(\n /^[a-z0-9][a-z0-9_-]*$/,\n \"Connection names may only contain lowercase letters, digits, underscores, and hyphens, and must start with a letter or digit\",\n )\n .describe(\"Lowercase letters, digits, underscores, and hyphens; starts with a letter or digit.\");\n\n/** Named selectors preserve exact source identity; user ownership comes only from authentication. */\nexport const projectConnectorNamedConnectionSchema = z.strictObject({\n projectId: z.string().min(1),\n providerConfigId: z.string().min(1),\n connectionName: projectConnectorConnectionNameSchema,\n});\n\n/** Select exactly one provider and name every connection explicitly. */\nexport const projectConnectorConnectInputSchema = z.union([\n z.strictObject({\n service: z.string().regex(/^[a-z0-9_-]+$/),\n connectionName: projectConnectorNamedConnectionSchema.shape.connectionName,\n returnUri: projectConnectorHttpUrlSchema,\n }),\n z.strictObject({\n providerConfigId: projectConnectorNamedConnectionSchema.shape.providerConfigId,\n connectionName: projectConnectorNamedConnectionSchema.shape.connectionName,\n returnUri: projectConnectorHttpUrlSchema,\n }),\n]);\n\n/** Public connection request projection, including its verified lifecycle. */\nexport const projectConnectorConnectionSchema = z.object({\n id: z.string(),\n projectId: z.string(),\n providerConfigId: z.string(),\n externalUserId: z.string(),\n service: z.string(),\n connectionName: projectConnectorNamedConnectionSchema.shape.connectionName,\n authorizationUrl: projectConnectorHttpUrlSchema,\n expiresAt: z.string(),\n state: projectConnectorConnectionStateSchema,\n});\n\n/** Stored account selectors bind actions to a specific product user and provider. */\nexport const projectConnectorAccountSchema = z.object({\n id: z.string(),\n projectId: z.string(),\n providerConfigId: z.string(),\n externalUserId: z.string(),\n service: z.string(),\n connectionName: projectConnectorNamedConnectionSchema.shape.connectionName,\n});\n\n/** Missing named requests remain JSON results rather than empty HTTP responses. */\nexport const projectConnectorNamedRequestSchema = z.strictObject({\n request: projectConnectorConnectionSchema.nullable(),\n});\n\n/** Missing named accounts remain JSON results rather than empty HTTP responses. */\nexport const projectConnectorNamedAccountSchema = z.strictObject({\n account: projectConnectorAccountSchema.nullable(),\n});\n\n/** Account pagination is owned by the fragment rather than the provider gateway. */\nexport const projectConnectorAccountsSchema = z.object({\n accounts: z.array(projectConnectorAccountSchema),\n cursor: z.string().nullable(),\n hasNextPage: z.boolean(),\n});\n\n/** Project-scoped OAuth provider overview excludes action IDs and credentials. */\nexport const projectConnectorProviderConfigsSchema = z\n .object({\n projectId: z.string().min(1),\n providerConfigs: z.array(\n z.object({\n id: z.string().min(1),\n service: z.string().min(1),\n displayName: z.string(),\n callbackUrl: projectConnectorHttpUrlSchema,\n effectiveScopes: z.array(z.string().min(1)),\n proxyAvailable: z.boolean(),\n }),\n ),\n })\n .refine(\n ({ providerConfigs }) =>\n new Set(providerConfigs.map((config) => config.id)).size === providerConfigs.length,\n \"Provider configuration IDs must be unique\",\n );\n\n/** Authoritative catalog contracts describe action values, not a provider's execution permissions. */\nexport const projectConnectorActionSchema = z.object({\n id: z.string().min(1),\n service: z.string().min(1),\n name: z.string(),\n description: z.string().nullable(),\n inputSchema: z.record(z.string(), z.unknown()),\n outputSchema: z.record(z.string(), z.unknown()),\n});\n\n/** Only actions allowed by this exact project OAuth configuration are included. */\nexport const projectConnectorProviderActionsSchema = z.object({\n projectId: projectConnectorProviderConfigsSchema.shape.projectId,\n providerConfigId: projectConnectorProviderConfigsSchema.shape.providerConfigs.element.shape.id,\n actions: z.array(projectConnectorActionSchema),\n});\n\n/** Project authentication does not imply that an individual provider account is usable. */\nexport const projectConnectorStatusSchema = z.object({ authenticated: z.literal(true) });\n\n/** Read-only provider identity associated with one verified account. */\nexport const projectConnectorProfileSchema = z.object({\n connectedAccountId: z.string(),\n externalUserId: z.string(),\n service: z.string(),\n profile: z.object({\n id: z.string(),\n kind: z.string(),\n username: z.string().nullable(),\n displayName: z.string().nullable(),\n avatarUrl: z.string().nullable(),\n email: z.string().nullable(),\n metadata: z.record(z.string(), z.unknown()),\n }),\n fetchedAt: z.number(),\n});\n\n/** Action output stays opaque; execution IDs support upstream troubleshooting. */\nexport const projectConnectorExecutionSchema = z.object({\n executionId: z.string(),\n actionId: z.string(),\n output: z.unknown().refine((value) => value !== undefined, \"Expected action output\"),\n});\n"],"mappings":"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"}
|
|
@@ -0,0 +1,449 @@
|
|
|
1
|
+
import { projectConnectorAccountsSchema, projectConnectorConnectInputSchema, projectConnectorConnectionSchema, projectConnectorExecutionSchema, projectConnectorNamedAccountSchema, projectConnectorNamedConnectionSchema, projectConnectorNamedRequestSchema, projectConnectorProfileSchema, projectConnectorProviderActionsSchema, projectConnectorProviderConfigsSchema, projectConnectorStatusSchema } from "./project-connector-contracts.js";
|
|
2
|
+
import { ProjectConnectorClientError, confirmProjectConnectorRequest } from "./project-connector-client.js";
|
|
3
|
+
import { projectConnectorSchema } from "./schema.js";
|
|
4
|
+
import { projectConnectorFragmentDefinition } from "./definition.js";
|
|
5
|
+
import { z } from "zod";
|
|
6
|
+
import { defineRoutes } from "@fragno-dev/core";
|
|
7
|
+
import { decodeCursor } from "@fragno-dev/db/cursor";
|
|
8
|
+
//#region src/routes.ts
|
|
9
|
+
function serializeProjectConnectorRequest(saved) {
|
|
10
|
+
return {
|
|
11
|
+
id: saved.id.toString(),
|
|
12
|
+
projectId: saved.projectId,
|
|
13
|
+
providerConfigId: saved.providerConfigId,
|
|
14
|
+
externalUserId: saved.externalUserId,
|
|
15
|
+
service: saved.service,
|
|
16
|
+
connectionName: saved.connectionName,
|
|
17
|
+
authorizationUrl: saved.authorizationUrl,
|
|
18
|
+
expiresAt: saved.expiresAt,
|
|
19
|
+
state: saved.state
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
/** Authenticated routes never accept a product user ID or account binding from the browser. */
|
|
23
|
+
const projectConnectorRoutes = defineRoutes(projectConnectorFragmentDefinition).create(({ config, deps, defineRoute }) => [
|
|
24
|
+
defineRoute({
|
|
25
|
+
method: "GET",
|
|
26
|
+
path: "/provider-configs",
|
|
27
|
+
outputSchema: projectConnectorProviderConfigsSchema,
|
|
28
|
+
errorCodes: ["UNAUTHENTICATED", "PROJECT_CONNECTOR_ERROR"],
|
|
29
|
+
handler: async function({ headers }, { json, error }) {
|
|
30
|
+
if (!await config.getExternalUserId(headers)) return error({
|
|
31
|
+
code: "UNAUTHENTICATED",
|
|
32
|
+
message: "Authentication required"
|
|
33
|
+
}, 401);
|
|
34
|
+
try {
|
|
35
|
+
return json(await deps.projectConnector.listProviderConfigs());
|
|
36
|
+
} catch (cause) {
|
|
37
|
+
if (!(cause instanceof ProjectConnectorClientError)) throw cause;
|
|
38
|
+
return error({
|
|
39
|
+
code: "PROJECT_CONNECTOR_ERROR",
|
|
40
|
+
message: cause.message
|
|
41
|
+
}, 502);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}),
|
|
45
|
+
defineRoute({
|
|
46
|
+
method: "GET",
|
|
47
|
+
path: "/provider-configs/:providerConfigId/actions",
|
|
48
|
+
outputSchema: projectConnectorProviderActionsSchema,
|
|
49
|
+
errorCodes: [
|
|
50
|
+
"UNAUTHENTICATED",
|
|
51
|
+
"PROVIDER_CONFIG_NOT_FOUND",
|
|
52
|
+
"PROJECT_CONNECTOR_ERROR"
|
|
53
|
+
],
|
|
54
|
+
handler: async function({ headers, pathParams }, { json, error }) {
|
|
55
|
+
if (!await config.getExternalUserId(headers)) return error({
|
|
56
|
+
code: "UNAUTHENTICATED",
|
|
57
|
+
message: "Authentication required"
|
|
58
|
+
}, 401);
|
|
59
|
+
try {
|
|
60
|
+
const actions = await deps.projectConnector.listProviderActions(pathParams.providerConfigId);
|
|
61
|
+
if (!actions) return error({
|
|
62
|
+
code: "PROVIDER_CONFIG_NOT_FOUND",
|
|
63
|
+
message: "Provider configuration not found"
|
|
64
|
+
}, 404);
|
|
65
|
+
return json(actions);
|
|
66
|
+
} catch (cause) {
|
|
67
|
+
if (!(cause instanceof ProjectConnectorClientError)) throw cause;
|
|
68
|
+
return error({
|
|
69
|
+
code: "PROJECT_CONNECTOR_ERROR",
|
|
70
|
+
message: cause.message
|
|
71
|
+
}, 502);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}),
|
|
75
|
+
defineRoute({
|
|
76
|
+
method: "GET",
|
|
77
|
+
path: "/status",
|
|
78
|
+
outputSchema: projectConnectorStatusSchema,
|
|
79
|
+
errorCodes: ["UNAUTHENTICATED", "PROJECT_CONNECTOR_ERROR"],
|
|
80
|
+
handler: async function({ headers }, { json, error }) {
|
|
81
|
+
if (!await config.getExternalUserId(headers)) return error({
|
|
82
|
+
code: "UNAUTHENTICATED",
|
|
83
|
+
message: "Authentication required"
|
|
84
|
+
}, 401);
|
|
85
|
+
try {
|
|
86
|
+
return json(await deps.projectConnector.check());
|
|
87
|
+
} catch (cause) {
|
|
88
|
+
if (!(cause instanceof ProjectConnectorClientError)) throw cause;
|
|
89
|
+
return error({
|
|
90
|
+
code: "PROJECT_CONNECTOR_ERROR",
|
|
91
|
+
message: cause.message
|
|
92
|
+
}, 502);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}),
|
|
96
|
+
defineRoute({
|
|
97
|
+
method: "POST",
|
|
98
|
+
path: "/connection-requests",
|
|
99
|
+
inputSchema: projectConnectorConnectInputSchema,
|
|
100
|
+
outputSchema: projectConnectorConnectionSchema,
|
|
101
|
+
errorCodes: [
|
|
102
|
+
"UNAUTHENTICATED",
|
|
103
|
+
"RETURN_URI_NOT_ALLOWED",
|
|
104
|
+
"PROJECT_CONNECTOR_ERROR"
|
|
105
|
+
],
|
|
106
|
+
handler: async function({ headers, input }, { json, error }) {
|
|
107
|
+
const externalUserId = await config.getExternalUserId(headers);
|
|
108
|
+
if (!externalUserId) return error({
|
|
109
|
+
code: "UNAUTHENTICATED",
|
|
110
|
+
message: "Authentication required"
|
|
111
|
+
}, 401);
|
|
112
|
+
const body = await input.valid();
|
|
113
|
+
if (!config.allowedReturnUrls(new URL(body.returnUri))) return error({
|
|
114
|
+
code: "RETURN_URI_NOT_ALLOWED",
|
|
115
|
+
message: "OAuth return URL is not allowed"
|
|
116
|
+
}, 400);
|
|
117
|
+
try {
|
|
118
|
+
const remote = await deps.projectConnector.connect(externalUserId, body);
|
|
119
|
+
if (remote.externalUserId !== externalUserId || remote.connectionName !== body.connectionName || ("providerConfigId" in body ? remote.providerConfigId !== body.providerConfigId : remote.service !== body.service)) throw new ProjectConnectorClientError("connection_identity_mismatch", 502);
|
|
120
|
+
const connection = {
|
|
121
|
+
id: remote.id,
|
|
122
|
+
projectId: remote.projectId,
|
|
123
|
+
providerConfigId: remote.providerConfigId,
|
|
124
|
+
externalUserId,
|
|
125
|
+
service: remote.service,
|
|
126
|
+
connectionName: remote.connectionName,
|
|
127
|
+
authorizationUrl: remote.authorizationUrl,
|
|
128
|
+
expiresAt: remote.expiresAt,
|
|
129
|
+
state: { status: "initiated" }
|
|
130
|
+
};
|
|
131
|
+
const named = {
|
|
132
|
+
projectId: remote.projectId,
|
|
133
|
+
providerConfigId: remote.providerConfigId,
|
|
134
|
+
connectionName: body.connectionName
|
|
135
|
+
};
|
|
136
|
+
await this.handlerTx().retrieve(({ forSchema }) => forSchema(projectConnectorSchema).findFirst("connectedAccount", (b) => b.whereIndex("idx_account_named_connection", (eb) => eb.and(eb("externalUserId", "=", externalUserId), eb("projectId", "=", named.projectId), eb("providerConfigId", "=", named.providerConfigId), eb("connectionName", "=", named.connectionName))))).mutate(({ forSchema, retrieveResult: [account] }) => {
|
|
137
|
+
const uow = forSchema(projectConnectorSchema);
|
|
138
|
+
uow.create("connectionRequest", connection);
|
|
139
|
+
if (!account) uow.triggerHook("onConnectionReadinessChanged", {
|
|
140
|
+
externalUserId,
|
|
141
|
+
service: remote.service,
|
|
142
|
+
connection: named,
|
|
143
|
+
ready: false
|
|
144
|
+
});
|
|
145
|
+
}).execute();
|
|
146
|
+
return json(connection);
|
|
147
|
+
} catch (cause) {
|
|
148
|
+
if (!(cause instanceof ProjectConnectorClientError)) throw cause;
|
|
149
|
+
return error({
|
|
150
|
+
code: "PROJECT_CONNECTOR_ERROR",
|
|
151
|
+
message: cause.message
|
|
152
|
+
}, 502);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
}),
|
|
156
|
+
defineRoute({
|
|
157
|
+
method: "GET",
|
|
158
|
+
path: "/connection-requests/by-name",
|
|
159
|
+
queryParameters: [
|
|
160
|
+
"projectId",
|
|
161
|
+
"providerConfigId",
|
|
162
|
+
"connectionName"
|
|
163
|
+
],
|
|
164
|
+
outputSchema: projectConnectorNamedRequestSchema,
|
|
165
|
+
errorCodes: [
|
|
166
|
+
"UNAUTHENTICATED",
|
|
167
|
+
"INVALID_SELECTOR",
|
|
168
|
+
"CONNECTION_AMBIGUOUS"
|
|
169
|
+
],
|
|
170
|
+
handler: async function({ headers, query }, { json, error }) {
|
|
171
|
+
const externalUserId = await config.getExternalUserId(headers);
|
|
172
|
+
if (!externalUserId) return error({
|
|
173
|
+
code: "UNAUTHENTICATED",
|
|
174
|
+
message: "Authentication required"
|
|
175
|
+
}, 401);
|
|
176
|
+
const selector = projectConnectorNamedConnectionSchema.safeParse({
|
|
177
|
+
projectId: query.get("projectId"),
|
|
178
|
+
providerConfigId: query.get("providerConfigId"),
|
|
179
|
+
connectionName: query.get("connectionName")
|
|
180
|
+
});
|
|
181
|
+
if (!selector.success) return error({
|
|
182
|
+
code: "INVALID_SELECTOR",
|
|
183
|
+
message: "Invalid named connection selector"
|
|
184
|
+
}, 400);
|
|
185
|
+
const [page] = await this.handlerTx().retrieve(({ forSchema }) => forSchema(projectConnectorSchema).findWithCursor("connectionRequest", (b) => b.whereIndex("idx_request_named_connection", (eb) => eb.and(eb("externalUserId", "=", externalUserId), eb("projectId", "=", selector.data.projectId), eb("providerConfigId", "=", selector.data.providerConfigId), eb("connectionName", "=", selector.data.connectionName))).orderByIndex("idx_request_named_connection", "asc").pageSize(2))).execute();
|
|
186
|
+
if (page.items.length > 1) return error({
|
|
187
|
+
code: "CONNECTION_AMBIGUOUS",
|
|
188
|
+
message: "Project Connector named connection matches multiple OAuth requests."
|
|
189
|
+
}, 409);
|
|
190
|
+
const [saved] = page.items;
|
|
191
|
+
return json({ request: saved ? serializeProjectConnectorRequest(saved) : null });
|
|
192
|
+
}
|
|
193
|
+
}),
|
|
194
|
+
defineRoute({
|
|
195
|
+
method: "POST",
|
|
196
|
+
path: "/connection-requests/:requestId/refresh",
|
|
197
|
+
outputSchema: projectConnectorConnectionSchema,
|
|
198
|
+
errorCodes: [
|
|
199
|
+
"UNAUTHENTICATED",
|
|
200
|
+
"REQUEST_NOT_FOUND",
|
|
201
|
+
"PROJECT_CONNECTOR_ERROR"
|
|
202
|
+
],
|
|
203
|
+
handler: async function({ headers, pathParams }, { json, error }) {
|
|
204
|
+
const externalUserId = await config.getExternalUserId(headers);
|
|
205
|
+
if (!externalUserId) return error({
|
|
206
|
+
code: "UNAUTHENTICATED",
|
|
207
|
+
message: "Authentication required"
|
|
208
|
+
}, 401);
|
|
209
|
+
let confirmed = null;
|
|
210
|
+
try {
|
|
211
|
+
const result = await this.handlerTx().retrieve(({ forSchema }) => forSchema(projectConnectorSchema).findFirst("connectionRequest", (b) => b.whereIndex("primary", (eb) => eb("id", "=", pathParams.requestId))).find("connectedAccount", (b) => b.whereIndex("idx_account_external_user_id", (eb) => eb("externalUserId", "=", externalUserId)).select([
|
|
212
|
+
"id",
|
|
213
|
+
"projectId",
|
|
214
|
+
"providerConfigId",
|
|
215
|
+
"service",
|
|
216
|
+
"connectionName"
|
|
217
|
+
]))).afterRetrieve(async (_uow, [saved]) => {
|
|
218
|
+
confirmed = null;
|
|
219
|
+
if (!saved || saved.externalUserId !== externalUserId || saved.state.status !== "initiated") return;
|
|
220
|
+
const remote = await deps.projectConnector.getConnectionRequest(saved.id.toString());
|
|
221
|
+
confirmed = confirmProjectConnectorRequest({
|
|
222
|
+
...saved,
|
|
223
|
+
id: saved.id.toString()
|
|
224
|
+
}, remote);
|
|
225
|
+
}).mutate(({ forSchema, retrieveResult: [saved, accounts] }) => {
|
|
226
|
+
if (!saved || saved.externalUserId !== externalUserId) return null;
|
|
227
|
+
const state = confirmed ?? saved.state;
|
|
228
|
+
const uow = forSchema(projectConnectorSchema);
|
|
229
|
+
if (saved.state.status === "initiated" && state.status !== "initiated") {
|
|
230
|
+
uow.update("connectionRequest", saved.id, (b) => b.set({ state }).check());
|
|
231
|
+
if (state.status === "connected") {
|
|
232
|
+
const existing = accounts.find((account) => account.id.toString() === state.connectedAccountId);
|
|
233
|
+
if (existing) uow.delete("connectedAccount", existing.id, (b) => b.check());
|
|
234
|
+
uow.create("connectedAccount", {
|
|
235
|
+
id: state.connectedAccountId,
|
|
236
|
+
projectId: saved.projectId,
|
|
237
|
+
providerConfigId: saved.providerConfigId,
|
|
238
|
+
externalUserId: saved.externalUserId,
|
|
239
|
+
service: saved.service,
|
|
240
|
+
connectionName: saved.connectionName
|
|
241
|
+
});
|
|
242
|
+
const sameName = (named) => (account) => account.projectId === named.projectId && account.providerConfigId === named.providerConfigId && account.connectionName === named.connectionName;
|
|
243
|
+
if (existing && !sameName(saved)(existing) && !accounts.some((account) => account !== existing && sameName(existing)(account))) uow.triggerHook("onConnectionReadinessChanged", {
|
|
244
|
+
externalUserId,
|
|
245
|
+
service: existing.service,
|
|
246
|
+
connection: {
|
|
247
|
+
projectId: existing.projectId,
|
|
248
|
+
providerConfigId: existing.providerConfigId,
|
|
249
|
+
connectionName: existing.connectionName
|
|
250
|
+
},
|
|
251
|
+
ready: false
|
|
252
|
+
});
|
|
253
|
+
if (!accounts.some(sameName(saved))) uow.triggerHook("onConnectionReadinessChanged", {
|
|
254
|
+
externalUserId,
|
|
255
|
+
service: saved.service,
|
|
256
|
+
connection: {
|
|
257
|
+
projectId: saved.projectId,
|
|
258
|
+
providerConfigId: saved.providerConfigId,
|
|
259
|
+
connectionName: saved.connectionName
|
|
260
|
+
},
|
|
261
|
+
ready: true
|
|
262
|
+
});
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
return serializeProjectConnectorRequest({
|
|
266
|
+
...saved,
|
|
267
|
+
state
|
|
268
|
+
});
|
|
269
|
+
}).execute();
|
|
270
|
+
if (!result) return error({
|
|
271
|
+
code: "REQUEST_NOT_FOUND",
|
|
272
|
+
message: "Connection request not found"
|
|
273
|
+
}, 404);
|
|
274
|
+
return json(result);
|
|
275
|
+
} catch (cause) {
|
|
276
|
+
if (!(cause instanceof ProjectConnectorClientError)) throw cause;
|
|
277
|
+
return error({
|
|
278
|
+
code: "PROJECT_CONNECTOR_ERROR",
|
|
279
|
+
message: cause.message
|
|
280
|
+
}, 502);
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
}),
|
|
284
|
+
defineRoute({
|
|
285
|
+
method: "GET",
|
|
286
|
+
path: "/accounts",
|
|
287
|
+
queryParameters: ["cursor"],
|
|
288
|
+
outputSchema: projectConnectorAccountsSchema,
|
|
289
|
+
errorCodes: ["UNAUTHENTICATED", "INVALID_CURSOR"],
|
|
290
|
+
handler: async function({ headers, query }, { json, error }) {
|
|
291
|
+
const externalUserId = await config.getExternalUserId(headers);
|
|
292
|
+
if (!externalUserId) return error({
|
|
293
|
+
code: "UNAUTHENTICATED",
|
|
294
|
+
message: "Authentication required"
|
|
295
|
+
}, 401);
|
|
296
|
+
let cursor = null;
|
|
297
|
+
const rawCursor = query.get("cursor");
|
|
298
|
+
if (rawCursor) try {
|
|
299
|
+
cursor = decodeCursor(rawCursor);
|
|
300
|
+
const accountId = cursor.indexValues["id"];
|
|
301
|
+
if (cursor.indexName !== "idx_account_external_user_id" || cursor.orderDirection !== "asc" || cursor.pageSize !== 25 || cursor.indexValues["externalUserId"] !== externalUserId || typeof accountId !== "string" || accountId.length === 0) throw new Error("Account cursor does not match this user or query");
|
|
302
|
+
} catch {
|
|
303
|
+
return error({
|
|
304
|
+
code: "INVALID_CURSOR",
|
|
305
|
+
message: "Invalid account cursor"
|
|
306
|
+
}, 400);
|
|
307
|
+
}
|
|
308
|
+
const [page] = await this.handlerTx().retrieve(({ forSchema }) => forSchema(projectConnectorSchema).findWithCursor("connectedAccount", (b) => {
|
|
309
|
+
const ordered = b.whereIndex("idx_account_external_user_id", (eb) => eb("externalUserId", "=", externalUserId)).orderByIndex("idx_account_external_user_id", "asc").pageSize(25);
|
|
310
|
+
return cursor ? ordered.after(cursor) : ordered;
|
|
311
|
+
})).execute();
|
|
312
|
+
return json({
|
|
313
|
+
accounts: page.items.map((account) => ({
|
|
314
|
+
id: account.id.toString(),
|
|
315
|
+
projectId: account.projectId,
|
|
316
|
+
providerConfigId: account.providerConfigId,
|
|
317
|
+
externalUserId: account.externalUserId,
|
|
318
|
+
service: account.service,
|
|
319
|
+
connectionName: account.connectionName
|
|
320
|
+
})),
|
|
321
|
+
cursor: page.cursor?.encode() ?? null,
|
|
322
|
+
hasNextPage: page.hasNextPage
|
|
323
|
+
});
|
|
324
|
+
}
|
|
325
|
+
}),
|
|
326
|
+
defineRoute({
|
|
327
|
+
method: "GET",
|
|
328
|
+
path: "/accounts/by-name",
|
|
329
|
+
queryParameters: [
|
|
330
|
+
"projectId",
|
|
331
|
+
"providerConfigId",
|
|
332
|
+
"connectionName"
|
|
333
|
+
],
|
|
334
|
+
outputSchema: projectConnectorNamedAccountSchema,
|
|
335
|
+
errorCodes: [
|
|
336
|
+
"UNAUTHENTICATED",
|
|
337
|
+
"INVALID_SELECTOR",
|
|
338
|
+
"CONNECTION_AMBIGUOUS"
|
|
339
|
+
],
|
|
340
|
+
handler: async function({ headers, query }, { json, error }) {
|
|
341
|
+
const externalUserId = await config.getExternalUserId(headers);
|
|
342
|
+
if (!externalUserId) return error({
|
|
343
|
+
code: "UNAUTHENTICATED",
|
|
344
|
+
message: "Authentication required"
|
|
345
|
+
}, 401);
|
|
346
|
+
const selector = projectConnectorNamedConnectionSchema.safeParse({
|
|
347
|
+
projectId: query.get("projectId"),
|
|
348
|
+
providerConfigId: query.get("providerConfigId"),
|
|
349
|
+
connectionName: query.get("connectionName")
|
|
350
|
+
});
|
|
351
|
+
if (!selector.success) return error({
|
|
352
|
+
code: "INVALID_SELECTOR",
|
|
353
|
+
message: "Invalid named connection selector"
|
|
354
|
+
}, 400);
|
|
355
|
+
const [page] = await this.handlerTx().retrieve(({ forSchema }) => forSchema(projectConnectorSchema).findWithCursor("connectedAccount", (b) => b.whereIndex("idx_account_named_connection", (eb) => eb.and(eb("externalUserId", "=", externalUserId), eb("projectId", "=", selector.data.projectId), eb("providerConfigId", "=", selector.data.providerConfigId), eb("connectionName", "=", selector.data.connectionName))).orderByIndex("idx_account_named_connection", "asc").pageSize(2))).execute();
|
|
356
|
+
if (page.items.length > 1) return error({
|
|
357
|
+
code: "CONNECTION_AMBIGUOUS",
|
|
358
|
+
message: "Project Connector named connection matches multiple confirmed accounts."
|
|
359
|
+
}, 409);
|
|
360
|
+
const [account] = page.items;
|
|
361
|
+
return json({ account: account ? {
|
|
362
|
+
id: account.id.toString(),
|
|
363
|
+
projectId: account.projectId,
|
|
364
|
+
providerConfigId: account.providerConfigId,
|
|
365
|
+
externalUserId: account.externalUserId,
|
|
366
|
+
service: account.service,
|
|
367
|
+
connectionName: account.connectionName
|
|
368
|
+
} : null });
|
|
369
|
+
}
|
|
370
|
+
}),
|
|
371
|
+
defineRoute({
|
|
372
|
+
method: "GET",
|
|
373
|
+
path: "/accounts/:accountId/profile",
|
|
374
|
+
outputSchema: projectConnectorProfileSchema,
|
|
375
|
+
errorCodes: [
|
|
376
|
+
"UNAUTHENTICATED",
|
|
377
|
+
"ACCOUNT_NOT_FOUND",
|
|
378
|
+
"PROJECT_CONNECTOR_ERROR"
|
|
379
|
+
],
|
|
380
|
+
handler: async function({ headers, pathParams }, { json, error }) {
|
|
381
|
+
const externalUserId = await config.getExternalUserId(headers);
|
|
382
|
+
if (!externalUserId) return error({
|
|
383
|
+
code: "UNAUTHENTICATED",
|
|
384
|
+
message: "Authentication required"
|
|
385
|
+
}, 401);
|
|
386
|
+
const [account] = await this.handlerTx().retrieve(({ forSchema }) => forSchema(projectConnectorSchema).findFirst("connectedAccount", (b) => b.whereIndex("primary", (eb) => eb("id", "=", pathParams.accountId)))).execute();
|
|
387
|
+
if (!account || account.externalUserId !== externalUserId) return error({
|
|
388
|
+
code: "ACCOUNT_NOT_FOUND",
|
|
389
|
+
message: "Connected account not found"
|
|
390
|
+
}, 404);
|
|
391
|
+
try {
|
|
392
|
+
const profile = await deps.projectConnector.getProfile(account.id.toString());
|
|
393
|
+
if (profile.connectedAccountId !== account.id.toString() || profile.externalUserId !== externalUserId || profile.service !== account.service) throw new ProjectConnectorClientError("account_identity_mismatch", 502);
|
|
394
|
+
return json(profile);
|
|
395
|
+
} catch (cause) {
|
|
396
|
+
if (!(cause instanceof ProjectConnectorClientError)) throw cause;
|
|
397
|
+
return error({
|
|
398
|
+
code: "PROJECT_CONNECTOR_ERROR",
|
|
399
|
+
message: cause.message
|
|
400
|
+
}, 502);
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
}),
|
|
404
|
+
defineRoute({
|
|
405
|
+
method: "POST",
|
|
406
|
+
path: "/accounts/:accountId/actions/:actionId",
|
|
407
|
+
inputSchema: z.strictObject({ input: z.record(z.string(), z.unknown()) }),
|
|
408
|
+
outputSchema: projectConnectorExecutionSchema,
|
|
409
|
+
errorCodes: [
|
|
410
|
+
"UNAUTHENTICATED",
|
|
411
|
+
"ACCOUNT_NOT_FOUND",
|
|
412
|
+
"ACTION_SERVICE_MISMATCH",
|
|
413
|
+
"PROJECT_CONNECTOR_ERROR"
|
|
414
|
+
],
|
|
415
|
+
handler: async function({ headers, pathParams, input }, { json, error }) {
|
|
416
|
+
const externalUserId = await config.getExternalUserId(headers);
|
|
417
|
+
if (!externalUserId) return error({
|
|
418
|
+
code: "UNAUTHENTICATED",
|
|
419
|
+
message: "Authentication required"
|
|
420
|
+
}, 401);
|
|
421
|
+
const body = await input.valid();
|
|
422
|
+
const [account] = await this.handlerTx().retrieve(({ forSchema }) => forSchema(projectConnectorSchema).findFirst("connectedAccount", (b) => b.whereIndex("primary", (eb) => eb("id", "=", pathParams.accountId)))).execute();
|
|
423
|
+
if (!account || account.externalUserId !== externalUserId) return error({
|
|
424
|
+
code: "ACCOUNT_NOT_FOUND",
|
|
425
|
+
message: "Connected account not found"
|
|
426
|
+
}, 404);
|
|
427
|
+
if (!pathParams.actionId.startsWith(`${account.service}.`) || !/^[a-z0-9_-]+\.[a-z0-9_-]+$/.test(pathParams.actionId)) return error({
|
|
428
|
+
code: "ACTION_SERVICE_MISMATCH",
|
|
429
|
+
message: "Action must belong to the connected account service"
|
|
430
|
+
}, 400);
|
|
431
|
+
try {
|
|
432
|
+
return json(await deps.projectConnector.execute({
|
|
433
|
+
...account,
|
|
434
|
+
id: account.id.toString()
|
|
435
|
+
}, pathParams.actionId, body.input));
|
|
436
|
+
} catch (cause) {
|
|
437
|
+
if (!(cause instanceof ProjectConnectorClientError)) throw cause;
|
|
438
|
+
return error({
|
|
439
|
+
code: "PROJECT_CONNECTOR_ERROR",
|
|
440
|
+
message: cause.message
|
|
441
|
+
}, 502);
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
})
|
|
445
|
+
]);
|
|
446
|
+
//#endregion
|
|
447
|
+
export { projectConnectorRoutes };
|
|
448
|
+
|
|
449
|
+
//# sourceMappingURL=routes.js.map
|