-
Notifications
You must be signed in to change notification settings - Fork 3
Use @vercel/oidc utilities to reduce auth duplication #34
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
62e37de
457df3a
f594c36
ac18fb5
af4a294
6c74006
269e276
49891fa
1becbe0
76d85ab
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -3,12 +3,11 @@ import { login } from "../commands/login"; | |
| import createDebugger from "debug"; | ||
| import chalk from "chalk"; | ||
| import { | ||
| getAuth, | ||
| updateAuthConfig, | ||
| isOAuthError, | ||
| OAuth, | ||
| } from "@vercel/sandbox/dist/auth/index.js"; | ||
| import { getVercelOidcToken } from "@vercel/oidc"; | ||
| getVercelOidcToken, | ||
| getVercelCliToken, | ||
| AccessTokenMissingError, | ||
| RefreshAccessTokenFailedError, | ||
| } from "@vercel/oidc"; | ||
|
|
||
| const debug = createDebugger("sandbox:args:auth"); | ||
|
|
||
|
|
@@ -38,79 +37,46 @@ export const token = cmd.option({ | |
| } | ||
| } | ||
|
|
||
| let auth = getAuth(); | ||
|
|
||
| // If there's no auth token, run the login command | ||
| if (!auth) { | ||
| await login.handler({}); | ||
| auth = getAuth(); | ||
| } | ||
|
|
||
| if (auth) { | ||
| const refreshed = await refreshToken(auth); | ||
| if (typeof refreshed === "object") { | ||
| auth = refreshed; | ||
| } else if ( | ||
| refreshed === "missing refresh token" || | ||
| refreshed === "invalid refresh token" | ||
| // Try to get CLI token, which handles auth.json reading and refresh | ||
| try { | ||
| return await getVercelCliToken(); | ||
|
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Do we need an access token? Or could we also get an OIDC token for Sandbox?
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Handling the access token is good idea. But then it should actually be called "access token". And we should also allow it be specified via an env var, e.g. |
||
| } catch (error) { | ||
| // Handle specific auth errors by triggering login | ||
| if ( | ||
| error instanceof AccessTokenMissingError || | ||
| error instanceof RefreshAccessTokenFailedError | ||
| ) { | ||
| debug( | ||
| `CLI token unavailable (${error.name}), prompting for login...`, | ||
| ); | ||
| console.warn( | ||
| chalk.yellow( | ||
| `${chalk.bold("notice:")} Your session has expired. Please log in again.`, | ||
| ), | ||
| ); | ||
| await login.handler({}); | ||
| auth = getAuth(); | ||
|
|
||
| // Try again after login | ||
| try { | ||
| return await getVercelCliToken(); | ||
| } catch (retryError) { | ||
| throw new Error( | ||
| [ | ||
| `Failed to retrieve authentication token.`, | ||
| `${chalk.bold("hint:")} Try logging in again with \`sandbox login\`.`, | ||
| "╰▶ Docs: https://vercel.com/docs/vercel-sandbox/cli-reference#authentication", | ||
| ].join("\n"), | ||
| ); | ||
| } | ||
| } | ||
| } | ||
|
|
||
| if (!auth || !auth.token) { | ||
| throw new Error( | ||
| [ | ||
| `Failed to retrieve authentication token.`, | ||
| `${chalk.bold("hint:")} Try logging in again with \`sandbox login\`.`, | ||
| "╰▶ Docs: https://vercel.com/docs/vercel-sandbox/cli-reference#authentication", | ||
| ].join("\n"), | ||
| ); | ||
| // Re-throw unexpected errors | ||
| throw error; | ||
| } | ||
|
|
||
| return auth.token; | ||
| }, | ||
| }, | ||
| }); | ||
|
|
||
| async function refreshToken(file: NonNullable<ReturnType<typeof getAuth>>) { | ||
| if (!file.expiresAt) return; | ||
| if (file.expiresAt.getTime() > Date.now()) { | ||
| return "not expired" as const; | ||
| } | ||
|
|
||
| if (!file.refreshToken) { | ||
| debug(`Token expired, yet refresh token unavailable.`); | ||
| return "missing refresh token" as const; | ||
| } | ||
|
|
||
| debug(`Refreshing token (expired at ${file.expiresAt.toISOString()})`); | ||
| const oauth = await OAuth(); | ||
| const newToken = await oauth.refreshToken(file.refreshToken).catch((err) => { | ||
| if (isOAuthError(err)) { | ||
| return null; | ||
| } | ||
| throw err; | ||
| }); | ||
| if (!newToken) { | ||
| return "invalid refresh token" as const; | ||
| } | ||
| updateAuthConfig({ | ||
| expiresAt: new Date(Date.now() + newToken.expires_in * 1000), | ||
| token: newToken.access_token, | ||
| refreshToken: newToken.refresh_token || file.refreshToken, | ||
| }); | ||
| const updated = getAuth(); | ||
| debug(`Token stored. expires at ${updated?.expiresAt?.toISOString()})`); | ||
| return updated; | ||
| } | ||
|
|
||
| function getMessage(error: unknown): string { | ||
| if (!(error instanceof Error)) { | ||
| return String(error); | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm working on it locally, this is a local install path for local testing. Once the
@vercel/oidcPR ships I'll update it.