| 148 | } |
| 149 | |
| 150 | class KeychainWithFileFallback implements TokenStore { |
| 151 | readonly backend = 'keyring' as const; |
| 152 | constructor( |
| 153 | private readonly keychain: TokenStore, |
| 154 | private readonly file: FileBackend, |
| 155 | ) {} |
| 156 | |
| 157 | async get(host: string): Promise<TokenEntry | null> { |
| 158 | const fromKeychain = await this.keychain.get(host); |
| 159 | if (fromKeychain != null) return fromKeychain; |
| 160 | |
| 161 | const fromFile = await this.file.get(host); |
| 162 | if (fromFile == null) return null; |
| 163 | |
| 164 | try { |
| 165 | await this.keychain.set(host, fromFile.login, fromFile.token, { |
| 166 | gitProtocol: fromFile.gitProtocol, |
| 167 | name: fromFile.name, |
| 168 | email: fromFile.email, |
| 169 | }); |
| 170 | await this.file.clear(host); |
| 171 | process.stderr.write( |
| 172 | `[auth] migrated ${host} credential from ~/.ok/auth.yml to the OS keychain\n`, |
| 173 | ); |
| 174 | } catch {} |
| 175 | return fromFile; |
| 176 | } |
| 177 | |
| 178 | set( |
| 179 | host: string, |
| 180 | login: string, |
| 181 | token: string, |
| 182 | extra?: Pick<TokenEntry, 'gitProtocol' | 'name' | 'email'>, |
| 183 | ): Promise<void> { |
| 184 | return this.keychain.set(host, login, token, extra); |
| 185 | } |
| 186 | |
| 187 | async clear(host: string): Promise<void> { |
| 188 | await this.keychain.clear(host); |
| 189 | if ((await this.file.get(host)) != null) await this.file.clear(host); |
| 190 | } |
| 191 | } |
| 192 | |
| 193 | export async function createTokenStore( |
| 194 | authFile?: string, |
nothing calls this directly
no outgoing calls
no test coverage detected