diff --git a/deadlock-plugins/deadlock-extension/src/core/callApi.service.ts b/deadlock-plugins/deadlock-extension/src/core/callApi.service.ts index afbd314b6ff18f6e0e9db2cded7e2b7a2bc01b57..3bfdad638b9384f40e7a66c44eefff8431e82d81 100644 --- a/deadlock-plugins/deadlock-extension/src/core/callApi.service.ts +++ b/deadlock-plugins/deadlock-extension/src/core/callApi.service.ts @@ -52,11 +52,14 @@ export default class CallApiService { originalConfig._retry = true; try { const storedRefreshToken = await this.extensionStore.getRefreshToken(); - const { accessToken } = await this.keycloackConnection.getToken({ + const { accessToken, refreshToken } = await this.keycloackConnection.getToken({ refreshToken: storedRefreshToken, openLink: Controller.openBrowserWithUrl, }); + await this.extensionStore.setAccessToken(accessToken); + await this.extensionStore.setRefreshToken(refreshToken); + this.callApi.defaults.headers.common['authorization'] = `BEARER ${accessToken}`; return this.callApi(originalConfig); } catch (_error: any) { diff --git a/deadlock-plugins/deadlock-extension/src/core/config.ts b/deadlock-plugins/deadlock-extension/src/core/config.ts index 9cb8eb8311da779c8cbf734799a6655fa41cdb8a..c0849c9d760a422e78274cb749c924528cc57e31 100644 --- a/deadlock-plugins/deadlock-extension/src/core/config.ts +++ b/deadlock-plugins/deadlock-extension/src/core/config.ts @@ -8,6 +8,9 @@ const onContainer = isDocker(); const deadlockExtensionPath = path.join(homeDir, 'deadlock-extension'); +const deadlockConfigPath = path.join(homeDir, '.deadlock'); +export const userSshKeyFolderPath = path.join(deadlockConfigPath, '.ssh'); + export const PROJECT_SRC_PATH = onContainer ? '/project' : path.join(homeDir, 'deadlock-extension', '/project'); export const PROJECT_DEADLOCK_DESKTOP_PATH = onContainer diff --git a/deadlock-plugins/deadlock-extension/src/core/controller.ts b/deadlock-plugins/deadlock-extension/src/core/controller.ts index fa128393125dd01a387efbee95126cb89028aa05..2f4ea94875cfaa923f8546e55567437d8314cddf 100644 --- a/deadlock-plugins/deadlock-extension/src/core/controller.ts +++ b/deadlock-plugins/deadlock-extension/src/core/controller.ts @@ -7,6 +7,7 @@ import { KEYCLOAK_USER_INFO_URL, REGISTRY_MISSION_URL, } from '../config'; + import { log } from '../recorder/utils'; import { OPEN_QUICK_SETUP_COMMAND } from '../theia/command'; import BriefingView from '../view/briefingView'; @@ -81,16 +82,10 @@ export default class Controller { } this.chooseMissionWorkdir(); } else { - await this.insertSshKeyInMissionWorkdir(folderUri[0].path); this.extensionStore.setMissionWorkdir(folderUri[0].path); } } - public async insertSshKeyInMissionWorkdir(sshKeyFolderPath) { - const { publicKey, privateKey } = await this.callApiService.getUserSshKey(); - this.sshKeyManager.createSshKeyFiles(publicKey, privateKey, `${sshKeyFolderPath}/.ssh`); - } - public async createMissionUserChallengeJson(missionId: string) {} public async clear() { @@ -99,6 +94,12 @@ export default class Controller { this.quickSetupView.isAlreadyConnected = false; } + public async createSshKeyPairIfNotExist() { + if (this.sshKeyManager.isSshKeyPairExist()) return; + const { publicKey, privateKey } = await this.callApiService.getUserSshKey(); + this.sshKeyManager.createSshKeyFiles(publicKey, privateKey); + } + public async authenticate() { // WARN generate a new device code every time student clicks on log in button. Should I keep it ?\ // The answer might be 'yes' because when the student is already authenticated, the log in button should be disabled. @@ -106,6 +107,7 @@ export default class Controller { const tokens = await this.connection.getToken({ openLink: Controller.openBrowserWithUrl }); await this.extensionStore.setAccessToken(tokens.accessToken); await this.extensionStore.setRefreshToken(tokens.refreshToken); + await this.createSshKeyPairIfNotExist(); this.quickSetupView.isAlreadyConnected = true; } public static openBrowserWithUrl(url: string) { diff --git a/deadlock-plugins/deadlock-extension/src/core/extensionStore.ts b/deadlock-plugins/deadlock-extension/src/core/extensionStore.ts index 5d4497c98e99a9929c8e88368d2c8a445bb602cc..42e5c7243f46cf811178736ff0c8299a8c2aae37 100644 --- a/deadlock-plugins/deadlock-extension/src/core/extensionStore.ts +++ b/deadlock-plugins/deadlock-extension/src/core/extensionStore.ts @@ -42,35 +42,35 @@ export default class ExtensionStore { window.showInformationMessage(`Nouveau dossier de stockage des missions: ${path}`); } - public async getAccessToken() { + public getAccessToken(): Thenable<string | undefined> { return this.readSecret(StoreKey.AccessTokenKey); } - public async getRefreshToken() { + public getRefreshToken(): Thenable<string | undefined> { return this.readSecret(StoreKey.RefreshTokenKey); } - public async setAccessToken(accessToken: string) { + public setAccessToken(accessToken: string): Thenable<void> { if (!accessToken) { log('Attempt to store undefined access token'); - return; + return Promise.resolve(); } return this.storeSecret(StoreKey.AccessTokenKey, accessToken); } - public async setRefreshToken(refreshToken: string) { + public setRefreshToken(refreshToken: string): Thenable<void> { if (!refreshToken) { log('Attempt to store undefined refresh token'); - return; + return Promise.resolve(); } return this.storeSecret(StoreKey.RefreshTokenKey, refreshToken); } - private async storeSecret(key: StoreKey, value: string) { - this.secretStorage.store(key, value); + private storeSecret(key: StoreKey, value: string): Thenable<void> { + return this.secretStorage.store(key, value); } - private async readSecret(key: StoreKey) { + private readSecret(key: StoreKey): Thenable<string | undefined> { return this.secretStorage.get(key); } } diff --git a/deadlock-plugins/deadlock-extension/src/core/mission.ts b/deadlock-plugins/deadlock-extension/src/core/mission.ts index d159354d6a1658ad89c2063ed6a67e47d0e98b4e..fc4c4b9340fe792eadb8051e3f01a3e2a8671291 100644 --- a/deadlock-plugins/deadlock-extension/src/core/mission.ts +++ b/deadlock-plugins/deadlock-extension/src/core/mission.ts @@ -6,6 +6,7 @@ import * as vscode from 'vscode'; import { error as err, log } from '../recorder/utils'; import ExtensionStore from './extensionStore'; import { User, UserChallengeJson } from '../customTypings/user.model'; +import { userSshKeyFolderPath } from './config'; /** * {@link https://nodejs.org/api/child_process.html#child_process_child_process_exec_command_options_callback} @@ -22,8 +23,7 @@ export default class Mission { private readonly remoteUserHomeDir: string; private readonly remoteMissionDir: any; private readonly remoteGiteaWorkDir: string; - private readonly userConfigPath: string; - private readonly userSshPath: string; + private readonly userMissionConfigPath: string; private readonly user: User; private readonly giteaPublicProperties: GiteaPublicProperties; @@ -35,8 +35,7 @@ export default class Mission { const { registryBaseURL, missionId, missionVersion } = params; this.hostBaseWorkDir = ExtensionStore.getInstance().getMissionWorkdir() ?? ''; this.hostMissionDir = `${this.hostBaseWorkDir}/${missionId}`; - this.userConfigPath = `${this.hostMissionDir}/.config`; - this.userSshPath = `${this.hostBaseWorkDir}/.ssh`; + this.userMissionConfigPath = `${this.hostMissionDir}/.config`; this.hostMissionDevcontainerDir = `${this.hostMissionDir}/.devcontainer`; this.hostMissionDevcontainerFileDir = `${this.hostMissionDevcontainerDir}/devcontainer.json`; this.hostMissionMountDir = `${this.hostMissionDir}/mounted`; @@ -57,7 +56,7 @@ export default class Mission { public async setup(options?: Partial<DockerfileSpecific & Base & VSCodespecific & LifecycleScripts>) { await fs.promises.mkdir(this.hostMissionDevcontainerDir, { recursive: true }); await fs.promises.mkdir(this.hostMissionMountDir, { recursive: true }); - await fs.promises.mkdir(this.userConfigPath, { recursive: true }); + await fs.promises.mkdir(this.userMissionConfigPath, { recursive: true }); await this.createDevContainerFile(options); await this.createUserChallengeJsonFile(); } @@ -72,8 +71,8 @@ export default class Mission { extensions: ['Deadlock.deadlock-coding'], remoteUser: 'deadlock', mounts: [ - `source=${this.userSshPath},target=/tmp/.ssh,type=bind,consistency=cached`, - `source=${this.userConfigPath},target=/home/config/,type=bind,consistency=cached`, + `source=${userSshKeyFolderPath},target=/tmp/.ssh,type=bind,consistency=cached`, + `source=${this.userMissionConfigPath},target=/home/config/,type=bind,consistency=cached`, ], userEnvProbe: 'interactiveShell', settings: { @@ -105,7 +104,7 @@ export default class Mission { private createUserChallengeJsonFile() { return fs.promises.writeFile( - `${this.userConfigPath}/user-challenge.json`, + `${this.userMissionConfigPath}/user-challenge.json`, (() => { const userChallengeJson: UserChallengeJson = { giteaHost: this.giteaPublicProperties.sshHost, diff --git a/deadlock-plugins/deadlock-extension/src/core/sshKeyManager.ts b/deadlock-plugins/deadlock-extension/src/core/sshKeyManager.ts index 3ebf3a59b348ca58631e85c69ab1ba3df9dc8031..38579ba740436debccada5f4704afc7632b78cd3 100644 --- a/deadlock-plugins/deadlock-extension/src/core/sshKeyManager.ts +++ b/deadlock-plugins/deadlock-extension/src/core/sshKeyManager.ts @@ -1,14 +1,27 @@ import ExtensionStore from './extensionStore'; import * as fs from 'fs'; +import { userSshKeyFolderPath } from './config'; export default class SshKeyManager { constructor() {} - public async createSshKeyFiles(publicKey: string, privateKey: string, sshKeyFolderPath: string) { - await this.createSshKeyFolderIfNotExist(sshKeyFolderPath); - await fs.promises.writeFile(`${sshKeyFolderPath}/id_rsa.pub`, publicKey); + public isSshKeyPairExist(): boolean { + return this.isPrivateKeyExist() && this.isPublicKeyExist(); + } + + private isPublicKeyExist(): boolean { + return fs.existsSync(`${userSshKeyFolderPath}/id_rsa.pub`); + } + + private isPrivateKeyExist(): boolean { + return fs.existsSync(`${userSshKeyFolderPath}/id_rsa`); + } + + public async createSshKeyFiles(publicKey: string, privateKey: string) { + await this.createSshKeyFolderIfNotExist(userSshKeyFolderPath); + await fs.promises.writeFile(`${userSshKeyFolderPath}/id_rsa.pub`, publicKey); - await fs.promises.writeFile(`${sshKeyFolderPath}/id_rsa`, privateKey, { mode: 0o600 }); + await fs.promises.writeFile(`${userSshKeyFolderPath}/id_rsa`, privateKey, { mode: 0o600 }); } private async createSshKeyFolderIfNotExist(sshKeyFolderPath) { if (!this.isSshKeyFolderExist(sshKeyFolderPath)) {