verdaccio-openid
Version:
A UI for OIDC authentication for Verdaccio, a fork of verdaccio-github-oauth-ui
226 lines (215 loc) • 7 kB
JavaScript
import process from 'node:process';
import express from 'express';
import open from 'open';
import colors from 'picocolors';
import { execSync } from 'node:child_process';
import { URL } from 'node:url';
import minimist from 'minimist';
var name = "verdaccio-openid";
var version = "0.14.1";
const plugin = {
name,
version
};
const pluginKey = name.replace("verdaccio-", "");
const authorizePath = "/-/oauth/authorize";
const cliPort = 8239;
const cliProviderId = "cli";
const messageGroupRequired = "You are not a member of the required access group.";
const messageLoggedAndCloseWindow = "You have logged in successfully and may close this window.";
function getAuthorizePath(id) {
return `${authorizePath}${`/${id}` }`;
}
const prefix = colors.blue(`[${pluginKey}]`);
const logger = {
info: console.log.bind(console, prefix),
success: (...args) => console.log(prefix, colors.green(args.join(" "))),
warn: (...args) => console.warn(prefix, colors.yellow(args.join(" "))),
error: (...args) => console.error(prefix, colors.red(args.join(" ")))
};
logger.info(`Version: ${plugin.name}@${plugin.version}`);
const PUBLIC_REGISTRIES = ["registry.npmjs.org", "registry.npmmirror.com", "registry.npm.taobao.org"];
let npmConfig;
function parseCliArgs() {
return minimist(process.argv.slice(2));
}
function runCommand(command, logCommand = true) {
if (logCommand) {
logger.info("Running command:", colors.blackBright(typeof logCommand === "string" ? logCommand : command));
}
return execSync(command).toString();
}
function getNpmConfig() {
if (!npmConfig) {
const npmConfigJson = runCommand("npm config list --json", false);
npmConfig = JSON.parse(npmConfigJson);
}
return npmConfig;
}
function removeTrailingSlash(input) {
return input.trim().replace(/\/?$/, "");
}
function getRegistryUrl() {
const cliArgs = parseCliArgs();
const registry = cliArgs.registry ?? getNpmConfig().registry;
if (!registry) {
return PUBLIC_REGISTRIES[0];
}
return removeTrailingSlash(registry);
}
function getNpmConfigFile() {
return getNpmConfig().userconfig;
}
function getNpmSaveCommand(registry, token) {
const url = new URL(registry);
let baseUrl = `${url.host}${url.pathname}`;
if (!baseUrl.endsWith("/")) {
baseUrl = `${baseUrl}/`;
}
return `npm config set //${baseUrl}:_authToken "${token}"`;
}
function saveNpmToken(token) {
const registry = getRegistryUrl();
const command = getNpmSaveCommand(registry, token);
runCommand(command, command.replace(/".*"/, '"<token>"'));
}
var img = "data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 57 49' width='57' height='49'%3e %3cg fill='none' stroke-width='2.4'%3e %3cpath fill='%23405236' stroke='%23405236' d='M46.06 18.8H33.54L28.4 29.08 14.86 2H2.34l22.4 44.8h7.32l14-28Z'/%3e %3cpath fill='%234A5E3F' stroke='%23405236' d='m32.06 46.8 2.58-5.11L14.86 2H2.34l22.4 44.8h7.32Z'/%3e %3cpath fill='%23CD4000' stroke='%23CD4000' d='m50.06 10.8 4.4-8.8H41.94l-4.4 8.8h12.52Z'/%3e %3cg stroke='%23CD4000' stroke-linecap='square'%3e %3cpath d='M37.6 2h15.22M33.6 6h15.22M29.6 10.8h15.22'/%3e %3c/g%3e %3c/g%3e%3c/svg%3e";
const styles = `
html,
body {
padding: 0;
margin: 0;
height: 100%;
background-color: #e0e0e0;
color: #24292F;
font-family: Helvetica, sans-serif;
position: relative;
text-align: center;
}
.wrap {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
a {
color: #3f51b5;
}
.img {
filter: drop-shadow(0 0.5rem 0.5rem #24292F80);
width: 114px;
height: 98px;
}
`;
function buildStatusPage(body, withBack = false) {
return `<!DOCTYPE html>
<html lang="en">
<head>
<title>${plugin.name} - ${plugin.version}</title>
<style>${styles}</style>
</head>
<body>
<div class="wrap">
<img src="${img}" class="img" alt="logo" />
${body}
${withBack ? `<p><a href="${typeof withBack === "object" ? withBack.backUrl : "javascript:history.back()"}">Go back</a></p>` : ""}
</div>
</body>
</html>`;
}
function buildErrorPage(error, withBack = false) {
return buildStatusPage(`<h1>Sorry :(</h1>
<p>${error?.message ?? error}</p>`, withBack);
}
function buildAccessDeniedPage(withBack = false) {
return buildStatusPage(`<h1>Access Denied</h1>
<p>${messageGroupRequired}</p>`, withBack);
}
const messageSuccess = "All done! We've updated your npm configuration.";
function respondWithCliMessage(status, message) {
switch (status) {
case "success":
{
logger.success(messageSuccess);
logger.info("Path:", colors.blackBright(getNpmConfigFile()));
break;
}
case "denied":
{
logger.error(messageGroupRequired);
break;
}
default:
{
logger.warn(message);
break;
}
}
}
function respondWithWebPage(status, message, res) {
res.setHeader("Content-Type", "text/html");
switch (status) {
case "success":
{
res.status(200).send(buildStatusPage(`<h1>Success!</h1>
<p>${messageSuccess}</p>
<p><code>${getNpmConfigFile()}</code></p>
<p>${messageLoggedAndCloseWindow}</p>`));
break;
}
case "denied":
{
res.status(401).send(buildAccessDeniedPage());
break;
}
default:
{
res.status(500).send(buildErrorPage(message));
break;
}
}
}
function getUsageInfo() {
return ["========================= Usage =========================", "It seems you are using the default npm registry.", "Please update it to your Verdaccio URL by either running:", "", "npm config set registry <URL>", "", "Or by using the registry argument:", "", `npx ${plugin.name} --registry <URL>`, "========================================================"];
}
function printUsage() {
for (const line of getUsageInfo()) {
logger.info(line);
}
}
function validateRegistry() {
const registry = getRegistryUrl();
if (PUBLIC_REGISTRIES.some(item => registry.includes(item))) {
printUsage();
process.exit(1);
}
return registry;
}
const registry = validateRegistry();
const authorizeUrl = registry + getAuthorizePath(cliProviderId);
const server = express().get("/", (req, res) => {
let status = String(req.query.status);
let message = String(req.query.message);
const token = String(req.query.token);
if (status === "success") {
try {
saveNpmToken(token);
} catch (error) {
status = "error";
message = error.message;
}
}
respondWithWebPage(status, message, res);
respondWithCliMessage(status, message);
server.close();
process.exit(status === "success" ? 0 : 1);
}).listen(cliPort, () => {
logger.info(`Listening on port ${cliPort}...`);
logger.info(`Opening ${authorizeUrl} in your browser...`);
open(authorizeUrl).catch(() => {
logger.error("Failed to open browser window.");
logger.warn("Please visit the following URL manually:");
logger.info(authorizeUrl);
});
});