From de917a5d74a6d8a9bb6a422ad8540c2bb98c835c Mon Sep 17 00:00:00 2001 From: x032205 Date: Sat, 14 Jun 2025 01:31:44 -0400 Subject: [PATCH] Fix CLI refresh token functionality + reduce token lifetime to 1d & 14d for refresh --- backend/src/lib/config/env.ts | 4 ++-- .../server/plugins/auth/inject-identity.ts | 2 +- backend/src/server/routes/v3/login-router.ts | 11 ++++++---- cli/packages/api/model.go | 2 +- cli/packages/cmd/login.go | 3 ++- cli/packages/models/cli.go | 2 +- cli/packages/util/credentials.go | 20 ++++++++----------- frontend/src/hooks/api/auth/queries.tsx | 1 + .../components/PasswordStep/PasswordStep.tsx | 16 +++++++++------ .../auth/SelectOrgPage/SelectOrgSection.tsx | 7 ++++--- 10 files changed, 37 insertions(+), 31 deletions(-) diff --git a/backend/src/lib/config/env.ts b/backend/src/lib/config/env.ts index 6b9d33c2a..0eb397a64 100644 --- a/backend/src/lib/config/env.ts +++ b/backend/src/lib/config/env.ts @@ -101,9 +101,9 @@ const envSchema = z LOOPS_API_KEY: zpStr(z.string().optional()), // jwt options AUTH_SECRET: zpStr(z.string()).default(process.env.JWT_AUTH_SECRET), // for those still using old JWT_AUTH_SECRET - JWT_AUTH_LIFETIME: zpStr(z.string().default("10d")), + JWT_AUTH_LIFETIME: zpStr(z.string().default("1d")), JWT_SIGNUP_LIFETIME: zpStr(z.string().default("15m")), - JWT_REFRESH_LIFETIME: zpStr(z.string().default("90d")), + JWT_REFRESH_LIFETIME: zpStr(z.string().default("14d")), JWT_INVITE_LIFETIME: zpStr(z.string().default("1d")), JWT_MFA_LIFETIME: zpStr(z.string().default("5m")), JWT_PROVIDER_AUTH_LIFETIME: zpStr(z.string().default("15m")), diff --git a/backend/src/server/plugins/auth/inject-identity.ts b/backend/src/server/plugins/auth/inject-identity.ts index f065bfbed..211bcea0c 100644 --- a/backend/src/server/plugins/auth/inject-identity.ts +++ b/backend/src/server/plugins/auth/inject-identity.ts @@ -107,7 +107,7 @@ export const injectIdentity = fp(async (server: FastifyZodProvider) => { server.addHook("onRequest", async (req) => { const appCfg = getConfig(); - if (req.url.includes(".well-known/est") || req.url.includes("/api/v3/auth/")) { + if (req.url.includes(".well-known/est") || req.url.includes("/api/v3/auth/") || req.url === "/api/v1/auth/token") { return; } diff --git a/backend/src/server/routes/v3/login-router.ts b/backend/src/server/routes/v3/login-router.ts index 91df68e16..c7740b665 100644 --- a/backend/src/server/routes/v3/login-router.ts +++ b/backend/src/server/routes/v3/login-router.ts @@ -50,7 +50,8 @@ export const registerLoginRouter = async (server: FastifyZodProvider) => { 200: z.object({ token: z.string(), isMfaEnabled: z.boolean(), - mfaMethod: z.string().optional() + mfaMethod: z.string().optional(), + refreshToken: z.string().optional() }) } }, @@ -101,7 +102,7 @@ export const registerLoginRouter = async (server: FastifyZodProvider) => { maxAge: 0 }); - return { token: tokens.access, isMfaEnabled: false }; + return { token: tokens.access, isMfaEnabled: false, refreshToken: tokens.refresh }; } }); @@ -129,7 +130,8 @@ export const registerLoginRouter = async (server: FastifyZodProvider) => { encryptedPrivateKey: z.string(), iv: z.string(), tag: z.string(), - token: z.string() + token: z.string(), + refreshToken: z.string().optional() }) } }, @@ -172,7 +174,8 @@ export const registerLoginRouter = async (server: FastifyZodProvider) => { tag: data.user.tag, protectedKey: data.user.protectedKey || null, protectedKeyIV: data.user.protectedKeyIV || null, - protectedKeyTag: data.user.protectedKeyTag || null + protectedKeyTag: data.user.protectedKeyTag || null, + refreshToken: data.token.refresh } as const; } }); diff --git a/cli/packages/api/model.go b/cli/packages/api/model.go index a7a797a0b..2ed790e41 100644 --- a/cli/packages/api/model.go +++ b/cli/packages/api/model.go @@ -267,7 +267,7 @@ type GetLoginTwoV2Response struct { ProtectedKey string `json:"protectedKey"` ProtectedKeyIV string `json:"protectedKeyIV"` ProtectedKeyTag string `json:"protectedKeyTag"` - RefreshToken string `json:"RefreshToken"` + RefreshToken string `json:"refreshToken"` } type VerifyMfaTokenRequest struct { diff --git a/cli/packages/cmd/login.go b/cli/packages/cmd/login.go index fd3ce1569..314e5e74e 100644 --- a/cli/packages/cmd/login.go +++ b/cli/packages/cmd/login.go @@ -111,7 +111,7 @@ var loginCmd = &cobra.Command{ infisicalClient := infisicalSdk.NewInfisicalClient(context.Background(), infisicalSdk.Config{ SiteUrl: config.INFISICAL_URL, UserAgent: api.USER_AGENT, - AutoTokenRefresh: false, + AutoTokenRefresh: true, CustomHeaders: customHeaders, }) @@ -438,6 +438,7 @@ func cliDefaultLogin(userCredentialsToBeStored *models.UserCredentials) { userCredentialsToBeStored.Email = email userCredentialsToBeStored.PrivateKey = string(decryptedPrivateKey) userCredentialsToBeStored.JTWToken = newJwtToken + userCredentialsToBeStored.RefreshToken = loginTwoResponse.RefreshToken } func init() { diff --git a/cli/packages/models/cli.go b/cli/packages/models/cli.go index 8b9fef6f6..411135117 100644 --- a/cli/packages/models/cli.go +++ b/cli/packages/models/cli.go @@ -6,7 +6,7 @@ type UserCredentials struct { Email string `json:"email"` PrivateKey string `json:"privateKey"` JTWToken string `json:"JTWToken"` - RefreshToken string `json:"RefreshToken"` + RefreshToken string `json:"refreshToken"` } // The file struct for Infisical config file diff --git a/cli/packages/util/credentials.go b/cli/packages/util/credentials.go index cd73e47ca..9250c6c01 100644 --- a/cli/packages/util/credentials.go +++ b/cli/packages/util/credentials.go @@ -94,19 +94,15 @@ func GetCurrentLoggedInUserDetails(setConfigVariables bool) (LoggedInUserDetails SetHeader("Accept", "application/json") isAuthenticated := api.CallIsAuthenticated(httpClient) - // TODO: add refresh token - // if !isAuthenticated { - // accessTokenResponse, err := api.CallGetNewAccessTokenWithRefreshToken(httpClient, userCreds.RefreshToken) - // if err == nil && accessTokenResponse.Token != "" { - // isAuthenticated = true - // userCreds.JTWToken = accessTokenResponse.Token - // } - // } + if !isAuthenticated { + accessTokenResponse, refreshErr := api.CallGetNewAccessTokenWithRefreshToken(httpClient, userCreds.RefreshToken) + if refreshErr == nil && accessTokenResponse.Token != "" { + isAuthenticated = true + userCreds.JTWToken = accessTokenResponse.Token + } + } - // err = StoreUserCredsInKeyRing(&userCreds) - // if err != nil { - // log.Debug().Msg("unable to store your user credentials with new access token") - // } + _ = StoreUserCredsInKeyRing(&userCreds) if !isAuthenticated { return LoggedInUserDetails{ diff --git a/frontend/src/hooks/api/auth/queries.tsx b/frontend/src/hooks/api/auth/queries.tsx index 796fd3152..9acd2308f 100644 --- a/frontend/src/hooks/api/auth/queries.tsx +++ b/frontend/src/hooks/api/auth/queries.tsx @@ -73,6 +73,7 @@ export const selectOrganization = async (data: { }) => { const { data: res } = await apiRequest.post<{ token: string; + refreshToken: string; isMfaEnabled: boolean; mfaMethod?: MfaMethod; }>("/api/v3/auth/select-organization", data); diff --git a/frontend/src/pages/auth/LoginPage/components/PasswordStep/PasswordStep.tsx b/frontend/src/pages/auth/LoginPage/components/PasswordStep/PasswordStep.tsx index 91432f9be..ee8e334bf 100644 --- a/frontend/src/pages/auth/LoginPage/components/PasswordStep/PasswordStep.tsx +++ b/frontend/src/pages/auth/LoginPage/components/PasswordStep/PasswordStep.tsx @@ -76,7 +76,9 @@ export const PasswordStep = ({ // case: organization ID is present from the provider auth token -- select the org and use the new jwt token in the CLI, then navigate to the org if (organizationId) { const finishWithOrgWorkflow = async () => { - const { token, isMfaEnabled, mfaMethod } = await selectOrganization({ organizationId }); + const { token, isMfaEnabled, mfaMethod, refreshToken } = await selectOrganization({ + organizationId + }); if (isMfaEnabled) { SecurityClient.setMfaToken(token); @@ -94,10 +96,11 @@ export const PasswordStep = ({ const payload = { privateKey, email, - JTWToken: token + JTWToken: token, + refreshToken }; await instance.post(cliUrl, payload).catch(() => { - // if error happens to communicate we set the token with an expiry in sessino storage + // if error happens to communicate we set the token with an expiry in session storage // the cli-redirect page has logic to show this to user and ask them to paste it in terminal sessionStorage.setItem( SessionStorageKeys.CLI_TERMINAL_TOKEN, @@ -187,7 +190,7 @@ export const PasswordStep = ({ // case: organization ID is present from the provider auth token -- select the org and use the new jwt token in the CLI, then navigate to the org if (organizationId) { const finishWithOrgWorkflow = async () => { - const { token, isMfaEnabled, mfaMethod } = await selectOrganization({ + const { token, isMfaEnabled, mfaMethod, refreshToken } = await selectOrganization({ organizationId }); @@ -206,10 +209,11 @@ export const PasswordStep = ({ const instance = axios.create(); const payload = { ...isCliLoginSuccessful.loginResponse, - JTWToken: token + JTWToken: token, + refreshToken }; await instance.post(cliUrl, payload).catch(() => { - // if error happens to communicate we set the token with an expiry in sessino storage + // if error happens to communicate we set the token with an expiry in session storage // the cli-redirect page has logic to show this to user and ask them to paste it in terminal sessionStorage.setItem( SessionStorageKeys.CLI_TERMINAL_TOKEN, diff --git a/frontend/src/pages/auth/SelectOrgPage/SelectOrgSection.tsx b/frontend/src/pages/auth/SelectOrgPage/SelectOrgSection.tsx index ac194cd05..014b4b1dd 100644 --- a/frontend/src/pages/auth/SelectOrgPage/SelectOrgSection.tsx +++ b/frontend/src/pages/auth/SelectOrgPage/SelectOrgSection.tsx @@ -112,7 +112,7 @@ export const SelectOrganizationSection = () => { return; } - const { token, isMfaEnabled, mfaMethod } = await selectOrg + const { token, isMfaEnabled, mfaMethod, refreshToken } = await selectOrg .mutateAsync({ organizationId: organization.id, userAgent: callbackPort ? UserAgentType.CLI : undefined @@ -151,13 +151,14 @@ export const SelectOrganizationSection = () => { const payload = { JTWToken: token, email: user?.email, - privateKey + privateKey, + refreshToken } as IsCliLoginSuccessful["loginResponse"]; // send request to server endpoint const instance = axios.create(); await instance.post(`http://127.0.0.1:${callbackPort}/`, payload).catch(() => { - // if error happens to communicate we set the token with an expiry in sessino storage + // if error happens to communicate we set the token with an expiry in session storage // the cli-redirect page has logic to show this to user and ask them to paste it in terminal sessionStorage.setItem( SessionStorageKeys.CLI_TERMINAL_TOKEN,