Initial commit
This commit is contained in:
63
src/lib/oauth/utils/banner.js
Normal file
63
src/lib/oauth/utils/banner.js
Normal file
@@ -0,0 +1,63 @@
|
||||
import figlet from "figlet";
|
||||
import gradient from "gradient-string";
|
||||
import chalkAnimation from "chalk-animation";
|
||||
|
||||
/**
|
||||
* Display banner
|
||||
*/
|
||||
export function showBanner() {
|
||||
const banner = figlet.textSync("LLM Proxy", {
|
||||
font: "ANSI Shadow",
|
||||
horizontalLayout: "default",
|
||||
verticalLayout: "default",
|
||||
});
|
||||
|
||||
console.log("\n" + gradient.pastel.multiline(banner));
|
||||
console.log(gradient.cristal(" 🚀 OAuth CLI for AI Providers\n"));
|
||||
}
|
||||
|
||||
/**
|
||||
* Display simple banner (no animation)
|
||||
*/
|
||||
export function showSimpleBanner() {
|
||||
const banner = figlet.textSync("EP CLI", {
|
||||
font: "Standard",
|
||||
horizontalLayout: "default",
|
||||
});
|
||||
console.log(gradient.pastel.multiline(banner));
|
||||
console.log(gradient.cristal(" OAuth CLI for AI Providers\n"));
|
||||
}
|
||||
|
||||
/**
|
||||
* Display success animation
|
||||
*/
|
||||
export async function showSuccess(message) {
|
||||
return new Promise((resolve) => {
|
||||
const animation = chalkAnimation.rainbow(`\n✨ ${message}\n`);
|
||||
setTimeout(() => {
|
||||
animation.stop();
|
||||
resolve();
|
||||
}, 1000);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Display loading animation
|
||||
*/
|
||||
export function showLoading(text) {
|
||||
const frames = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"];
|
||||
let i = 0;
|
||||
|
||||
const interval = setInterval(() => {
|
||||
process.stdout.write(`\r${frames[i]} ${text}`);
|
||||
i = (i + 1) % frames.length;
|
||||
}, 80);
|
||||
|
||||
return {
|
||||
stop: () => {
|
||||
clearInterval(interval);
|
||||
process.stdout.write("\r");
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
38
src/lib/oauth/utils/pkce.js
Normal file
38
src/lib/oauth/utils/pkce.js
Normal file
@@ -0,0 +1,38 @@
|
||||
import crypto from "crypto";
|
||||
|
||||
/**
|
||||
* Generate PKCE code verifier (43-128 characters)
|
||||
*/
|
||||
export function generateCodeVerifier() {
|
||||
return crypto.randomBytes(32).toString("base64url");
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate PKCE code challenge from verifier (S256 method)
|
||||
*/
|
||||
export function generateCodeChallenge(verifier) {
|
||||
return crypto.createHash("sha256").update(verifier).digest("base64url");
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate random state for CSRF protection
|
||||
*/
|
||||
export function generateState() {
|
||||
return crypto.randomBytes(32).toString("base64url");
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate complete PKCE pair
|
||||
*/
|
||||
export function generatePKCE() {
|
||||
const codeVerifier = generateCodeVerifier();
|
||||
const codeChallenge = generateCodeChallenge(codeVerifier);
|
||||
const state = generateState();
|
||||
|
||||
return {
|
||||
codeVerifier,
|
||||
codeChallenge,
|
||||
state,
|
||||
};
|
||||
}
|
||||
|
||||
116
src/lib/oauth/utils/server.js
Normal file
116
src/lib/oauth/utils/server.js
Normal file
@@ -0,0 +1,116 @@
|
||||
import http from "http";
|
||||
import { URL } from "url";
|
||||
|
||||
/**
|
||||
* Start a local HTTP server to receive OAuth callback
|
||||
* @param {Function} onCallback - Called with query params when callback received
|
||||
* @param {number} fixedPort - Optional fixed port number (default: random)
|
||||
* @returns {Promise<{server: http.Server, port: number, close: Function}>}
|
||||
*/
|
||||
export function startLocalServer(onCallback, fixedPort = null) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const server = http.createServer((req, res) => {
|
||||
const url = new URL(req.url, `http://localhost`);
|
||||
|
||||
if (url.pathname === "/callback" || url.pathname === "/auth/callback") {
|
||||
const params = Object.fromEntries(url.searchParams);
|
||||
|
||||
// Send success response to browser with auto-close attempt
|
||||
res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
|
||||
res.end(`<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>Authentication Successful</title>
|
||||
<style>
|
||||
body { font-family: system-ui; display: flex; justify-content: center; align-items: center; height: 100vh; margin: 0; background: #f5f5f5; }
|
||||
.container { text-align: center; padding: 2rem; background: white; border-radius: 8px; box-shadow: 0 2px 10px rgba(0,0,0,0.1); }
|
||||
.success { color: #22c55e; font-size: 3rem; }
|
||||
h1 { margin: 1rem 0; }
|
||||
p { color: #666; }
|
||||
#countdown { font-weight: bold; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<div class="success">✓</div>
|
||||
<h1>Authentication Successful</h1>
|
||||
<p id="message">Closing in <span id="countdown">3</span> seconds...</p>
|
||||
</div>
|
||||
<script>
|
||||
let count = 3;
|
||||
const countdown = document.getElementById("countdown");
|
||||
const message = document.getElementById("message");
|
||||
const timer = setInterval(() => {
|
||||
count--;
|
||||
countdown.textContent = count;
|
||||
if (count <= 0) {
|
||||
clearInterval(timer);
|
||||
window.close();
|
||||
setTimeout(() => {
|
||||
message.textContent = "Please close this tab manually.";
|
||||
}, 500);
|
||||
}
|
||||
}, 1000);
|
||||
</script>
|
||||
</body>
|
||||
</html>`);
|
||||
|
||||
// Call callback with params
|
||||
onCallback(params);
|
||||
} else {
|
||||
res.writeHead(404);
|
||||
res.end("Not found");
|
||||
}
|
||||
});
|
||||
|
||||
// Listen on fixed port or find available port
|
||||
const portToUse = fixedPort || 0;
|
||||
server.listen(portToUse, "127.0.0.1", () => {
|
||||
const { port } = server.address();
|
||||
resolve({
|
||||
server,
|
||||
port,
|
||||
close: () => server.close(),
|
||||
});
|
||||
});
|
||||
|
||||
server.on("error", (err) => {
|
||||
if (err.code === "EADDRINUSE" && fixedPort) {
|
||||
reject(new Error(`Port ${fixedPort} is already in use. Please close other applications using this port.`));
|
||||
} else {
|
||||
reject(err);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Wait for callback with timeout
|
||||
* @param {number} timeoutMs - Timeout in milliseconds
|
||||
* @returns {Promise<Object>} - Callback params
|
||||
*/
|
||||
export function waitForCallback(timeoutMs = 300000) {
|
||||
return new Promise((resolve, reject) => {
|
||||
let resolved = false;
|
||||
|
||||
const timeout = setTimeout(() => {
|
||||
if (!resolved) {
|
||||
resolved = true;
|
||||
reject(new Error("Authentication timeout"));
|
||||
}
|
||||
}, timeoutMs);
|
||||
|
||||
const onCallback = (params) => {
|
||||
if (!resolved) {
|
||||
resolved = true;
|
||||
clearTimeout(timeout);
|
||||
resolve(params);
|
||||
}
|
||||
};
|
||||
|
||||
// Return the callback function
|
||||
resolve.__onCallback = onCallback;
|
||||
});
|
||||
}
|
||||
|
||||
48
src/lib/oauth/utils/ui.js
Normal file
48
src/lib/oauth/utils/ui.js
Normal file
@@ -0,0 +1,48 @@
|
||||
import chalk from "chalk";
|
||||
import ora from "ora";
|
||||
|
||||
/**
|
||||
* UI Helper Functions
|
||||
*/
|
||||
|
||||
export function success(message) {
|
||||
console.log(chalk.green(`\n✓ ${message}\n`));
|
||||
}
|
||||
|
||||
export function error(message) {
|
||||
console.log(chalk.red(`\n✗ ${message}\n`));
|
||||
}
|
||||
|
||||
export function info(message) {
|
||||
console.log(chalk.blue(`\n${message}\n`));
|
||||
}
|
||||
|
||||
export function warn(message) {
|
||||
console.log(chalk.yellow(`\n⚠ ${message}\n`));
|
||||
}
|
||||
|
||||
export function gray(message) {
|
||||
console.log(chalk.gray(message));
|
||||
}
|
||||
|
||||
export function spinner(text) {
|
||||
return ora(text);
|
||||
}
|
||||
|
||||
export function printSection(title) {
|
||||
console.log(chalk.blue(`\n${title}\n`));
|
||||
}
|
||||
|
||||
export function printKeyValue(key, value, isSuccess = false) {
|
||||
const color = isSuccess ? chalk.green : chalk.gray;
|
||||
console.log(color(` ${key}: ${value}`));
|
||||
}
|
||||
|
||||
export function printList(items, isSuccess = false) {
|
||||
const symbol = isSuccess ? "✓" : "✗";
|
||||
const color = isSuccess ? chalk.green : chalk.gray;
|
||||
items.forEach((item) => {
|
||||
console.log(color(` ${symbol} ${item}`));
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user