@@ -10,20 +10,19 @@ import * as HttpClientRequest from "effect/http/HttpClientRequest";
1010import * as HttpClientResponse from "effect/http/HttpClientResponse" ;
1111import * as Schema from "effect/Schema" ;
1212
13- import { capture , exec , UserError } from "./runtime.ts" ;
13+ import { type Credentials , loadCredentials , saveCredentials } from "./credentials.ts" ;
14+ import { openBrowser , useDeviceLogin } from "./desktop.ts" ;
15+ import { UserError } from "./runtime.ts" ;
1416
15- const KEYCHAIN_SERVICE = "com.blankparticle.bp.oauth" ;
1617const CALLBACK_TIMEOUT_MS = 2 * 60 * 1000 ;
1718
1819const Discovery = Schema . Struct ( {
1920 authorization_endpoint : Schema . String ,
2021 token_endpoint : Schema . String ,
2122 device_authorization_endpoint : Schema . optional ( Schema . String ) ,
2223} ) ;
23- const TokenResponse = Schema . Struct ( { id_token : Schema . String , expires_in : Schema . Number } ) ;
2424/** The signed id_token is the bearer credential; there is no refresh, `bp login` again once it expires */
25- const Credentials = Schema . Struct ( { idToken : Schema . String , expiresAt : Schema . Number } ) ;
26- type Credentials = typeof Credentials . Type ;
25+ const TokenResponse = Schema . Struct ( { id_token : Schema . String , expires_in : Schema . Number } ) ;
2726
2827const oauthError = ( message : string ) => new UserError ( { message } ) ;
2928
@@ -41,31 +40,6 @@ const requestJson = <S extends Schema.ConstraintDecoder<unknown>>(
4140 Effect . mapError ( ( cause ) => ( cause instanceof UserError ? cause : oauthError ( `OAuth request failed: ${ cause } ` ) ) ) ,
4241 ) ;
4342
44- const keychainCredentials = ( baseUrl : string ) =>
45- Effect . flatMap ( capture ( "security" , [ "find-generic-password" , "-a" , baseUrl , "-s" , KEYCHAIN_SERVICE , "-w" ] ) , ( raw ) => {
46- if ( raw === null ) return Effect . succeed < Credentials | null > ( null ) ;
47- return Effect . try ( {
48- try : ( ) => Schema . decodeUnknownSync ( Credentials ) ( JSON . parse ( raw ) ) ,
49- catch : ( ) => oauthError ( "stored OAuth credentials are invalid; run `bp login` again" ) ,
50- } ) ;
51- } ) ;
52-
53- const saveCredentials = ( baseUrl : string , credentials : Credentials ) =>
54- Effect . flatMap (
55- capture ( "security" , [
56- "add-generic-password" ,
57- "-U" ,
58- "-a" ,
59- baseUrl ,
60- "-s" ,
61- KEYCHAIN_SERVICE ,
62- "-w" ,
63- JSON . stringify ( credentials ) ,
64- ] ) ,
65- ( result ) =>
66- result === null ? Effect . fail ( oauthError ( "could not save OAuth credentials to macOS Keychain" ) ) : Effect . void ,
67- ) ;
68-
6943const randomBase64Url = ( bytes : number ) =>
7044 Buffer . from ( crypto . getRandomValues ( new Uint8Array ( bytes ) ) ) . toString ( "base64url" ) ;
7145
@@ -183,10 +157,6 @@ const identity = (resource: string) => ({
183157 resource,
184158} ) ;
185159
186- /** Best effort; on machines without Helium the printed URL is the fallback */
187- const openBrowser = ( url : string ) =>
188- process . platform === "darwin" ? Effect . ignore ( exec ( "open" , [ "-a" , "Helium" , url ] ) ) : Effect . void ;
189-
190160const store = ( resource : string , tokens : { id_token : string ; expires_in : number } ) =>
191161 Effect . gen ( function * ( ) {
192162 const credentials : Credentials = { idToken : tokens . id_token , expiresAt : Date . now ( ) + tokens . expires_in * 1000 } ;
@@ -284,15 +254,14 @@ export const login = (baseUrl: string, options: { device: boolean }) =>
284254 Discovery ,
285255 HttpClientRequest . get ( `${ authOrigin ( ) } /.well-known/openid-configuration` ) ,
286256 ) ;
287- // Off macOS there is no known browser to open, so the code flow is the default there
288- return yield * options . device || process . platform !== "darwin"
257+ return yield * useDeviceLogin ( options . device )
289258 ? loginWithDevice ( resource , metadata )
290259 : loginWithBrowser ( resource , metadata ) ;
291260 } ) ;
292261
293262export const accessToken = ( baseUrl : string ) =>
294263 Effect . gen ( function * ( ) {
295- const credentials = yield * keychainCredentials ( new URL ( baseUrl ) . origin ) ;
264+ const credentials = yield * loadCredentials ( new URL ( baseUrl ) . origin ) ;
296265 if ( credentials === null ) return yield * oauthError ( "not signed in; run `bp login`" ) ;
297266 if ( credentials . expiresAt <= Date . now ( ) + 30_000 ) return yield * oauthError ( "sign-in expired; run `bp login` again" ) ;
298267 return credentials . idToken ;
0 commit comments