repowise 0.1.5 → 0.1.6
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/bin/repowise.js +23 -6
- package/dist/src/commands/create.d.ts +1 -1
- package/dist/src/commands/create.d.ts.map +1 -1
- package/dist/src/commands/create.js +237 -2
- package/dist/src/commands/create.js.map +1 -1
- package/dist/src/commands/listen.d.ts +3 -2
- package/dist/src/commands/listen.d.ts.map +1 -1
- package/dist/src/commands/listen.js +45 -3
- package/dist/src/commands/listen.js.map +1 -1
- package/dist/src/commands/login.d.ts +4 -1
- package/dist/src/commands/login.d.ts.map +1 -1
- package/dist/src/commands/login.js +56 -2
- package/dist/src/commands/login.js.map +1 -1
- package/dist/src/commands/logout.d.ts +1 -1
- package/dist/src/commands/logout.d.ts.map +1 -1
- package/dist/src/commands/logout.js +10 -2
- package/dist/src/commands/logout.js.map +1 -1
- package/dist/src/commands/start.d.ts +2 -0
- package/dist/src/commands/start.d.ts.map +1 -0
- package/dist/src/commands/start.js +17 -0
- package/dist/src/commands/start.js.map +1 -0
- package/dist/src/commands/status.d.ts +1 -1
- package/dist/src/commands/status.d.ts.map +1 -1
- package/dist/src/commands/status.js +61 -2
- package/dist/src/commands/status.js.map +1 -1
- package/dist/src/commands/stop.d.ts +2 -0
- package/dist/src/commands/stop.d.ts.map +1 -0
- package/dist/src/commands/stop.js +17 -0
- package/dist/src/commands/stop.js.map +1 -0
- package/dist/src/lib/ai-tools.d.ts +23 -0
- package/dist/src/lib/ai-tools.d.ts.map +1 -0
- package/dist/src/lib/ai-tools.js +193 -0
- package/dist/src/lib/ai-tools.js.map +1 -0
- package/dist/src/lib/api.d.ts.map +1 -1
- package/dist/src/lib/api.js +25 -4
- package/dist/src/lib/api.js.map +1 -1
- package/dist/src/lib/auth.d.ts +18 -0
- package/dist/src/lib/auth.d.ts.map +1 -1
- package/dist/src/lib/auth.js +251 -7
- package/dist/src/lib/auth.js.map +1 -1
- package/dist/src/lib/config.d.ts +2 -0
- package/dist/src/lib/config.d.ts.map +1 -1
- package/dist/src/lib/config.js.map +1 -1
- package/dist/src/lib/env.d.ts +10 -0
- package/dist/src/lib/env.d.ts.map +1 -0
- package/dist/src/lib/env.js +26 -0
- package/dist/src/lib/env.js.map +1 -0
- package/dist/src/lib/interview-handler.d.ts +2 -0
- package/dist/src/lib/interview-handler.d.ts.map +1 -0
- package/dist/src/lib/interview-handler.js +96 -0
- package/dist/src/lib/interview-handler.js.map +1 -0
- package/dist/src/lib/progress-renderer.d.ts +69 -0
- package/dist/src/lib/progress-renderer.d.ts.map +1 -0
- package/dist/src/lib/progress-renderer.js +186 -0
- package/dist/src/lib/progress-renderer.js.map +1 -0
- package/dist/src/lib/prompts.d.ts +6 -1
- package/dist/src/lib/prompts.d.ts.map +1 -1
- package/dist/src/lib/prompts.js +21 -3
- package/dist/src/lib/prompts.js.map +1 -1
- package/dist/src/types/index.d.ts +1 -0
- package/dist/src/types/index.d.ts.map +1 -1
- package/dist/tsup.config.d.ts +3 -0
- package/dist/tsup.config.d.ts.map +1 -0
- package/dist/tsup.config.js +18 -0
- package/dist/tsup.config.js.map +1 -0
- package/package.json +1 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"stop.d.ts","sourceRoot":"","sources":["../../../src/commands/stop.ts"],"names":[],"mappings":"AAAA,wBAAsB,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC,CAgB1C"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export async function stop() {
|
|
2
|
+
try {
|
|
3
|
+
const { isRunning, stopProcess } = await import('@repowise/listener/process-manager');
|
|
4
|
+
if (!(await isRunning())) {
|
|
5
|
+
console.log('Listener is not running.');
|
|
6
|
+
return;
|
|
7
|
+
}
|
|
8
|
+
await stopProcess();
|
|
9
|
+
console.log('Listener stopped.');
|
|
10
|
+
}
|
|
11
|
+
catch (err) {
|
|
12
|
+
const message = err instanceof Error ? err.message : 'Unknown error';
|
|
13
|
+
console.error(`Failed to stop listener: ${message}`);
|
|
14
|
+
process.exitCode = 1;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
//# sourceMappingURL=stop.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"stop.js","sourceRoot":"","sources":["../../../src/commands/stop.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,KAAK,UAAU,IAAI;IACxB,IAAI,CAAC;QACH,MAAM,EAAE,SAAS,EAAE,WAAW,EAAE,GAAG,MAAM,MAAM,CAAC,oCAAoC,CAAC,CAAC;QAEtF,IAAI,CAAC,CAAC,MAAM,SAAS,EAAE,CAAC,EAAE,CAAC;YACzB,OAAO,CAAC,GAAG,CAAC,0BAA0B,CAAC,CAAC;YACxC,OAAO;QACT,CAAC;QAED,MAAM,WAAW,EAAE,CAAC;QACpB,OAAO,CAAC,GAAG,CAAC,mBAAmB,CAAC,CAAC;IACnC,CAAC;IAAC,OAAO,GAAY,EAAE,CAAC;QACtB,MAAM,OAAO,GAAG,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,eAAe,CAAC;QACrE,OAAO,CAAC,KAAK,CAAC,4BAA4B,OAAO,EAAE,CAAC,CAAC;QACrD,OAAO,CAAC,QAAQ,GAAG,CAAC,CAAC;IACvB,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
export type AiTool = 'cursor' | 'claude-code' | 'copilot' | 'windsurf' | 'cline' | 'codex' | 'roo-code';
|
|
2
|
+
export interface ContextFileInfo {
|
|
3
|
+
fileName: string;
|
|
4
|
+
relativePath: string;
|
|
5
|
+
}
|
|
6
|
+
interface ToolConfig {
|
|
7
|
+
label: string;
|
|
8
|
+
fileName: string;
|
|
9
|
+
filePath: string;
|
|
10
|
+
markerStart: string;
|
|
11
|
+
markerEnd: string;
|
|
12
|
+
format: 'markdown' | 'plain-text';
|
|
13
|
+
}
|
|
14
|
+
export declare const AI_TOOL_CONFIG: Record<AiTool, ToolConfig>;
|
|
15
|
+
export declare const SUPPORTED_TOOLS: AiTool[];
|
|
16
|
+
export declare function generateReference(tool: AiTool, repoName: string, contextFolder: string, contextFiles: ContextFileInfo[]): string;
|
|
17
|
+
export declare function updateToolConfig(repoRoot: string, tool: AiTool, repoName: string, contextFolder: string, contextFiles: ContextFileInfo[]): Promise<{
|
|
18
|
+
created: boolean;
|
|
19
|
+
}>;
|
|
20
|
+
export declare function removeToolConfig(repoRoot: string, tool: AiTool): Promise<void>;
|
|
21
|
+
export declare function scanLocalContextFiles(repoRoot: string, contextFolder: string): Promise<ContextFileInfo[]>;
|
|
22
|
+
export {};
|
|
23
|
+
//# sourceMappingURL=ai-tools.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ai-tools.d.ts","sourceRoot":"","sources":["../../../src/lib/ai-tools.ts"],"names":[],"mappings":"AAGA,MAAM,MAAM,MAAM,GACd,QAAQ,GACR,aAAa,GACb,SAAS,GACT,UAAU,GACV,OAAO,GACP,OAAO,GACP,UAAU,CAAC;AAEf,MAAM,WAAW,eAAe;IAC9B,QAAQ,EAAE,MAAM,CAAC;IACjB,YAAY,EAAE,MAAM,CAAC;CACtB;AAED,UAAU,UAAU;IAClB,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,EAAE,MAAM,CAAC;IACpB,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,UAAU,GAAG,YAAY,CAAC;CACnC;AAED,eAAO,MAAM,cAAc,EAAE,MAAM,CAAC,MAAM,EAAE,UAAU,CAyDrD,CAAC;AAEF,eAAO,MAAM,eAAe,EAAE,MAAM,EAA4C,CAAC;AAejF,wBAAgB,iBAAiB,CAC/B,IAAI,EAAE,MAAM,EACZ,QAAQ,EAAE,MAAM,EAChB,aAAa,EAAE,MAAM,EACrB,YAAY,EAAE,eAAe,EAAE,GAC9B,MAAM,CA4DR;AAED,wBAAsB,gBAAgB,CACpC,QAAQ,EAAE,MAAM,EAChB,IAAI,EAAE,MAAM,EACZ,QAAQ,EAAE,MAAM,EAChB,aAAa,EAAE,MAAM,EACrB,YAAY,EAAE,eAAe,EAAE,GAC9B,OAAO,CAAC;IAAE,OAAO,EAAE,OAAO,CAAA;CAAE,CAAC,CAuC/B;AAED,wBAAsB,gBAAgB,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAsBpF;AAED,wBAAsB,qBAAqB,CACzC,QAAQ,EAAE,MAAM,EAChB,aAAa,EAAE,MAAM,GACpB,OAAO,CAAC,eAAe,EAAE,CAAC,CAe5B"}
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
import { readFile, writeFile, mkdir, readdir } from 'node:fs/promises';
|
|
2
|
+
import { join, dirname } from 'node:path';
|
|
3
|
+
export const AI_TOOL_CONFIG = {
|
|
4
|
+
cursor: {
|
|
5
|
+
label: 'Cursor',
|
|
6
|
+
fileName: '.cursorrules',
|
|
7
|
+
filePath: '.cursorrules',
|
|
8
|
+
markerStart: '# --- repowise-start ---',
|
|
9
|
+
markerEnd: '# --- repowise-end ---',
|
|
10
|
+
format: 'plain-text',
|
|
11
|
+
},
|
|
12
|
+
'claude-code': {
|
|
13
|
+
label: 'Claude Code',
|
|
14
|
+
fileName: 'CLAUDE.md',
|
|
15
|
+
filePath: 'CLAUDE.md',
|
|
16
|
+
markerStart: '<!-- repowise-start -->',
|
|
17
|
+
markerEnd: '<!-- repowise-end -->',
|
|
18
|
+
format: 'markdown',
|
|
19
|
+
},
|
|
20
|
+
copilot: {
|
|
21
|
+
label: 'GitHub Copilot',
|
|
22
|
+
fileName: 'copilot-instructions.md',
|
|
23
|
+
filePath: '.github/copilot-instructions.md',
|
|
24
|
+
markerStart: '<!-- repowise-start -->',
|
|
25
|
+
markerEnd: '<!-- repowise-end -->',
|
|
26
|
+
format: 'markdown',
|
|
27
|
+
},
|
|
28
|
+
windsurf: {
|
|
29
|
+
label: 'Windsurf',
|
|
30
|
+
fileName: '.windsurfrules',
|
|
31
|
+
filePath: '.windsurfrules',
|
|
32
|
+
markerStart: '# --- repowise-start ---',
|
|
33
|
+
markerEnd: '# --- repowise-end ---',
|
|
34
|
+
format: 'plain-text',
|
|
35
|
+
},
|
|
36
|
+
cline: {
|
|
37
|
+
label: 'Cline',
|
|
38
|
+
fileName: '.clinerules',
|
|
39
|
+
filePath: '.clinerules',
|
|
40
|
+
markerStart: '# --- repowise-start ---',
|
|
41
|
+
markerEnd: '# --- repowise-end ---',
|
|
42
|
+
format: 'plain-text',
|
|
43
|
+
},
|
|
44
|
+
codex: {
|
|
45
|
+
label: 'Codex',
|
|
46
|
+
fileName: 'AGENTS.md',
|
|
47
|
+
filePath: 'AGENTS.md',
|
|
48
|
+
markerStart: '<!-- repowise-start -->',
|
|
49
|
+
markerEnd: '<!-- repowise-end -->',
|
|
50
|
+
format: 'markdown',
|
|
51
|
+
},
|
|
52
|
+
'roo-code': {
|
|
53
|
+
label: 'Roo Code',
|
|
54
|
+
fileName: 'rules.md',
|
|
55
|
+
filePath: '.roo/rules.md',
|
|
56
|
+
markerStart: '<!-- repowise-start -->',
|
|
57
|
+
markerEnd: '<!-- repowise-end -->',
|
|
58
|
+
format: 'markdown',
|
|
59
|
+
},
|
|
60
|
+
};
|
|
61
|
+
export const SUPPORTED_TOOLS = Object.keys(AI_TOOL_CONFIG);
|
|
62
|
+
function sanitizeRepoName(name) {
|
|
63
|
+
// Strip markdown/HTML special chars to prevent content injection
|
|
64
|
+
return name.replace(/[<>[\]`()|\\]/g, '');
|
|
65
|
+
}
|
|
66
|
+
function fileDescriptionFromName(fileName) {
|
|
67
|
+
return fileName
|
|
68
|
+
.replace(/\.md$/, '')
|
|
69
|
+
.split('-')
|
|
70
|
+
.map((w) => w.charAt(0).toUpperCase() + w.slice(1))
|
|
71
|
+
.join(' ');
|
|
72
|
+
}
|
|
73
|
+
export function generateReference(tool, repoName, contextFolder, contextFiles) {
|
|
74
|
+
const config = AI_TOOL_CONFIG[tool];
|
|
75
|
+
const safeName = sanitizeRepoName(repoName);
|
|
76
|
+
const fileLines = contextFiles.map((f) => {
|
|
77
|
+
const desc = fileDescriptionFromName(f.fileName);
|
|
78
|
+
const isOverview = f.fileName === 'project-overview.md';
|
|
79
|
+
return { path: f.relativePath, desc: isOverview ? `${desc} (full index of all files)` : desc };
|
|
80
|
+
});
|
|
81
|
+
const hasFiles = fileLines.length > 0;
|
|
82
|
+
if (config.format === 'markdown') {
|
|
83
|
+
const lines = [
|
|
84
|
+
config.markerStart,
|
|
85
|
+
'',
|
|
86
|
+
`## Project Context \u2014 ${safeName}`,
|
|
87
|
+
'',
|
|
88
|
+
`This repository has AI-optimized context files generated by RepoWise.`,
|
|
89
|
+
`Before making changes, read the relevant context files in \`${contextFolder}/\` to understand the project's architecture, coding patterns, conventions, and domain knowledge.`,
|
|
90
|
+
'',
|
|
91
|
+
`**Start here:** \`${contextFolder}/project-overview.md\` \u2014 the routing document that describes every context file and when to read it.`,
|
|
92
|
+
'',
|
|
93
|
+
];
|
|
94
|
+
if (hasFiles) {
|
|
95
|
+
lines.push(`**Core context files:**`, '', ...fileLines.map((f) => `- \`${f.path}\` \u2014 ${f.desc}`), '', `> Additional context files may exist beyond this list. Check \`project-overview.md\` for the complete index.`);
|
|
96
|
+
}
|
|
97
|
+
lines.push('', config.markerEnd);
|
|
98
|
+
return lines.join('\n');
|
|
99
|
+
}
|
|
100
|
+
// plain-text format
|
|
101
|
+
const lines = [
|
|
102
|
+
config.markerStart,
|
|
103
|
+
`# Project Context \u2014 ${safeName}`,
|
|
104
|
+
'#',
|
|
105
|
+
`# This repository has AI-optimized context files generated by RepoWise.`,
|
|
106
|
+
`# Before making changes, read the relevant context files in ${contextFolder}/`,
|
|
107
|
+
`# to understand the project's architecture, coding patterns, conventions, and domain knowledge.`,
|
|
108
|
+
'#',
|
|
109
|
+
`# Start here: ${contextFolder}/project-overview.md`,
|
|
110
|
+
`# The routing document that describes every context file and when to read it.`,
|
|
111
|
+
];
|
|
112
|
+
if (hasFiles) {
|
|
113
|
+
lines.push('#', `# Core context files:`, ...fileLines.map((f) => `# ${f.path} \u2014 ${f.desc}`), '#', '# Additional context files may exist beyond this list.', '# Check project-overview.md for the complete index.');
|
|
114
|
+
}
|
|
115
|
+
lines.push(config.markerEnd);
|
|
116
|
+
return lines.join('\n');
|
|
117
|
+
}
|
|
118
|
+
export async function updateToolConfig(repoRoot, tool, repoName, contextFolder, contextFiles) {
|
|
119
|
+
const config = AI_TOOL_CONFIG[tool];
|
|
120
|
+
const fullPath = join(repoRoot, config.filePath);
|
|
121
|
+
// Ensure parent directory exists for tools in subdirectories
|
|
122
|
+
const dir = dirname(fullPath);
|
|
123
|
+
if (dir !== repoRoot) {
|
|
124
|
+
await mkdir(dir, { recursive: true });
|
|
125
|
+
}
|
|
126
|
+
const referenceBlock = generateReference(tool, repoName, contextFolder, contextFiles);
|
|
127
|
+
let existing = '';
|
|
128
|
+
let created = true;
|
|
129
|
+
try {
|
|
130
|
+
existing = await readFile(fullPath, 'utf-8');
|
|
131
|
+
created = false;
|
|
132
|
+
}
|
|
133
|
+
catch (err) {
|
|
134
|
+
if (err.code !== 'ENOENT')
|
|
135
|
+
throw err;
|
|
136
|
+
}
|
|
137
|
+
const startIdx = existing.indexOf(config.markerStart);
|
|
138
|
+
const endIdx = existing.indexOf(config.markerEnd);
|
|
139
|
+
let content;
|
|
140
|
+
if (startIdx !== -1 && endIdx !== -1 && endIdx > startIdx) {
|
|
141
|
+
// Replace between markers (inclusive)
|
|
142
|
+
const before = existing.slice(0, startIdx);
|
|
143
|
+
const after = existing.slice(endIdx + config.markerEnd.length);
|
|
144
|
+
content = before + referenceBlock + after;
|
|
145
|
+
}
|
|
146
|
+
else {
|
|
147
|
+
// Append to end
|
|
148
|
+
const separator = existing.length > 0 && !existing.endsWith('\n') ? '\n\n' : existing.length > 0 ? '\n' : '';
|
|
149
|
+
content = existing + separator + referenceBlock + '\n';
|
|
150
|
+
}
|
|
151
|
+
await writeFile(fullPath, content, 'utf-8');
|
|
152
|
+
return { created };
|
|
153
|
+
}
|
|
154
|
+
export async function removeToolConfig(repoRoot, tool) {
|
|
155
|
+
const config = AI_TOOL_CONFIG[tool];
|
|
156
|
+
const fullPath = join(repoRoot, config.filePath);
|
|
157
|
+
let existing;
|
|
158
|
+
try {
|
|
159
|
+
existing = await readFile(fullPath, 'utf-8');
|
|
160
|
+
}
|
|
161
|
+
catch (err) {
|
|
162
|
+
if (err.code === 'ENOENT')
|
|
163
|
+
return;
|
|
164
|
+
throw err;
|
|
165
|
+
}
|
|
166
|
+
const startIdx = existing.indexOf(config.markerStart);
|
|
167
|
+
const endIdx = existing.indexOf(config.markerEnd);
|
|
168
|
+
if (startIdx === -1 || endIdx === -1)
|
|
169
|
+
return;
|
|
170
|
+
const before = existing.slice(0, startIdx);
|
|
171
|
+
const after = existing.slice(endIdx + config.markerEnd.length);
|
|
172
|
+
const content = (before + after).replace(/\n{3,}/g, '\n\n').trim() + '\n';
|
|
173
|
+
await writeFile(fullPath, content, 'utf-8');
|
|
174
|
+
}
|
|
175
|
+
export async function scanLocalContextFiles(repoRoot, contextFolder) {
|
|
176
|
+
const folderPath = join(repoRoot, contextFolder);
|
|
177
|
+
try {
|
|
178
|
+
const entries = await readdir(folderPath, { withFileTypes: true });
|
|
179
|
+
return entries
|
|
180
|
+
.filter((e) => e.isFile() && e.name.endsWith('.md'))
|
|
181
|
+
.map((e) => ({
|
|
182
|
+
fileName: e.name,
|
|
183
|
+
relativePath: `${contextFolder}/${e.name}`,
|
|
184
|
+
}))
|
|
185
|
+
.sort((a, b) => a.fileName.localeCompare(b.fileName));
|
|
186
|
+
}
|
|
187
|
+
catch (err) {
|
|
188
|
+
if (err.code === 'ENOENT')
|
|
189
|
+
return [];
|
|
190
|
+
throw err;
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
//# sourceMappingURL=ai-tools.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ai-tools.js","sourceRoot":"","sources":["../../../src/lib/ai-tools.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"api.d.ts","sourceRoot":"","sources":["../../../src/lib/api.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"api.d.ts","sourceRoot":"","sources":["../../../src/lib/api.ts"],"names":[],"mappings":"AAWA,wBAAsB,UAAU,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,CAAC,CAAC,CAkCnF"}
|
package/dist/src/lib/api.js
CHANGED
|
@@ -1,15 +1,36 @@
|
|
|
1
|
-
|
|
1
|
+
import { clearCredentials, getValidCredentials } from './auth.js';
|
|
2
|
+
import { getEnvConfig } from './env.js';
|
|
3
|
+
function getApiUrl() {
|
|
4
|
+
return process.env['REPOWISE_API_URL'] ?? getEnvConfig().apiUrl;
|
|
5
|
+
}
|
|
2
6
|
export async function apiRequest(path, options) {
|
|
3
|
-
const
|
|
7
|
+
const credentials = await getValidCredentials();
|
|
8
|
+
if (!credentials) {
|
|
9
|
+
throw new Error('Not logged in. Run `repowise login` first.');
|
|
10
|
+
}
|
|
11
|
+
const response = await fetch(`${getApiUrl()}${path}`, {
|
|
4
12
|
...options,
|
|
5
13
|
headers: {
|
|
6
14
|
'Content-Type': 'application/json',
|
|
15
|
+
Authorization: `Bearer ${credentials.accessToken}`,
|
|
7
16
|
...options?.headers,
|
|
8
17
|
},
|
|
9
18
|
});
|
|
19
|
+
if (response.status === 401) {
|
|
20
|
+
await clearCredentials();
|
|
21
|
+
throw new Error('Session expired. Run `repowise login` again.');
|
|
22
|
+
}
|
|
10
23
|
if (!response.ok) {
|
|
11
|
-
|
|
12
|
-
|
|
24
|
+
let message = `Request failed with status ${response.status}`;
|
|
25
|
+
try {
|
|
26
|
+
const body = (await response.json());
|
|
27
|
+
if (body.error?.message)
|
|
28
|
+
message = body.error.message;
|
|
29
|
+
}
|
|
30
|
+
catch {
|
|
31
|
+
// Response body is not JSON (e.g. gateway 502/503) — use default message
|
|
32
|
+
}
|
|
33
|
+
throw new Error(message);
|
|
13
34
|
}
|
|
14
35
|
const json = (await response.json());
|
|
15
36
|
return json.data;
|
package/dist/src/lib/api.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"api.js","sourceRoot":"","sources":["../../../src/lib/api.ts"],"names":[],"mappings":"AAAA,MAAM,OAAO,
|
|
1
|
+
{"version":3,"file":"api.js","sourceRoot":"","sources":["../../../src/lib/api.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,mBAAmB,EAAE,MAAM,WAAW,CAAC;AAClE,OAAO,EAAE,YAAY,EAAE,MAAM,UAAU,CAAC;AAExC,SAAS,SAAS;IAChB,OAAO,OAAO,CAAC,GAAG,CAAC,kBAAkB,CAAC,IAAI,YAAY,EAAE,CAAC,MAAM,CAAC;AAClE,CAAC;AAMD,MAAM,CAAC,KAAK,UAAU,UAAU,CAAI,IAAY,EAAE,OAAqB;IACrE,MAAM,WAAW,GAAG,MAAM,mBAAmB,EAAE,CAAC;IAEhD,IAAI,CAAC,WAAW,EAAE,CAAC;QACjB,MAAM,IAAI,KAAK,CAAC,4CAA4C,CAAC,CAAC;IAChE,CAAC;IAED,MAAM,QAAQ,GAAG,MAAM,KAAK,CAAC,GAAG,SAAS,EAAE,GAAG,IAAI,EAAE,EAAE;QACpD,GAAG,OAAO;QACV,OAAO,EAAE;YACP,cAAc,EAAE,kBAAkB;YAClC,aAAa,EAAE,UAAU,WAAW,CAAC,WAAW,EAAE;YAClD,GAAG,OAAO,EAAE,OAAO;SACpB;KACF,CAAC,CAAC;IAEH,IAAI,QAAQ,CAAC,MAAM,KAAK,GAAG,EAAE,CAAC;QAC5B,MAAM,gBAAgB,EAAE,CAAC;QACzB,MAAM,IAAI,KAAK,CAAC,8CAA8C,CAAC,CAAC;IAClE,CAAC;IAED,IAAI,CAAC,QAAQ,CAAC,EAAE,EAAE,CAAC;QACjB,IAAI,OAAO,GAAG,8BAA8B,QAAQ,CAAC,MAAM,EAAE,CAAC;QAC9D,IAAI,CAAC;YACH,MAAM,IAAI,GAAG,CAAC,MAAM,QAAQ,CAAC,IAAI,EAAE,CAAa,CAAC;YACjD,IAAI,IAAI,CAAC,KAAK,EAAE,OAAO;gBAAE,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC;QACxD,CAAC;QAAC,MAAM,CAAC;YACP,yEAAyE;QAC3E,CAAC;QACD,MAAM,IAAI,KAAK,CAAC,OAAO,CAAC,CAAC;IAC3B,CAAC;IAED,MAAM,IAAI,GAAG,CAAC,MAAM,QAAQ,CAAC,IAAI,EAAE,CAAgB,CAAC;IACpD,OAAO,IAAI,CAAC,IAAI,CAAC;AACnB,CAAC"}
|
package/dist/src/lib/auth.d.ts
CHANGED
|
@@ -1,10 +1,28 @@
|
|
|
1
|
+
export declare const CLI_CALLBACK_PORT = 19876;
|
|
1
2
|
export interface StoredCredentials {
|
|
2
3
|
accessToken: string;
|
|
3
4
|
refreshToken: string;
|
|
4
5
|
idToken: string;
|
|
5
6
|
expiresAt: number;
|
|
6
7
|
}
|
|
8
|
+
export declare function generateCodeVerifier(): string;
|
|
9
|
+
export declare function generateCodeChallenge(verifier: string): string;
|
|
10
|
+
export declare function generateState(): string;
|
|
11
|
+
export declare function getAuthorizeUrl(codeChallenge: string, state: string): string;
|
|
12
|
+
export interface CallbackResult {
|
|
13
|
+
code: string;
|
|
14
|
+
state: string;
|
|
15
|
+
}
|
|
16
|
+
export declare function startCallbackServer(): Promise<CallbackResult>;
|
|
17
|
+
export declare function exchangeCodeForTokens(code: string, codeVerifier: string): Promise<StoredCredentials>;
|
|
18
|
+
export declare function refreshTokens(refreshToken: string): Promise<StoredCredentials>;
|
|
7
19
|
export declare function getStoredCredentials(): Promise<StoredCredentials | null>;
|
|
8
20
|
export declare function storeCredentials(credentials: StoredCredentials): Promise<void>;
|
|
9
21
|
export declare function clearCredentials(): Promise<void>;
|
|
22
|
+
export declare function getValidCredentials(): Promise<StoredCredentials | null>;
|
|
23
|
+
export declare function performLogin(): Promise<StoredCredentials>;
|
|
24
|
+
export declare function decodeIdToken(idToken: string): {
|
|
25
|
+
email: string;
|
|
26
|
+
tenantId?: string;
|
|
27
|
+
};
|
|
10
28
|
//# sourceMappingURL=auth.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"auth.d.ts","sourceRoot":"","sources":["../../../src/lib/auth.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"auth.d.ts","sourceRoot":"","sources":["../../../src/lib/auth.ts"],"names":[],"mappings":"AAUA,eAAO,MAAM,iBAAiB,QAAQ,CAAC;AAmBvC,MAAM,WAAW,iBAAiB;IAChC,WAAW,EAAE,MAAM,CAAC;IACpB,YAAY,EAAE,MAAM,CAAC;IACrB,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;CACnB;AAID,wBAAgB,oBAAoB,IAAI,MAAM,CAE7C;AAED,wBAAgB,qBAAqB,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAE9D;AAED,wBAAgB,aAAa,IAAI,MAAM,CAEtC;AAID,wBAAgB,eAAe,CAAC,aAAa,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,CAiB5E;AAQD,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;CACf;AAED,wBAAgB,mBAAmB,IAAI,OAAO,CAAC,cAAc,CAAC,CA8E7D;AAID,wBAAsB,qBAAqB,CACzC,IAAI,EAAE,MAAM,EACZ,YAAY,EAAE,MAAM,GACnB,OAAO,CAAC,iBAAiB,CAAC,CA+B5B;AAID,wBAAsB,aAAa,CAAC,YAAY,EAAE,MAAM,GAAG,OAAO,CAAC,iBAAiB,CAAC,CA2BpF;AAID,wBAAsB,oBAAoB,IAAI,OAAO,CAAC,iBAAiB,GAAG,IAAI,CAAC,CAY9E;AAED,wBAAsB,gBAAgB,CAAC,WAAW,EAAE,iBAAiB,GAAG,OAAO,CAAC,IAAI,CAAC,CAIpF;AAED,wBAAsB,gBAAgB,IAAI,OAAO,CAAC,IAAI,CAAC,CAMtD;AAED,wBAAsB,mBAAmB,IAAI,OAAO,CAAC,iBAAiB,GAAG,IAAI,CAAC,CAiB7E;AAID,wBAAsB,YAAY,IAAI,OAAO,CAAC,iBAAiB,CAAC,CA2B/D;AA0CD,wBAAgB,aAAa,CAAC,OAAO,EAAE,MAAM,GAAG;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;CAAE,CAYnF"}
|
package/dist/src/lib/auth.js
CHANGED
|
@@ -1,27 +1,271 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { createHash, randomBytes } from 'node:crypto';
|
|
2
|
+
import { readFile, writeFile, mkdir, chmod, unlink } from 'node:fs/promises';
|
|
3
|
+
import http from 'node:http';
|
|
2
4
|
import { homedir } from 'node:os';
|
|
3
5
|
import { join } from 'node:path';
|
|
6
|
+
import { getEnvConfig } from './env.js';
|
|
4
7
|
const CONFIG_DIR = join(homedir(), '.repowise');
|
|
5
8
|
const CREDENTIALS_PATH = join(CONFIG_DIR, 'credentials.json');
|
|
9
|
+
export const CLI_CALLBACK_PORT = 19876;
|
|
10
|
+
const CALLBACK_TIMEOUT_MS = 120_000; // 2 minutes
|
|
11
|
+
// Cognito config — read lazily so tests can set env vars before function calls
|
|
12
|
+
function getCognitoConfig() {
|
|
13
|
+
const env = getEnvConfig();
|
|
14
|
+
return {
|
|
15
|
+
domain: process.env['REPOWISE_COGNITO_DOMAIN'] ?? env.cognitoDomain,
|
|
16
|
+
clientId: process.env['REPOWISE_COGNITO_CLIENT_ID'] ?? env.cognitoClientId,
|
|
17
|
+
region: process.env['REPOWISE_COGNITO_REGION'] ?? env.cognitoRegion,
|
|
18
|
+
customDomain: env.customDomain,
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
function getCognitoBaseUrl() {
|
|
22
|
+
const { domain, region, customDomain } = getCognitoConfig();
|
|
23
|
+
return customDomain ? `https://${domain}` : `https://${domain}.auth.${region}.amazoncognito.com`;
|
|
24
|
+
}
|
|
25
|
+
// --- PKCE (Proof Key for Code Exchange) ---
|
|
26
|
+
export function generateCodeVerifier() {
|
|
27
|
+
return randomBytes(32).toString('base64url');
|
|
28
|
+
}
|
|
29
|
+
export function generateCodeChallenge(verifier) {
|
|
30
|
+
return createHash('sha256').update(verifier).digest('base64url');
|
|
31
|
+
}
|
|
32
|
+
export function generateState() {
|
|
33
|
+
return randomBytes(32).toString('hex');
|
|
34
|
+
}
|
|
35
|
+
// --- Cognito URL Construction ---
|
|
36
|
+
export function getAuthorizeUrl(codeChallenge, state) {
|
|
37
|
+
const { clientId } = getCognitoConfig();
|
|
38
|
+
if (!clientId) {
|
|
39
|
+
throw new Error('Missing REPOWISE_COGNITO_CLIENT_ID environment variable. Configure it before running login.');
|
|
40
|
+
}
|
|
41
|
+
const params = new URLSearchParams({
|
|
42
|
+
response_type: 'code',
|
|
43
|
+
client_id: clientId,
|
|
44
|
+
redirect_uri: `http://localhost:${CLI_CALLBACK_PORT}/callback`,
|
|
45
|
+
code_challenge: codeChallenge,
|
|
46
|
+
code_challenge_method: 'S256',
|
|
47
|
+
scope: 'openid email profile',
|
|
48
|
+
state,
|
|
49
|
+
});
|
|
50
|
+
return `${getCognitoBaseUrl()}/oauth2/authorize?${params.toString()}`;
|
|
51
|
+
}
|
|
52
|
+
function getTokenUrl() {
|
|
53
|
+
return `${getCognitoBaseUrl()}/oauth2/token`;
|
|
54
|
+
}
|
|
55
|
+
export function startCallbackServer() {
|
|
56
|
+
return new Promise((resolve, reject) => {
|
|
57
|
+
const server = http.createServer((req, res) => {
|
|
58
|
+
// req.url is always defined for incoming HTTP requests (only undefined for client requests)
|
|
59
|
+
const url = new URL(req.url, `http://localhost:${CLI_CALLBACK_PORT}`);
|
|
60
|
+
if (url.pathname !== '/callback') {
|
|
61
|
+
res.writeHead(404);
|
|
62
|
+
res.end();
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
const code = url.searchParams.get('code');
|
|
66
|
+
const state = url.searchParams.get('state');
|
|
67
|
+
const error = url.searchParams.get('error');
|
|
68
|
+
if (error) {
|
|
69
|
+
res.writeHead(200, { 'Content-Type': 'text/html' });
|
|
70
|
+
res.end(callbackPage('Authentication Failed', 'Something went wrong. Please close this tab and try again.', true));
|
|
71
|
+
server.close();
|
|
72
|
+
reject(new Error(`Authentication error: ${error}`));
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
if (!code || !state) {
|
|
76
|
+
res.writeHead(400, { 'Content-Type': 'text/html' });
|
|
77
|
+
res.end(callbackPage('Missing Parameters', 'The callback was missing required data. Please close this tab and try again.', true));
|
|
78
|
+
server.close();
|
|
79
|
+
reject(new Error('Missing code or state in callback'));
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
res.writeHead(200, { 'Content-Type': 'text/html' });
|
|
83
|
+
res.end(callbackPage('Authentication Successful', 'You can close this tab and return to the terminal.', false));
|
|
84
|
+
server.close();
|
|
85
|
+
resolve({ code, state });
|
|
86
|
+
});
|
|
87
|
+
server.listen(CLI_CALLBACK_PORT, '127.0.0.1');
|
|
88
|
+
server.on('error', (err) => {
|
|
89
|
+
if (err.code === 'EADDRINUSE') {
|
|
90
|
+
reject(new Error(`Port ${CLI_CALLBACK_PORT} is already in use. Close the conflicting process and try again.`));
|
|
91
|
+
}
|
|
92
|
+
else {
|
|
93
|
+
reject(err);
|
|
94
|
+
}
|
|
95
|
+
});
|
|
96
|
+
const timeout = setTimeout(() => {
|
|
97
|
+
server.close();
|
|
98
|
+
reject(new Error('Authentication timed out. Please try again.'));
|
|
99
|
+
}, CALLBACK_TIMEOUT_MS);
|
|
100
|
+
server.on('close', () => clearTimeout(timeout));
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
// --- Token Exchange ---
|
|
104
|
+
export async function exchangeCodeForTokens(code, codeVerifier) {
|
|
105
|
+
const response = await fetch(getTokenUrl(), {
|
|
106
|
+
method: 'POST',
|
|
107
|
+
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
|
|
108
|
+
body: new URLSearchParams({
|
|
109
|
+
grant_type: 'authorization_code',
|
|
110
|
+
client_id: getCognitoConfig().clientId,
|
|
111
|
+
redirect_uri: `http://localhost:${CLI_CALLBACK_PORT}/callback`,
|
|
112
|
+
code,
|
|
113
|
+
code_verifier: codeVerifier,
|
|
114
|
+
}),
|
|
115
|
+
});
|
|
116
|
+
if (!response.ok) {
|
|
117
|
+
const text = await response.text();
|
|
118
|
+
throw new Error(`Token exchange failed: ${response.status} ${text}`);
|
|
119
|
+
}
|
|
120
|
+
const data = (await response.json());
|
|
121
|
+
return {
|
|
122
|
+
accessToken: data.access_token,
|
|
123
|
+
refreshToken: data.refresh_token,
|
|
124
|
+
idToken: data.id_token,
|
|
125
|
+
expiresAt: Date.now() + data.expires_in * 1000,
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
// --- Token Refresh ---
|
|
129
|
+
export async function refreshTokens(refreshToken) {
|
|
130
|
+
const response = await fetch(getTokenUrl(), {
|
|
131
|
+
method: 'POST',
|
|
132
|
+
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
|
|
133
|
+
body: new URLSearchParams({
|
|
134
|
+
grant_type: 'refresh_token',
|
|
135
|
+
client_id: getCognitoConfig().clientId,
|
|
136
|
+
refresh_token: refreshToken,
|
|
137
|
+
}),
|
|
138
|
+
});
|
|
139
|
+
if (!response.ok) {
|
|
140
|
+
throw new Error(`Token refresh failed: ${response.status}`);
|
|
141
|
+
}
|
|
142
|
+
const data = (await response.json());
|
|
143
|
+
return {
|
|
144
|
+
accessToken: data.access_token,
|
|
145
|
+
refreshToken, // Cognito does not return a new refresh token
|
|
146
|
+
idToken: data.id_token,
|
|
147
|
+
expiresAt: Date.now() + data.expires_in * 1000,
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
// --- Credential Storage ---
|
|
6
151
|
export async function getStoredCredentials() {
|
|
7
152
|
try {
|
|
8
153
|
const data = await readFile(CREDENTIALS_PATH, 'utf-8');
|
|
9
154
|
return JSON.parse(data);
|
|
10
155
|
}
|
|
11
|
-
catch {
|
|
12
|
-
|
|
156
|
+
catch (err) {
|
|
157
|
+
// File doesn't exist or corrupt JSON — treat as no credentials
|
|
158
|
+
if (err.code === 'ENOENT' || err instanceof SyntaxError) {
|
|
159
|
+
return null;
|
|
160
|
+
}
|
|
161
|
+
// Permission errors (EPERM, EACCES) should surface — the file exists but can't be read
|
|
162
|
+
throw err;
|
|
13
163
|
}
|
|
14
164
|
}
|
|
15
165
|
export async function storeCredentials(credentials) {
|
|
16
|
-
await mkdir(CONFIG_DIR, { recursive: true });
|
|
17
|
-
await writeFile(CREDENTIALS_PATH, JSON.stringify(credentials, null, 2)
|
|
166
|
+
await mkdir(CONFIG_DIR, { recursive: true, mode: 0o700 });
|
|
167
|
+
await writeFile(CREDENTIALS_PATH, JSON.stringify(credentials, null, 2));
|
|
168
|
+
await chmod(CREDENTIALS_PATH, 0o600);
|
|
18
169
|
}
|
|
19
170
|
export async function clearCredentials() {
|
|
20
171
|
try {
|
|
21
|
-
await
|
|
172
|
+
await unlink(CREDENTIALS_PATH);
|
|
173
|
+
}
|
|
174
|
+
catch (err) {
|
|
175
|
+
if (err.code !== 'ENOENT')
|
|
176
|
+
throw err;
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
export async function getValidCredentials() {
|
|
180
|
+
const creds = await getStoredCredentials();
|
|
181
|
+
if (!creds)
|
|
182
|
+
return null;
|
|
183
|
+
// Refresh if within 5 minutes of expiry
|
|
184
|
+
if (Date.now() > creds.expiresAt - 5 * 60 * 1000) {
|
|
185
|
+
try {
|
|
186
|
+
const refreshed = await refreshTokens(creds.refreshToken);
|
|
187
|
+
await storeCredentials(refreshed);
|
|
188
|
+
return refreshed;
|
|
189
|
+
}
|
|
190
|
+
catch {
|
|
191
|
+
await clearCredentials();
|
|
192
|
+
return null;
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
return creds;
|
|
196
|
+
}
|
|
197
|
+
// --- Auto-Login (Story 2.1) ---
|
|
198
|
+
export async function performLogin() {
|
|
199
|
+
const codeVerifier = generateCodeVerifier();
|
|
200
|
+
const codeChallenge = generateCodeChallenge(codeVerifier);
|
|
201
|
+
const state = generateState();
|
|
202
|
+
const authorizeUrl = getAuthorizeUrl(codeChallenge, state);
|
|
203
|
+
const callbackPromise = startCallbackServer();
|
|
204
|
+
try {
|
|
205
|
+
const open = (await import('open')).default;
|
|
206
|
+
await open(authorizeUrl);
|
|
207
|
+
}
|
|
208
|
+
catch {
|
|
209
|
+
// Print URL as fallback
|
|
210
|
+
console.log(`\nOpen this URL in your browser to authenticate:\n`);
|
|
211
|
+
console.log(authorizeUrl);
|
|
212
|
+
}
|
|
213
|
+
const { code, state: returnedState } = await callbackPromise;
|
|
214
|
+
if (returnedState !== state) {
|
|
215
|
+
throw new Error('State mismatch — possible CSRF attack. Please try again.');
|
|
216
|
+
}
|
|
217
|
+
const credentials = await exchangeCodeForTokens(code, codeVerifier);
|
|
218
|
+
await storeCredentials(credentials);
|
|
219
|
+
return credentials;
|
|
220
|
+
}
|
|
221
|
+
// --- ID Token Decoding ---
|
|
222
|
+
function callbackPage(title, message, isError) {
|
|
223
|
+
const icon = isError
|
|
224
|
+
? '<svg width="48" height="48" fill="none" viewBox="0 0 24 24"><circle cx="12" cy="12" r="10" stroke="#ef4444" stroke-width="2"/><path stroke="#ef4444" stroke-width="2" stroke-linecap="round" d="M15 9l-6 6M9 9l6 6"/></svg>'
|
|
225
|
+
: '<svg width="48" height="48" fill="none" viewBox="0 0 24 24"><circle cx="12" cy="12" r="10" stroke="#10b981" stroke-width="2"/><path stroke="#10b981" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" d="M8 12l3 3 5-5"/></svg>';
|
|
226
|
+
return `<!DOCTYPE html>
|
|
227
|
+
<html lang="en">
|
|
228
|
+
<head>
|
|
229
|
+
<meta charset="UTF-8">
|
|
230
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
231
|
+
<title>${title} — RepoWise</title>
|
|
232
|
+
<link rel="icon" href="https://staging.repowise.ai/favicon.svg" type="image/svg+xml">
|
|
233
|
+
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
|
|
234
|
+
<style>
|
|
235
|
+
* { margin: 0; padding: 0; box-sizing: border-box; }
|
|
236
|
+
body { font-family: 'Inter', system-ui, sans-serif; background: #0a0b14; color: #e4e4e7; min-height: 100vh; display: flex; align-items: center; justify-content: center; }
|
|
237
|
+
.card { text-align: center; max-width: 440px; padding: 48px 40px; }
|
|
238
|
+
.logo { margin-bottom: 32px; }
|
|
239
|
+
.logo svg { height: 48px; width: auto; }
|
|
240
|
+
.icon { margin-bottom: 20px; }
|
|
241
|
+
h1 { font-size: 24px; font-weight: 700; margin-bottom: 8px; color: ${isError ? '#ef4444' : '#e4e4e7'}; }
|
|
242
|
+
p { font-size: 15px; color: #a1a1aa; line-height: 1.5; }
|
|
243
|
+
</style>
|
|
244
|
+
</head>
|
|
245
|
+
<body>
|
|
246
|
+
<div class="card">
|
|
247
|
+
<div class="logo">
|
|
248
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 200 50" height="48">
|
|
249
|
+
<text x="0" y="38" font-family="Inter, system-ui, sans-serif" font-weight="700" font-size="36" fill="#e4e4e7">Repo<tspan fill="#6c5ce7">Wise</tspan></text>
|
|
250
|
+
</svg>
|
|
251
|
+
</div>
|
|
252
|
+
<div class="icon">${icon}</div>
|
|
253
|
+
<h1>${title}</h1>
|
|
254
|
+
<p>${message}</p>
|
|
255
|
+
</div>
|
|
256
|
+
</body>
|
|
257
|
+
</html>`;
|
|
258
|
+
}
|
|
259
|
+
export function decodeIdToken(idToken) {
|
|
260
|
+
try {
|
|
261
|
+
const parts = idToken.split('.');
|
|
262
|
+
if (parts.length < 2)
|
|
263
|
+
return { email: 'unknown' };
|
|
264
|
+
const payload = JSON.parse(Buffer.from(parts[1], 'base64url').toString());
|
|
265
|
+
return { email: payload.email ?? 'unknown', tenantId: payload['custom:tenant_id'] };
|
|
22
266
|
}
|
|
23
267
|
catch {
|
|
24
|
-
|
|
268
|
+
return { email: 'unknown' };
|
|
25
269
|
}
|
|
26
270
|
}
|
|
27
271
|
//# sourceMappingURL=auth.js.map
|