Skip to content
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

Ensure auth device token is saved to a file so it persists upgrades and reinstalls #3640

Merged
merged 5 commits into from
Aug 26, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/lib/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ export const TEST_SETTINGS_FILE_KEY = 'playwright-test-settings'

export const DEFAULT_HOST = 'https://api.zoo.dev'
export const SETTINGS_FILE_NAME = 'settings.toml'
export const TOKEN_FILE_NAME = 'token.txt'
export const PROJECT_SETTINGS_FILE_NAME = 'project.toml'
export const COOKIE_NAME = '__Secure-next-auth.session-token'

Expand Down
46 changes: 46 additions & 0 deletions src/lib/desktop.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import {
PROJECT_FOLDER,
PROJECT_SETTINGS_FILE_NAME,
SETTINGS_FILE_NAME,
TOKEN_FILE_NAME,
} from './constants'
import { DeepPartial } from './types'
import { ProjectConfiguration } from 'wasm-lib/kcl/bindings/ProjectConfiguration'
Expand Down Expand Up @@ -396,6 +397,23 @@ const getAppSettingsFilePath = async () => {
}
return window.electron.path.join(fullPath, SETTINGS_FILE_NAME)
}
const getTokenFilePath = async () => {
const isTestEnv = window.electron.process.env.IS_PLAYWRIGHT === 'true'
const testSettingsPath = window.electron.process.env.TEST_SETTINGS_FILE_KEY
const appConfig = await window.electron.getPath('appData')
const fullPath = isTestEnv
? testSettingsPath
: window.electron.path.join(appConfig, getAppFolderName())
try {
await window.electron.stat(fullPath)
} catch (e) {
// File/path doesn't exist
if (e === 'ENOENT') {
await window.electron.mkdir(fullPath, { recursive: true })
}
}
return window.electron.path.join(fullPath, TOKEN_FILE_NAME)
}

const getProjectSettingsFilePath = async (projectPath: string) => {
try {
Expand Down Expand Up @@ -475,6 +493,34 @@ export const writeAppSettingsFile = async (tomlStr: string) => {
return window.electron.writeFile(appSettingsFilePath, tomlStr)
}

export const readTokenFile = async () => {
let settingsPath = await getTokenFilePath()

if (window.electron.exists(settingsPath)) {
const token: string = await window.electron.readFile(settingsPath)
if (!token) return ''

return token
}
return ''
}

export const writeTokenFile = async (token: string) => {
const tokenFilePath = await getTokenFilePath()
if (err(token)) return Promise.reject(token)
return window.electron.writeFile(tokenFilePath, token)
}

let appStateStore: Project | undefined = undefined

export const getState = async (): Promise<Project | undefined> => {
return Promise.resolve(appStateStore)
}

export const setState = async (state: Project | undefined): Promise<void> => {
appStateStore = state
}

export const getUser = async (
token: string,
hostname: string
Expand Down
40 changes: 33 additions & 7 deletions src/machines/authMachine.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,11 @@ import {
VITE_KC_SKIP_AUTH,
DEV,
} from 'env'
import { getUser as getUserDesktop } from 'lib/desktop'
import {
getUser as getUserDesktop,
readTokenFile,
writeTokenFile,
} from 'lib/desktop'
import { COOKIE_NAME } from 'lib/constants'

const SKIP_AUTH = VITE_KC_SKIP_AUTH === 'true' && DEV
Expand Down Expand Up @@ -53,6 +57,7 @@ const persistedToken =

export const authMachine = createMachine<UserContext, Events>(
{
/** @xstate-layout N4IgpgJg5mDOIC5QEECuAXAFgOgMabFwGsBJAMwBkB7KGCEgOwGIIqGxsBLBgNyqI75CRALQAbGnRHcA2gAYAuolAAHKrE7pObZSAAeiAIwBmAEzYA7ABYAbAFZTcgBzGbN44adWANCACeiKbGdthypk4AnBFyVs6uQXYAvom+aFh4BMTk1LSQjExgAE6FVIXYKmIAhuhkpQC2GcLikpDSDPJKSCBqGlo6XQYIrk7YETYWctYRxmMWFk6+AUPj2I5OdjZyrnZOFmbJqRg4Ern0zDkABFQYHbo9mtoMuoOGFhHYxlZOhvbOsUGGRaIL4WbBONzWQxWYwWOx2H4HEBpY4tCAAeQwTEuskUd3UD36oEGIlMNlCuzk8Js0TcVisgP8iG2lmcGysb0mW3ByRSIAYVAgcF0yLxvUez0QIms5ImVJpNjpDKWxmw9PGdLh4Te00+iORjSylFRjFFBKeA0QThGQWcexMwWhniBCGiqrepisUVMdlszgieqO2BOdBNXXufXNRKMHtGVuphlJkXs4Wdriso2CCasdgipOidID6WDkAx6FNEYlCAT5jmcjrckMdj2b3GzpsjbBMVMWezDbGPMSQA */
id: 'Auth',
initial: 'checkIfLoggedIn',
states: {
Expand Down Expand Up @@ -85,6 +90,9 @@ export const authMachine = createMachine<UserContext, Events>(
on: {
'Log out': {
target: 'loggedOut',
actions: () => {
if (isDesktop()) writeTokenFile('')
},
},
},
},
Expand All @@ -96,7 +104,6 @@ export const authMachine = createMachine<UserContext, Events>(
actions: assign({
token: (_, event) => {
const token = event.token || ''
localStorage.setItem(TOKEN_PERSIST_KEY, token)
return token
},
}),
Expand All @@ -120,11 +127,7 @@ export const authMachine = createMachine<UserContext, Events>(
)

async function getUser(context: UserContext) {
const token = VITE_KC_DEV_TOKEN
? VITE_KC_DEV_TOKEN
: context.token && context.token !== ''
? context.token
: getCookie(COOKIE_NAME) || localStorage?.getItem(TOKEN_PERSIST_KEY)
const token = await getAndSyncStoredToken(context)
const url = withBaseURL('/user')
const headers: { [key: string]: string } = {
'Content-Type': 'application/json',
Expand Down Expand Up @@ -189,3 +192,26 @@ function getCookie(cname: string): string | null {
}
return null
}

async function getAndSyncStoredToken(context: UserContext): Promise<string> {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for rewriting this ternary logic. Flow is easier to read

// dev mode
if (VITE_KC_DEV_TOKEN) return VITE_KC_DEV_TOKEN

const token =
context.token && context.token !== ''
? context.token
: getCookie(COOKIE_NAME) || localStorage?.getItem(TOKEN_PERSIST_KEY) || ''
if (token) {
// has just logged in, update storage
localStorage.setItem(TOKEN_PERSIST_KEY, token)
isDesktop() && writeTokenFile(token)
return token
}
if (!isDesktop()) return ''
const fileToken = isDesktop() ? await readTokenFile() : ''
// prefer other above, but file will ensure login persists after app updates
if (!fileToken) return ''
// has token in file, update localStorage
localStorage.setItem(TOKEN_PERSIST_KEY, fileToken)
return fileToken
}
Loading