diff --git a/src/auth/CommandLogin.ts b/src/auth/CommandLogin.ts index 51ec1fc8..02ca7000 100644 --- a/src/auth/CommandLogin.ts +++ b/src/auth/CommandLogin.ts @@ -1,15 +1,17 @@ +import type { AuthIdentityToken } from 'polykey/tokens/payloads/authIdentityToken.js'; import type PolykeyClient from 'polykey/PolykeyClient.js'; import CommandPolykey from '../CommandPolykey.js'; import * as binProcessors from '../utils/processors.js'; import * as binUtils from '../utils/index.js'; import * as binOptions from '../utils/options.js'; +import * as errors from '../errors.js'; class CommandLogin extends CommandPolykey { constructor(...args: ConstructorParameters) { super(...args); this.name('login'); this.description('Login to a platform with your Polykey identity'); - this.argument('', 'The URL to login using Polykey'); + this.argument('[url]', 'The URL to login using Polykey. Default is `enterprise.polykey.com`'); this.addOption(binOptions.nodeId); this.addOption(binOptions.clientHost); this.addOption(binOptions.clientPort); @@ -19,6 +21,7 @@ class CommandLogin extends CommandPolykey { 'polykey/PolykeyClient.js' ); const { default: open } = await import('open'); + const { default: Token } = await import('polykey/tokens/Token.js'); const clientOptions = await binProcessors.processClientOptions( options.nodePath, options.nodeId, @@ -55,12 +58,15 @@ class CommandLogin extends CommandPolykey { ); // Send the returned JWT to the returnURL provided by the initial token - const compactHeader = binUtils.jsonToCompactJWT(response); - const targetURL = new URL( - url.endsWith('/') ? url.slice(0, url.length) : url, - ); - const subPath: string = options.returnURLPath ?? '/oauth2/oidc'; - targetURL.pathname = subPath.startsWith('/') ? subPath : `/${subPath}`; + const compactHeader = + Token.fromEncoded(response).toCompact(); + if (compactHeader == null) { + throw new errors.ErrorPolykeyCLIInvalidJWT( + 'Too many signatures, expected 1', + ); + } + const targetURL = binUtils.normalizeURL(url ?? 'enterprise.polykey.com'); + targetURL.pathname = options.returnURLPath ?? '/oauth2/oidc'; targetURL.searchParams.append('token', compactHeader); // Print out the URL to stderr diff --git a/src/utils/utils.ts b/src/utils/utils.ts index 882a2f93..2d544c50 100644 --- a/src/utils/utils.ts +++ b/src/utils/utils.ts @@ -1,5 +1,4 @@ import type { FileSystem, POJO } from 'polykey/types.js'; -import type { SignedTokenEncoded } from 'polykey/tokens/types.js'; import type { TableRow, TableOptions, @@ -19,6 +18,13 @@ import * as errors from '../errors.js'; // @ts-ignore package.json is outside rootDir import packageJson from '../../package.json' assert { type: 'json' }; +const validEnvRegex = /[a-zA-Z_]+[a-zA-Z0-9_]*/; +// We want to actually match control codes here! +// eslint-disable-next-line no-control-regex +const encodeEscapedRegex = /[\x00-\x1F\x7F-\x9F"'`\\]/g; +const decodeEscapedRegex = /\\([nrtvf"'`\\]|u[0-9a-fA-F]{4})/g; +const urlProtocolRegex = /^[a-zA-Z][a-zA-Z\d+\-.]*:\/\//; + /** * Convert verbosity to LogLevel */ @@ -129,10 +135,6 @@ function decodeEscapedWrapped(str: string): string { return decodeEscaped(str.substring(1, str.length - 1)); } -// We want to actually match control codes here! -// eslint-disable-next-line no-control-regex -const encodeEscapedRegex = /[\x00-\x1F\x7F-\x9F"'`\\]/g; - /** * This function: * @@ -169,8 +171,6 @@ function encodeEscaped(str: string): string { }); } -const decodeEscapedRegex = /\\([nrtvf"'`\\]|u[0-9a-fA-F]{4})/g; - /** * This function: * @@ -600,8 +600,6 @@ function remoteErrorCause(e: any): [any, number] { return [errorCause, depth]; } -const validEnvRegex = /[a-zA-Z_]+[a-zA-Z0-9_]*/; - /** * Returns a formatted version string in the format of `[ APPVERSION, LIBRARYVERSION, NETWORKVERSION, STATEVERSION ]` */ @@ -637,13 +635,18 @@ async function importFS(fs?: FileSystem): Promise { return fsImported; } -function jsonToCompactJWT(token: SignedTokenEncoded): string { - if (token.signatures.length !== 1) { - throw new errors.ErrorPolykeyCLIInvalidJWT( - 'Too many signatures, expected 1', - ); +/** + * The return URL will not contain a trailing slash + */ +function normalizeURL(url: string): URL { + // If the protocol is missing from the URL, add https:// as the default + if (!urlProtocolRegex.test(url)) { + url = 'https://' + url; + } + if (url.endsWith('/')) { + url = url.slice(0, url.length); } - return `${token.signatures[0].protected}.${token.payload}.${token.signatures[0].signature}`; + return new URL(url); } export { @@ -669,7 +672,7 @@ export { generateVersionString, promise, importFS, - jsonToCompactJWT, + normalizeURL, }; export type { OutputObject };