Skip to content

Commit

Permalink
fix: Microsoft account re-login everyday due to the accounts data is …
Browse files Browse the repository at this point in the history
…not properly saved
  • Loading branch information
ci010 committed Feb 7, 2024
1 parent 27bd550 commit 005ebea
Show file tree
Hide file tree
Showing 7 changed files with 27 additions and 70 deletions.
27 changes: 9 additions & 18 deletions xmcl-electron-app/main/ElectronSecretStorage.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import { SecretStorage } from '@xmcl/runtime/app'
import { safeStorage } from 'electron'
import filenamify from 'filenamify'
import { ensureDir, readFile, unlink, writeFile } from 'fs-extra'
import { deletePassword, getPassword, setPassword } from 'keytar'
import { join } from 'path'

function encrypt(s: string) {
try {
Expand All @@ -20,30 +21,20 @@ function decrypt(s: Buffer) {
}

export class ElectronSecretStorage implements SecretStorage {
constructor(private fallbackDir: string) {}
constructor(private dir: string) {}

async get(service: string, account: string): Promise<string | undefined> {
try {
return (await getPassword(service, account) || undefined)
} catch (e) {
return await readFile(this.fallbackDir).then(decrypt, () => undefined)
}
const key = filenamify(service + '@' + account)
return await readFile(join(this.dir, key)).then(decrypt, () => undefined)
}

async put(service: string, account: string, value: string): Promise<void> {
const key = filenamify(service + '@' + account)
if (value) {
try {
await setPassword(service, account, value)
} catch {
await ensureDir(this.fallbackDir)
await writeFile(this.fallbackDir, encrypt(value)).catch(() => undefined)
}
await ensureDir(this.dir)
await writeFile(join(this.dir, key), encrypt(value)).catch(() => undefined)
} else {
try {
await deletePassword(service, account)
} catch {
await unlink(this.fallbackDir).catch(() => undefined)
}
await unlink(join(this.dir, key)).catch(() => undefined)
}
}
}
1 change: 0 additions & 1 deletion xmcl-electron-app/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,6 @@
"filenamify": "^5.1.1",
"fs-extra": "^10.1.0",
"graceful-fs": "^4.2.10",
"keytar": "^7.9.0",
"lodash.debounce": "^4.0.8",
"node-datachannel": "0.5.3",
"node-disk-info": "^1.3.0",
Expand Down
4 changes: 1 addition & 3 deletions xmcl-keystone-ui/src/views/AppLoginForm.vue
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,6 @@ import { Ref } from 'vue'
import { useAccountSystemHistory, useAllowThirdparty, useAuthorityItems } from '../composables/login'
import { kUserContext, useLoginValidation } from '../composables/user'
import AppLoginAuthoritySelect from './AppLoginAuthoritySelect.vue'
import { kSettingsState } from '@/composables/setting'
import { kYggdrasilServices } from '@/composables/yggrasil'
const props = defineProps<{
Expand All @@ -148,8 +147,7 @@ const emit = defineEmits(['seed', 'login'])
const { t } = useI18n()
const { select } = injection(kUserContext)
const { login, abortLogin } = useService(UserServiceKey)
const { on } = useService(OfficialUserServiceKey)
const { login, abortLogin, on } = useService(UserServiceKey)
// Shared data
const data = reactive({
Expand Down
9 changes: 9 additions & 0 deletions xmcl-runtime-api/src/services/UserService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,15 @@ interface UserServiceEventMap {
'user-login': string
'error': UserException
'auth-profile-added': string
'microsoft-authorize-url': string
'device-code': {
userCode: string
deviceCode: string
verificationUri: string
expiresIn: number
interval: number
message: string
}
}

export class UserState {
Expand Down
2 changes: 1 addition & 1 deletion xmcl-runtime/user/accountSystems/MicrosoftOAuthClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ export class MicrosoftOAuthClient {
clientId: this.clientId,
},
cache: {
cachePlugin: createPlugin('xmcl', account, this.logger, this.storage),
cachePlugin: createPlugin('xmcl-oauth', account, this.logger, this.storage),
},
system: {
loggerOptions: {
Expand Down
53 changes: 7 additions & 46 deletions xmcl-runtime/user/credentialPlugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
*/

import { ICachePlugin, TokenCacheContext } from '@azure/msal-common'
import { platform } from 'os'
import { SecretStorage } from '~/app/SecretStorage'
import { Logger } from '~/logger'
import { AnyError } from '~/util/error'
Expand All @@ -13,54 +12,13 @@ const CredentialSerializeError = AnyError.make('CredentialSerializeError')

export function createPlugin(serviceName: string, accountName: string, logger: Logger, storage: SecretStorage): ICachePlugin {
accountName = accountName || 'XMCL_MICROSOFT_ACCOUNT'
if (platform() === 'win32') {
return {
async beforeCacheAccess(cacheContext: TokenCacheContext): Promise<void> {
try {
const part1 = await storage.get(`${serviceName}:1`, accountName)
if (part1) {
const part2 = await storage.get(`${serviceName}:2`, accountName)
const part3 = await storage.get(`${serviceName}:3`, accountName)
const part4 = await storage.get(`${serviceName}:4`, accountName)
if (part2 && part3 && part4) {
const content = part1 + part2 + part3 + part4
if (cacheContext.cacheHasChanged) {
return
}
cacheContext.tokenCache.deserialize(content)
}
}
} catch (e) {
// Should not prevent the login
logger.error(new CredentialSerializeError('Fail to deserialize the credential cache', { cause: e }))
}
},
async afterCacheAccess(cacheContext: TokenCacheContext): Promise<void> {
if (cacheContext.cacheHasChanged) {
try {
const currentCache = cacheContext.tokenCache.serialize()
const quad = Math.floor(currentCache.length / 4)
const part1 = currentCache.substring(0, quad)
const part2 = currentCache.substring(quad, quad + quad)
const part3 = currentCache.substring(quad + quad, quad + quad + quad)
const part4 = currentCache.substring(quad + quad + quad, currentCache.length)
await storage.put(`${serviceName}:1`, accountName, part1)
await storage.put(`${serviceName}:2`, accountName, part2)
await storage.put(`${serviceName}:3`, accountName, part3)
await storage.put(`${serviceName}:4`, accountName, part4)
} catch (e) {
logger.error(new CredentialSerializeError('Fail to serialzie the credential cache', { cause: e }))
}
}
},
}
}
let cachedInMemory: boolean
const plugin: ICachePlugin = {
async beforeCacheAccess(cacheContext: TokenCacheContext): Promise<void> {
const secret = await storage.get(serviceName, accountName).catch((e) => {
logger.error(new CredentialSerializeError('Fail to deserialize the credential cache', { cause: e }))
})
if (cacheContext.cacheHasChanged) {
if (cachedInMemory && cacheContext.cacheHasChanged) {
return
}
if (secret) {
Expand All @@ -73,8 +31,11 @@ export function createPlugin(serviceName: string, accountName: string, logger: L
},
async afterCacheAccess(cacheContext: TokenCacheContext): Promise<void> {
try {
const currentCache = cacheContext.tokenCache.serialize()
await storage.put(serviceName, accountName, currentCache)
if (cacheContext.cacheHasChanged) {
const currentCache = cacheContext.tokenCache.serialize()
cachedInMemory = true
await storage.put(serviceName, accountName, currentCache)
}
} catch (e) {
logger.error(new CredentialSerializeError('Fail to serialzie the credential cache', { cause: e }))
}
Expand Down
1 change: 0 additions & 1 deletion xmcl-runtime/user/pluginOfficialUserApi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,6 @@ export const pluginOfficialUserApi: LauncherAppPlugin = (app) => {
})
},
async (directRedirectToLauncher) => {
if (IS_DEV) directRedirectToLauncher = true
const port = await app.localhostServerPort ?? 25555
return (directRedirectToLauncher ? `http://localhost:${port}/auth` : `https://xmcl.app/auth?port=${port}`)
},
Expand Down

0 comments on commit 005ebea

Please sign in to comment.