@chaaskit/server 0.1.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/dist/api/admin.js +438 -0
- package/dist/api/admin.js.map +1 -0
- package/dist/api/agents.js +21 -0
- package/dist/api/agents.js.map +1 -0
- package/dist/api/api-keys.js +122 -0
- package/dist/api/api-keys.js.map +1 -0
- package/dist/api/auth.js +399 -0
- package/dist/api/auth.js.map +1 -0
- package/dist/api/chat.js +900 -0
- package/dist/api/chat.js.map +1 -0
- package/dist/api/config.js +91 -0
- package/dist/api/config.js.map +1 -0
- package/dist/api/documents.js +237 -0
- package/dist/api/documents.js.map +1 -0
- package/dist/api/export.js +107 -0
- package/dist/api/export.js.map +1 -0
- package/dist/api/health.js +25 -0
- package/dist/api/health.js.map +1 -0
- package/dist/api/mcp-server.js +84 -0
- package/dist/api/mcp-server.js.map +1 -0
- package/dist/api/mcp.js +400 -0
- package/dist/api/mcp.js.map +1 -0
- package/dist/api/mentions.js +94 -0
- package/dist/api/mentions.js.map +1 -0
- package/dist/api/oauth.js +366 -0
- package/dist/api/oauth.js.map +1 -0
- package/dist/api/payments.js +473 -0
- package/dist/api/payments.js.map +1 -0
- package/dist/api/projects.js +301 -0
- package/dist/api/projects.js.map +1 -0
- package/dist/api/scheduled-prompts.js +617 -0
- package/dist/api/scheduled-prompts.js.map +1 -0
- package/dist/api/search.js +85 -0
- package/dist/api/search.js.map +1 -0
- package/dist/api/share.js +188 -0
- package/dist/api/share.js.map +1 -0
- package/dist/api/slack.js +468 -0
- package/dist/api/slack.js.map +1 -0
- package/dist/api/teams.js +693 -0
- package/dist/api/teams.js.map +1 -0
- package/dist/api/templates.js +134 -0
- package/dist/api/templates.js.map +1 -0
- package/dist/api/threads.js +323 -0
- package/dist/api/threads.js.map +1 -0
- package/dist/api/upload.js +57 -0
- package/dist/api/upload.js.map +1 -0
- package/dist/api/user.js +111 -0
- package/dist/api/user.js.map +1 -0
- package/dist/api/v1/openai.js +245 -0
- package/dist/api/v1/openai.js.map +1 -0
- package/dist/app.js +168 -0
- package/dist/app.js.map +1 -0
- package/dist/bin/cli.js +57 -0
- package/dist/bin/cli.js.map +1 -0
- package/dist/commands/db-sync.js +108 -0
- package/dist/commands/db-sync.js.map +1 -0
- package/dist/config/loader.js +374 -0
- package/dist/config/loader.js.map +1 -0
- package/dist/documents/extractors.js +136 -0
- package/dist/documents/extractors.js.map +1 -0
- package/dist/extensions/glob.js +53 -0
- package/dist/extensions/glob.js.map +1 -0
- package/dist/extensions/loader.js +72 -0
- package/dist/extensions/loader.js.map +1 -0
- package/dist/index.js +25 -0
- package/dist/index.js.map +1 -0
- package/dist/loaders/index.js +75 -0
- package/dist/loaders/index.js.map +1 -0
- package/dist/mcp/client.js +551 -0
- package/dist/mcp/client.js.map +1 -0
- package/dist/mcp/server.js +335 -0
- package/dist/mcp/server.js.map +1 -0
- package/dist/middleware/apiKeyAuth.js +136 -0
- package/dist/middleware/apiKeyAuth.js.map +1 -0
- package/dist/middleware/auth.js +192 -0
- package/dist/middleware/auth.js.map +1 -0
- package/dist/middleware/errorHandler.js +41 -0
- package/dist/middleware/errorHandler.js.map +1 -0
- package/dist/middleware/mcpServerAuth.js +164 -0
- package/dist/middleware/mcpServerAuth.js.map +1 -0
- package/dist/middleware/requestLogger.js +9 -0
- package/dist/middleware/requestLogger.js.map +1 -0
- package/dist/middleware/team.js +132 -0
- package/dist/middleware/team.js.map +1 -0
- package/dist/oauth/server.js +410 -0
- package/dist/oauth/server.js.map +1 -0
- package/dist/queue/cli.js +93 -0
- package/dist/queue/cli.js.map +1 -0
- package/dist/queue/handlers/index.js +91 -0
- package/dist/queue/handlers/index.js.map +1 -0
- package/dist/queue/handlers/scheduled-prompt.js +270 -0
- package/dist/queue/handlers/scheduled-prompt.js.map +1 -0
- package/dist/queue/index.js +91 -0
- package/dist/queue/index.js.map +1 -0
- package/dist/queue/providers/memory.js +296 -0
- package/dist/queue/providers/memory.js.map +1 -0
- package/dist/queue/providers/sqs.js +275 -0
- package/dist/queue/providers/sqs.js.map +1 -0
- package/dist/queue/scheduler.js +355 -0
- package/dist/queue/scheduler.js.map +1 -0
- package/dist/queue/types.js +5 -0
- package/dist/queue/types.js.map +1 -0
- package/dist/queue/worker.js +230 -0
- package/dist/queue/worker.js.map +1 -0
- package/dist/registry/index.js +40 -0
- package/dist/registry/index.js.map +1 -0
- package/dist/server.js +207 -0
- package/dist/server.js.map +1 -0
- package/dist/services/agent.js +530 -0
- package/dist/services/agent.js.map +1 -0
- package/dist/services/agents.js +194 -0
- package/dist/services/agents.js.map +1 -0
- package/dist/services/documents.js +507 -0
- package/dist/services/documents.js.map +1 -0
- package/dist/services/email/index.js +91 -0
- package/dist/services/email/index.js.map +1 -0
- package/dist/services/email/providers/ses.js +97 -0
- package/dist/services/email/providers/ses.js.map +1 -0
- package/dist/services/email/templates.js +194 -0
- package/dist/services/email/templates.js.map +1 -0
- package/dist/services/email/types.js +5 -0
- package/dist/services/email/types.js.map +1 -0
- package/dist/services/encryption.js +69 -0
- package/dist/services/encryption.js.map +1 -0
- package/dist/services/oauth-discovery.js +226 -0
- package/dist/services/oauth-discovery.js.map +1 -0
- package/dist/services/pendingConfirmation.js +105 -0
- package/dist/services/pendingConfirmation.js.map +1 -0
- package/dist/services/scheduledPrompts.js +70 -0
- package/dist/services/scheduledPrompts.js.map +1 -0
- package/dist/services/slack/client.js +174 -0
- package/dist/services/slack/client.js.map +1 -0
- package/dist/services/slack/events.js +189 -0
- package/dist/services/slack/events.js.map +1 -0
- package/dist/services/slack/index.js +6 -0
- package/dist/services/slack/index.js.map +1 -0
- package/dist/services/slack/notifications.js +124 -0
- package/dist/services/slack/notifications.js.map +1 -0
- package/dist/services/slack/signature.js +74 -0
- package/dist/services/slack/signature.js.map +1 -0
- package/dist/services/slack/thread-context.js +191 -0
- package/dist/services/slack/thread-context.js.map +1 -0
- package/dist/services/toolConfirmation.js +55 -0
- package/dist/services/toolConfirmation.js.map +1 -0
- package/dist/services/usage.js +241 -0
- package/dist/services/usage.js.map +1 -0
- package/dist/ssr/build.js +90 -0
- package/dist/ssr/build.js.map +1 -0
- package/dist/ssr/components/SSRMessageList.js +120 -0
- package/dist/ssr/components/SSRMessageList.js.map +1 -0
- package/dist/ssr/entry.client.js +8 -0
- package/dist/ssr/entry.client.js.map +1 -0
- package/dist/ssr/entry.server.js +71 -0
- package/dist/ssr/entry.server.js.map +1 -0
- package/dist/ssr/handler.js +51 -0
- package/dist/ssr/handler.js.map +1 -0
- package/dist/ssr/root.js +184 -0
- package/dist/ssr/root.js.map +1 -0
- package/dist/ssr/routes/login.js +140 -0
- package/dist/ssr/routes/login.js.map +1 -0
- package/dist/ssr/routes/pricing.js +195 -0
- package/dist/ssr/routes/pricing.js.map +1 -0
- package/dist/ssr/routes/privacy.js +39 -0
- package/dist/ssr/routes/privacy.js.map +1 -0
- package/dist/ssr/routes/register.js +148 -0
- package/dist/ssr/routes/register.js.map +1 -0
- package/dist/ssr/routes/shared.$shareId.js +153 -0
- package/dist/ssr/routes/shared.$shareId.js.map +1 -0
- package/dist/ssr/routes/terms.js +39 -0
- package/dist/ssr/routes/terms.js.map +1 -0
- package/dist/storage/index.js +43 -0
- package/dist/storage/index.js.map +1 -0
- package/dist/storage/providers/database.js +38 -0
- package/dist/storage/providers/database.js.map +1 -0
- package/dist/storage/providers/filesystem.js +51 -0
- package/dist/storage/providers/filesystem.js.map +1 -0
- package/dist/storage/types.js +2 -0
- package/dist/storage/types.js.map +1 -0
- package/dist/tools/documents.js +336 -0
- package/dist/tools/documents.js.map +1 -0
- package/dist/tools/get-plan-usage.js +82 -0
- package/dist/tools/get-plan-usage.js.map +1 -0
- package/dist/tools/index.js +106 -0
- package/dist/tools/index.js.map +1 -0
- package/dist/tools/types.js +2 -0
- package/dist/tools/types.js.map +1 -0
- package/dist/tools/web-scrape.js +145 -0
- package/dist/tools/web-scrape.js.map +1 -0
- package/package.json +93 -0
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AWS SES Email Provider
|
|
3
|
+
*
|
|
4
|
+
* Production-ready email provider using Amazon SES.
|
|
5
|
+
* Uses the AWS SDK default credential chain (environment variables, shared credentials,
|
|
6
|
+
* IAM instance/task roles, etc.).
|
|
7
|
+
*
|
|
8
|
+
* Requires @aws-sdk/client-ses to be installed:
|
|
9
|
+
* pnpm add @aws-sdk/client-ses
|
|
10
|
+
*/
|
|
11
|
+
/**
|
|
12
|
+
* AWS SES Email Provider
|
|
13
|
+
*/
|
|
14
|
+
export class SESEmailProvider {
|
|
15
|
+
name = 'ses';
|
|
16
|
+
client = null;
|
|
17
|
+
region;
|
|
18
|
+
fromAddress;
|
|
19
|
+
fromName;
|
|
20
|
+
closed = false;
|
|
21
|
+
constructor(config, fromAddress, fromName) {
|
|
22
|
+
this.region = config.region;
|
|
23
|
+
this.fromAddress = fromAddress;
|
|
24
|
+
this.fromName = fromName;
|
|
25
|
+
console.log(`[SES] Email provider initialized (region: ${config.region})`);
|
|
26
|
+
}
|
|
27
|
+
async getClient() {
|
|
28
|
+
if (this.client) {
|
|
29
|
+
return this.client;
|
|
30
|
+
}
|
|
31
|
+
// Dynamic import to avoid requiring the SDK at compile time
|
|
32
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
33
|
+
const sesModule = await import('@aws-sdk/client-ses');
|
|
34
|
+
const SESClient = sesModule.SESClient;
|
|
35
|
+
this.client = new SESClient({
|
|
36
|
+
region: this.region,
|
|
37
|
+
});
|
|
38
|
+
return this.client;
|
|
39
|
+
}
|
|
40
|
+
async send(message) {
|
|
41
|
+
if (this.closed) {
|
|
42
|
+
throw new Error('Email provider is closed');
|
|
43
|
+
}
|
|
44
|
+
const client = await this.getClient();
|
|
45
|
+
const toAddresses = Array.isArray(message.to) ? message.to : [message.to];
|
|
46
|
+
const from = this.fromName
|
|
47
|
+
? `${this.fromName} <${this.fromAddress}>`
|
|
48
|
+
: this.fromAddress;
|
|
49
|
+
// Dynamic import for the command
|
|
50
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
51
|
+
const sesModule = await import('@aws-sdk/client-ses');
|
|
52
|
+
const SendEmailCommand = sesModule.SendEmailCommand;
|
|
53
|
+
const commandInput = {
|
|
54
|
+
Source: from,
|
|
55
|
+
Destination: {
|
|
56
|
+
ToAddresses: toAddresses,
|
|
57
|
+
},
|
|
58
|
+
Message: {
|
|
59
|
+
Subject: {
|
|
60
|
+
Data: message.subject,
|
|
61
|
+
Charset: 'UTF-8',
|
|
62
|
+
},
|
|
63
|
+
Body: {
|
|
64
|
+
...(message.text && {
|
|
65
|
+
Text: {
|
|
66
|
+
Data: message.text,
|
|
67
|
+
Charset: 'UTF-8',
|
|
68
|
+
},
|
|
69
|
+
}),
|
|
70
|
+
...(message.html && {
|
|
71
|
+
Html: {
|
|
72
|
+
Data: message.html,
|
|
73
|
+
Charset: 'UTF-8',
|
|
74
|
+
},
|
|
75
|
+
}),
|
|
76
|
+
},
|
|
77
|
+
},
|
|
78
|
+
...(message.replyTo && {
|
|
79
|
+
ReplyToAddresses: [message.replyTo],
|
|
80
|
+
}),
|
|
81
|
+
};
|
|
82
|
+
const command = new SendEmailCommand(commandInput);
|
|
83
|
+
const response = await client.send(command);
|
|
84
|
+
console.log(`[SES] Email sent to ${toAddresses.join(', ')} (messageId: ${response.MessageId})`);
|
|
85
|
+
return {
|
|
86
|
+
messageId: response.MessageId ?? 'unknown',
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
async close() {
|
|
90
|
+
this.closed = true;
|
|
91
|
+
if (this.client) {
|
|
92
|
+
this.client.destroy();
|
|
93
|
+
}
|
|
94
|
+
console.log('[SES] Email provider closed');
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
//# sourceMappingURL=ses.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ses.js","sourceRoot":"","sources":["../../../../src/services/email/providers/ses.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Email template generators for transactional emails
|
|
3
|
+
*/
|
|
4
|
+
/**
|
|
5
|
+
* Generate the common email wrapper HTML
|
|
6
|
+
*/
|
|
7
|
+
function wrapInEmailTemplate(content, params) {
|
|
8
|
+
return `
|
|
9
|
+
<!DOCTYPE html>
|
|
10
|
+
<html>
|
|
11
|
+
<head>
|
|
12
|
+
<meta charset="utf-8">
|
|
13
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
14
|
+
<title>${params.appName}</title>
|
|
15
|
+
<style>
|
|
16
|
+
body {
|
|
17
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
|
|
18
|
+
line-height: 1.6;
|
|
19
|
+
color: #333;
|
|
20
|
+
background-color: #f5f5f5;
|
|
21
|
+
margin: 0;
|
|
22
|
+
padding: 0;
|
|
23
|
+
}
|
|
24
|
+
.container {
|
|
25
|
+
max-width: 560px;
|
|
26
|
+
margin: 0 auto;
|
|
27
|
+
padding: 40px 20px;
|
|
28
|
+
}
|
|
29
|
+
.card {
|
|
30
|
+
background-color: #ffffff;
|
|
31
|
+
border-radius: 8px;
|
|
32
|
+
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
|
33
|
+
padding: 40px;
|
|
34
|
+
}
|
|
35
|
+
.header {
|
|
36
|
+
text-align: center;
|
|
37
|
+
margin-bottom: 32px;
|
|
38
|
+
}
|
|
39
|
+
.header h1 {
|
|
40
|
+
color: #1a1a1a;
|
|
41
|
+
font-size: 24px;
|
|
42
|
+
font-weight: 600;
|
|
43
|
+
margin: 0;
|
|
44
|
+
}
|
|
45
|
+
.content {
|
|
46
|
+
margin-bottom: 32px;
|
|
47
|
+
}
|
|
48
|
+
.code-display {
|
|
49
|
+
background-color: #f8f9fa;
|
|
50
|
+
border: 2px solid #e9ecef;
|
|
51
|
+
border-radius: 8px;
|
|
52
|
+
padding: 24px;
|
|
53
|
+
text-align: center;
|
|
54
|
+
margin: 24px 0;
|
|
55
|
+
}
|
|
56
|
+
.code {
|
|
57
|
+
font-family: 'SF Mono', Monaco, 'Courier New', monospace;
|
|
58
|
+
font-size: 36px;
|
|
59
|
+
font-weight: 700;
|
|
60
|
+
letter-spacing: 8px;
|
|
61
|
+
color: #1a1a1a;
|
|
62
|
+
}
|
|
63
|
+
.button {
|
|
64
|
+
display: inline-block;
|
|
65
|
+
background-color: #6366f1;
|
|
66
|
+
color: #ffffff !important;
|
|
67
|
+
text-decoration: none;
|
|
68
|
+
padding: 14px 32px;
|
|
69
|
+
border-radius: 6px;
|
|
70
|
+
font-weight: 600;
|
|
71
|
+
font-size: 16px;
|
|
72
|
+
}
|
|
73
|
+
.button:hover {
|
|
74
|
+
background-color: #4f46e5;
|
|
75
|
+
}
|
|
76
|
+
.button-container {
|
|
77
|
+
text-align: center;
|
|
78
|
+
margin: 24px 0;
|
|
79
|
+
}
|
|
80
|
+
.footer {
|
|
81
|
+
text-align: center;
|
|
82
|
+
color: #6b7280;
|
|
83
|
+
font-size: 14px;
|
|
84
|
+
margin-top: 24px;
|
|
85
|
+
}
|
|
86
|
+
.muted {
|
|
87
|
+
color: #6b7280;
|
|
88
|
+
font-size: 14px;
|
|
89
|
+
}
|
|
90
|
+
.divider {
|
|
91
|
+
border-top: 1px solid #e5e7eb;
|
|
92
|
+
margin: 24px 0;
|
|
93
|
+
}
|
|
94
|
+
</style>
|
|
95
|
+
</head>
|
|
96
|
+
<body>
|
|
97
|
+
<div class="container">
|
|
98
|
+
<div class="card">
|
|
99
|
+
${content}
|
|
100
|
+
</div>
|
|
101
|
+
<div class="footer">
|
|
102
|
+
<p>© ${new Date().getFullYear()} ${params.appName}. All rights reserved.</p>
|
|
103
|
+
</div>
|
|
104
|
+
</div>
|
|
105
|
+
</body>
|
|
106
|
+
</html>
|
|
107
|
+
`.trim();
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Generate verification email HTML
|
|
111
|
+
*/
|
|
112
|
+
export function generateVerificationEmailHtml(code, config) {
|
|
113
|
+
const content = `
|
|
114
|
+
<div class="header">
|
|
115
|
+
<h1>Verify your email</h1>
|
|
116
|
+
</div>
|
|
117
|
+
<div class="content">
|
|
118
|
+
<p>Welcome to ${config.app.name}! Please enter this verification code to complete your registration:</p>
|
|
119
|
+
<div class="code-display">
|
|
120
|
+
<span class="code">${code}</span>
|
|
121
|
+
</div>
|
|
122
|
+
<p class="muted">This code will expire in 15 minutes. If you didn't create an account with ${config.app.name}, you can safely ignore this email.</p>
|
|
123
|
+
</div>
|
|
124
|
+
`;
|
|
125
|
+
return wrapInEmailTemplate(content, {
|
|
126
|
+
appName: config.app.name,
|
|
127
|
+
appUrl: config.app.url,
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* Generate verification email plain text
|
|
132
|
+
*/
|
|
133
|
+
export function generateVerificationEmailText(code, config) {
|
|
134
|
+
return `
|
|
135
|
+
Verify your email
|
|
136
|
+
|
|
137
|
+
Welcome to ${config.app.name}! Please enter this verification code to complete your registration:
|
|
138
|
+
|
|
139
|
+
${code}
|
|
140
|
+
|
|
141
|
+
This code will expire in 15 minutes.
|
|
142
|
+
|
|
143
|
+
If you didn't create an account with ${config.app.name}, you can safely ignore this email.
|
|
144
|
+
`.trim();
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
147
|
+
* Generate team invitation email HTML
|
|
148
|
+
*/
|
|
149
|
+
export function generateTeamInviteEmailHtml(teamName, inviteUrl, inviterName, config) {
|
|
150
|
+
const inviterText = inviterName
|
|
151
|
+
? `${inviterName} has invited you`
|
|
152
|
+
: 'You have been invited';
|
|
153
|
+
const content = `
|
|
154
|
+
<div class="header">
|
|
155
|
+
<h1>You're invited to join a team</h1>
|
|
156
|
+
</div>
|
|
157
|
+
<div class="content">
|
|
158
|
+
<p>${inviterText} to join <strong>${teamName}</strong> on ${config.app.name}.</p>
|
|
159
|
+
<p>Click the button below to accept the invitation and join the team:</p>
|
|
160
|
+
<div class="button-container">
|
|
161
|
+
<a href="${inviteUrl}" class="button">Accept Invitation</a>
|
|
162
|
+
</div>
|
|
163
|
+
<div class="divider"></div>
|
|
164
|
+
<p class="muted">This invitation will expire in 7 days. If you don't have an account yet, you'll be able to create one when you accept the invitation.</p>
|
|
165
|
+
<p class="muted">If you can't click the button, copy and paste this link into your browser:</p>
|
|
166
|
+
<p class="muted" style="word-break: break-all;">${inviteUrl}</p>
|
|
167
|
+
</div>
|
|
168
|
+
`;
|
|
169
|
+
return wrapInEmailTemplate(content, {
|
|
170
|
+
appName: config.app.name,
|
|
171
|
+
appUrl: config.app.url,
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
/**
|
|
175
|
+
* Generate team invitation email plain text
|
|
176
|
+
*/
|
|
177
|
+
export function generateTeamInviteEmailText(teamName, inviteUrl, inviterName, config) {
|
|
178
|
+
const inviterText = inviterName
|
|
179
|
+
? `${inviterName} has invited you`
|
|
180
|
+
: 'You have been invited';
|
|
181
|
+
return `
|
|
182
|
+
You're invited to join a team
|
|
183
|
+
|
|
184
|
+
${inviterText} to join "${teamName}" on ${config.app.name}.
|
|
185
|
+
|
|
186
|
+
Accept the invitation by visiting this link:
|
|
187
|
+
${inviteUrl}
|
|
188
|
+
|
|
189
|
+
This invitation will expire in 7 days.
|
|
190
|
+
|
|
191
|
+
If you don't have an account yet, you'll be able to create one when you accept the invitation.
|
|
192
|
+
`.trim();
|
|
193
|
+
}
|
|
194
|
+
//# sourceMappingURL=templates.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"templates.js","sourceRoot":"","sources":["../../../src/services/email/templates.ts"],"names":[],"mappings":"AAAA;;GAEG;AASH;;GAEG;AACH,SAAS,mBAAmB,CAAC,OAAe,EAAE,MAA0B;IACtE,OAAO;;;;;;WAME,MAAM,CAAC,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;QAqFjB,OAAO;;;kBAGG,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,IAAI,MAAM,CAAC,OAAO;;;;;CAK3D,CAAC,IAAI,EAAE,CAAC;AACT,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,6BAA6B,CAC3C,IAAY,EACZ,MAAiB;IAEjB,MAAM,OAAO,GAAG;;;;;sBAKI,MAAM,CAAC,GAAG,CAAC,IAAI;;6BAER,IAAI;;mGAEkE,MAAM,CAAC,GAAG,CAAC,IAAI;;GAE/G,CAAC;IAEF,OAAO,mBAAmB,CAAC,OAAO,EAAE;QAClC,OAAO,EAAE,MAAM,CAAC,GAAG,CAAC,IAAI;QACxB,MAAM,EAAE,MAAM,CAAC,GAAG,CAAC,GAAG;KACvB,CAAC,CAAC;AACL,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,6BAA6B,CAC3C,IAAY,EACZ,MAAiB;IAEjB,OAAO;;;aAGI,MAAM,CAAC,GAAG,CAAC,IAAI;;EAE1B,IAAI;;;;uCAIiC,MAAM,CAAC,GAAG,CAAC,IAAI;CACrD,CAAC,IAAI,EAAE,CAAC;AACT,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,2BAA2B,CACzC,QAAgB,EAChB,SAAiB,EACjB,WAA0B,EAC1B,MAAiB;IAEjB,MAAM,WAAW,GAAG,WAAW;QAC7B,CAAC,CAAC,GAAG,WAAW,kBAAkB;QAClC,CAAC,CAAC,uBAAuB,CAAC;IAE5B,MAAM,OAAO,GAAG;;;;;WAKP,WAAW,oBAAoB,QAAQ,gBAAgB,MAAM,CAAC,GAAG,CAAC,IAAI;;;mBAG9D,SAAS;;;;;wDAK4B,SAAS;;GAE9D,CAAC;IAEF,OAAO,mBAAmB,CAAC,OAAO,EAAE;QAClC,OAAO,EAAE,MAAM,CAAC,GAAG,CAAC,IAAI;QACxB,MAAM,EAAE,MAAM,CAAC,GAAG,CAAC,GAAG;KACvB,CAAC,CAAC;AACL,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,2BAA2B,CACzC,QAAgB,EAChB,SAAiB,EACjB,WAA0B,EAC1B,MAAiB;IAEjB,MAAM,WAAW,GAAG,WAAW;QAC7B,CAAC,CAAC,GAAG,WAAW,kBAAkB;QAClC,CAAC,CAAC,uBAAuB,CAAC;IAE5B,OAAO;;;EAGP,WAAW,aAAa,QAAQ,QAAQ,MAAM,CAAC,GAAG,CAAC,IAAI;;;EAGvD,SAAS;;;;;CAKV,CAAC,IAAI,EAAE,CAAC;AACT,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../../src/services/email/types.ts"],"names":[],"mappings":"AAAA;;GAEG"}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import crypto from 'crypto';
|
|
2
|
+
const ALGORITHM = 'aes-256-gcm';
|
|
3
|
+
const IV_LENGTH = 12;
|
|
4
|
+
const AUTH_TAG_LENGTH = 16;
|
|
5
|
+
const KEY_LENGTH = 32;
|
|
6
|
+
function getEncryptionKey() {
|
|
7
|
+
const keySource = process.env.MCP_CREDENTIAL_KEY || process.env.SESSION_SECRET;
|
|
8
|
+
if (!keySource || keySource.length < 32) {
|
|
9
|
+
throw new Error('MCP_CREDENTIAL_KEY or SESSION_SECRET must be set and at least 32 characters');
|
|
10
|
+
}
|
|
11
|
+
// Derive a 32-byte key from the source using SHA-256
|
|
12
|
+
return crypto.createHash('sha256').update(keySource).digest();
|
|
13
|
+
}
|
|
14
|
+
export function encryptCredential(data) {
|
|
15
|
+
const key = getEncryptionKey();
|
|
16
|
+
const iv = crypto.randomBytes(IV_LENGTH);
|
|
17
|
+
const plaintext = JSON.stringify(data);
|
|
18
|
+
const cipher = crypto.createCipheriv(ALGORITHM, key, iv);
|
|
19
|
+
const encrypted = Buffer.concat([
|
|
20
|
+
cipher.update(plaintext, 'utf8'),
|
|
21
|
+
cipher.final(),
|
|
22
|
+
]);
|
|
23
|
+
const authTag = cipher.getAuthTag();
|
|
24
|
+
// Format: iv:authTag:ciphertext (all base64)
|
|
25
|
+
return [
|
|
26
|
+
iv.toString('base64'),
|
|
27
|
+
authTag.toString('base64'),
|
|
28
|
+
encrypted.toString('base64'),
|
|
29
|
+
].join(':');
|
|
30
|
+
}
|
|
31
|
+
export function decryptCredential(encryptedString) {
|
|
32
|
+
const key = getEncryptionKey();
|
|
33
|
+
const parts = encryptedString.split(':');
|
|
34
|
+
if (parts.length !== 3) {
|
|
35
|
+
throw new Error('Invalid encrypted credential format');
|
|
36
|
+
}
|
|
37
|
+
const [ivBase64, authTagBase64, ciphertextBase64] = parts;
|
|
38
|
+
const iv = Buffer.from(ivBase64, 'base64');
|
|
39
|
+
const authTag = Buffer.from(authTagBase64, 'base64');
|
|
40
|
+
const ciphertext = Buffer.from(ciphertextBase64, 'base64');
|
|
41
|
+
const decipher = crypto.createDecipheriv(ALGORITHM, key, iv);
|
|
42
|
+
decipher.setAuthTag(authTag);
|
|
43
|
+
const decrypted = Buffer.concat([
|
|
44
|
+
decipher.update(ciphertext),
|
|
45
|
+
decipher.final(),
|
|
46
|
+
]);
|
|
47
|
+
return JSON.parse(decrypted.toString('utf8'));
|
|
48
|
+
}
|
|
49
|
+
// Helper to check if OAuth token is expired (with 5 min buffer)
|
|
50
|
+
export function isTokenExpired(expiresAt) {
|
|
51
|
+
if (!expiresAt)
|
|
52
|
+
return false;
|
|
53
|
+
const bufferMs = 5 * 60 * 1000; // 5 minutes
|
|
54
|
+
return Date.now() >= (expiresAt * 1000) - bufferMs;
|
|
55
|
+
}
|
|
56
|
+
// Generate PKCE code verifier and challenge
|
|
57
|
+
export function generatePKCE() {
|
|
58
|
+
const codeVerifier = crypto.randomBytes(32).toString('base64url');
|
|
59
|
+
const codeChallenge = crypto
|
|
60
|
+
.createHash('sha256')
|
|
61
|
+
.update(codeVerifier)
|
|
62
|
+
.digest('base64url');
|
|
63
|
+
return { codeVerifier, codeChallenge };
|
|
64
|
+
}
|
|
65
|
+
// Generate state for OAuth CSRF protection
|
|
66
|
+
export function generateOAuthState() {
|
|
67
|
+
return crypto.randomBytes(32).toString('base64url');
|
|
68
|
+
}
|
|
69
|
+
//# sourceMappingURL=encryption.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"encryption.js","sourceRoot":"","sources":["../../src/services/encryption.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
const metadataCache = new Map();
|
|
2
|
+
// Persistent client registrations (survives cache expiry but not server restart)
|
|
3
|
+
const clientRegistrations = new Map();
|
|
4
|
+
const CACHE_TTL_MS = 60 * 60 * 1000; // 1 hour
|
|
5
|
+
async function fetchJson(url) {
|
|
6
|
+
try {
|
|
7
|
+
const response = await fetch(url, {
|
|
8
|
+
headers: { Accept: 'application/json' },
|
|
9
|
+
});
|
|
10
|
+
if (!response.ok) {
|
|
11
|
+
console.log(`[OAuth Discovery] ${url} returned ${response.status}`);
|
|
12
|
+
return null;
|
|
13
|
+
}
|
|
14
|
+
return await response.json();
|
|
15
|
+
}
|
|
16
|
+
catch (error) {
|
|
17
|
+
console.log(`[OAuth Discovery] Failed to fetch ${url}:`, error);
|
|
18
|
+
return null;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
// Discover protected resource metadata from MCP server
|
|
22
|
+
async function discoverProtectedResourceMetadata(serverUrl) {
|
|
23
|
+
const url = new URL(serverUrl);
|
|
24
|
+
// Try well-known paths per RFC 9728
|
|
25
|
+
// Path-aware: /.well-known/oauth-protected-resource/path
|
|
26
|
+
const pathAwareUrl = `${url.origin}/.well-known/oauth-protected-resource${url.pathname}`;
|
|
27
|
+
let metadata = await fetchJson(pathAwareUrl);
|
|
28
|
+
if (metadata)
|
|
29
|
+
return metadata;
|
|
30
|
+
// Root well-known
|
|
31
|
+
const rootUrl = `${url.origin}/.well-known/oauth-protected-resource`;
|
|
32
|
+
metadata = await fetchJson(rootUrl);
|
|
33
|
+
if (metadata)
|
|
34
|
+
return metadata;
|
|
35
|
+
// Try making an unauthenticated request to get WWW-Authenticate header
|
|
36
|
+
try {
|
|
37
|
+
const response = await fetch(serverUrl, { method: 'GET' });
|
|
38
|
+
if (response.status === 401) {
|
|
39
|
+
const wwwAuth = response.headers.get('WWW-Authenticate');
|
|
40
|
+
if (wwwAuth) {
|
|
41
|
+
// Parse: Bearer resource_metadata="url", scope="scopes"
|
|
42
|
+
const resourceMetadataMatch = wwwAuth.match(/resource_metadata="([^"]+)"/);
|
|
43
|
+
if (resourceMetadataMatch) {
|
|
44
|
+
metadata = await fetchJson(resourceMetadataMatch[1]);
|
|
45
|
+
if (metadata)
|
|
46
|
+
return metadata;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
catch {
|
|
52
|
+
// Ignore fetch errors
|
|
53
|
+
}
|
|
54
|
+
return null;
|
|
55
|
+
}
|
|
56
|
+
// Discover authorization server metadata
|
|
57
|
+
async function discoverAuthServerMetadata(issuer) {
|
|
58
|
+
const url = new URL(issuer);
|
|
59
|
+
// Try paths per RFC 8414 and OpenID Connect Discovery
|
|
60
|
+
const paths = url.pathname && url.pathname !== '/'
|
|
61
|
+
? [
|
|
62
|
+
// Path-aware discovery
|
|
63
|
+
`${url.origin}/.well-known/oauth-authorization-server${url.pathname}`,
|
|
64
|
+
`${url.origin}/.well-known/openid-configuration${url.pathname}`,
|
|
65
|
+
`${issuer}/.well-known/openid-configuration`,
|
|
66
|
+
]
|
|
67
|
+
: [
|
|
68
|
+
`${url.origin}/.well-known/oauth-authorization-server`,
|
|
69
|
+
`${url.origin}/.well-known/openid-configuration`,
|
|
70
|
+
];
|
|
71
|
+
for (const path of paths) {
|
|
72
|
+
const metadata = await fetchJson(path);
|
|
73
|
+
if (metadata) {
|
|
74
|
+
// Verify PKCE support
|
|
75
|
+
if (metadata.code_challenge_methods_supported &&
|
|
76
|
+
!metadata.code_challenge_methods_supported.includes('S256')) {
|
|
77
|
+
console.warn(`[OAuth Discovery] Auth server doesn't support S256 PKCE`);
|
|
78
|
+
}
|
|
79
|
+
return metadata;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
return null;
|
|
83
|
+
}
|
|
84
|
+
// Dynamic client registration per RFC 7591
|
|
85
|
+
async function registerClient(registrationEndpoint, config) {
|
|
86
|
+
const apiUrl = process.env.API_URL || 'http://localhost:3000';
|
|
87
|
+
const appUrl = process.env.APP_URL || 'http://localhost:5173';
|
|
88
|
+
const request = {
|
|
89
|
+
client_name: config.oauth?.clientName || config.name || 'Chat SaaS Client',
|
|
90
|
+
redirect_uris: [
|
|
91
|
+
`${apiUrl}/api/mcp/oauth/callback`,
|
|
92
|
+
],
|
|
93
|
+
grant_types: ['authorization_code', 'refresh_token'],
|
|
94
|
+
response_types: ['code'],
|
|
95
|
+
token_endpoint_auth_method: 'none', // Public client
|
|
96
|
+
client_uri: config.oauth?.clientUri || appUrl,
|
|
97
|
+
};
|
|
98
|
+
try {
|
|
99
|
+
const response = await fetch(registrationEndpoint, {
|
|
100
|
+
method: 'POST',
|
|
101
|
+
headers: {
|
|
102
|
+
'Content-Type': 'application/json',
|
|
103
|
+
Accept: 'application/json',
|
|
104
|
+
},
|
|
105
|
+
body: JSON.stringify(request),
|
|
106
|
+
});
|
|
107
|
+
if (!response.ok) {
|
|
108
|
+
const errorText = await response.text();
|
|
109
|
+
console.error(`[OAuth Discovery] Client registration failed:`, errorText);
|
|
110
|
+
return null;
|
|
111
|
+
}
|
|
112
|
+
return await response.json();
|
|
113
|
+
}
|
|
114
|
+
catch (error) {
|
|
115
|
+
console.error(`[OAuth Discovery] Client registration error:`, error);
|
|
116
|
+
return null;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
// Store client registration in memory
|
|
120
|
+
function storeClientRegistration(serverId, registration) {
|
|
121
|
+
clientRegistrations.set(serverId, registration);
|
|
122
|
+
console.log(`[OAuth Discovery] Stored client registration for ${serverId}`);
|
|
123
|
+
}
|
|
124
|
+
// Load stored client registration from memory
|
|
125
|
+
function loadClientRegistration(serverId) {
|
|
126
|
+
const registration = clientRegistrations.get(serverId);
|
|
127
|
+
if (!registration) {
|
|
128
|
+
return null;
|
|
129
|
+
}
|
|
130
|
+
// Check if expired
|
|
131
|
+
if (registration.client_secret_expires_at &&
|
|
132
|
+
registration.client_secret_expires_at * 1000 < Date.now()) {
|
|
133
|
+
clientRegistrations.delete(serverId);
|
|
134
|
+
return null;
|
|
135
|
+
}
|
|
136
|
+
return registration;
|
|
137
|
+
}
|
|
138
|
+
// Main discovery function
|
|
139
|
+
export async function discoverOAuthConfig(config) {
|
|
140
|
+
if (!config.url) {
|
|
141
|
+
console.error('[OAuth Discovery] Server URL is required');
|
|
142
|
+
return null;
|
|
143
|
+
}
|
|
144
|
+
// Check if manually configured
|
|
145
|
+
if (config.oauth?.authorizationEndpoint && config.oauth?.tokenEndpoint && config.oauth?.clientId) {
|
|
146
|
+
let clientId = config.oauth.clientId;
|
|
147
|
+
// Resolve env var reference
|
|
148
|
+
if (clientId.startsWith('${') && clientId.endsWith('}')) {
|
|
149
|
+
const envVar = clientId.slice(2, -1);
|
|
150
|
+
clientId = process.env[envVar] || '';
|
|
151
|
+
}
|
|
152
|
+
let clientSecret;
|
|
153
|
+
if (config.oauth.clientSecretEnvVar) {
|
|
154
|
+
clientSecret = process.env[config.oauth.clientSecretEnvVar];
|
|
155
|
+
}
|
|
156
|
+
return {
|
|
157
|
+
authorizationEndpoint: config.oauth.authorizationEndpoint,
|
|
158
|
+
tokenEndpoint: config.oauth.tokenEndpoint,
|
|
159
|
+
clientId,
|
|
160
|
+
clientSecret,
|
|
161
|
+
scopes: config.oauth.scopes,
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
// Check cache
|
|
165
|
+
const cached = metadataCache.get(config.url);
|
|
166
|
+
if (cached && cached.expiresAt > Date.now()) {
|
|
167
|
+
if (cached.authServer && cached.clientRegistration) {
|
|
168
|
+
return {
|
|
169
|
+
authorizationEndpoint: cached.authServer.authorization_endpoint,
|
|
170
|
+
tokenEndpoint: cached.authServer.token_endpoint,
|
|
171
|
+
clientId: cached.clientRegistration.client_id,
|
|
172
|
+
clientSecret: cached.clientRegistration.client_secret,
|
|
173
|
+
scopes: cached.protectedResource?.scopes_supported || config.oauth?.scopes,
|
|
174
|
+
};
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
console.log(`[OAuth Discovery] Discovering OAuth config for ${config.url}`);
|
|
178
|
+
// Step 1: Discover protected resource metadata
|
|
179
|
+
const protectedResource = await discoverProtectedResourceMetadata(config.url);
|
|
180
|
+
if (!protectedResource?.authorization_servers?.length) {
|
|
181
|
+
console.error('[OAuth Discovery] No authorization servers found in protected resource metadata');
|
|
182
|
+
return null;
|
|
183
|
+
}
|
|
184
|
+
const authServerUrl = protectedResource.authorization_servers[0];
|
|
185
|
+
console.log(`[OAuth Discovery] Found authorization server: ${authServerUrl}`);
|
|
186
|
+
// Step 2: Discover authorization server metadata
|
|
187
|
+
const authServer = await discoverAuthServerMetadata(authServerUrl);
|
|
188
|
+
if (!authServer) {
|
|
189
|
+
console.error('[OAuth Discovery] Failed to discover authorization server metadata');
|
|
190
|
+
return null;
|
|
191
|
+
}
|
|
192
|
+
console.log(`[OAuth Discovery] Found endpoints: auth=${authServer.authorization_endpoint}, token=${authServer.token_endpoint}`);
|
|
193
|
+
// Step 3: Get or create client registration
|
|
194
|
+
let clientRegistration = loadClientRegistration(config.id);
|
|
195
|
+
if (!clientRegistration && authServer.registration_endpoint) {
|
|
196
|
+
console.log(`[OAuth Discovery] Registering client at ${authServer.registration_endpoint}`);
|
|
197
|
+
clientRegistration = await registerClient(authServer.registration_endpoint, config);
|
|
198
|
+
if (clientRegistration) {
|
|
199
|
+
storeClientRegistration(config.id, clientRegistration);
|
|
200
|
+
console.log(`[OAuth Discovery] Client registered: ${clientRegistration.client_id}`);
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
if (!clientRegistration) {
|
|
204
|
+
console.error('[OAuth Discovery] No client registration available');
|
|
205
|
+
return null;
|
|
206
|
+
}
|
|
207
|
+
// Cache the results
|
|
208
|
+
metadataCache.set(config.url, {
|
|
209
|
+
protectedResource,
|
|
210
|
+
authServer,
|
|
211
|
+
clientRegistration,
|
|
212
|
+
expiresAt: Date.now() + CACHE_TTL_MS,
|
|
213
|
+
});
|
|
214
|
+
return {
|
|
215
|
+
authorizationEndpoint: authServer.authorization_endpoint,
|
|
216
|
+
tokenEndpoint: authServer.token_endpoint,
|
|
217
|
+
clientId: clientRegistration.client_id,
|
|
218
|
+
clientSecret: clientRegistration.client_secret,
|
|
219
|
+
scopes: protectedResource.scopes_supported || config.oauth?.scopes,
|
|
220
|
+
};
|
|
221
|
+
}
|
|
222
|
+
// Clear cached metadata (e.g., when client registration fails)
|
|
223
|
+
export function clearOAuthCache(serverUrl) {
|
|
224
|
+
metadataCache.delete(serverUrl);
|
|
225
|
+
}
|
|
226
|
+
//# sourceMappingURL=oauth-discovery.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"oauth-discovery.js","sourceRoot":"","sources":["../../src/services/oauth-discovery.ts"],"names":[],"mappings":"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"}
|