@@ -10,6 +10,54 @@ export { CITokenError } from './ci-auth.svc.ts';
1010
1111export type AuthErrorCode = 'NOT_LOGGED_IN' | 'SESSION_EXPIRED' ;
1212
13+ export type TokenSource = 'oauth' | 'ci' ;
14+
15+ export type TokenProvider = ( forceRefresh ?: boolean ) => Promise < string > ;
16+
17+ export const AUTH_ERROR_MESSAGES = {
18+ UNAUTHENTICATED : 'Please log in to perform this action. To authenticate, please run an "auth login" command.' ,
19+ SESSION_EXPIRED : 'Your session has expired. To re-authenticate, please run an "auth login" command.' ,
20+ INVALID_TOKEN : 'Your session has expired. To re-authenticate, please run an "auth login" command.' ,
21+ FORBIDDEN : 'You do not have permission to perform this action.' ,
22+ NOT_LOGGED_IN_GENERIC : 'You are not logged in. Please run an "auth login" command to authenticate.' ,
23+ } as const ;
24+
25+ export async function getTokenForScanWithSource (
26+ preferOAuth ?: boolean ,
27+ ) : Promise < { token : string ; source : TokenSource } > {
28+ if ( preferOAuth ) {
29+ const token = await requireAccessToken ( ) ;
30+ return { token, source : 'oauth' } ;
31+ }
32+
33+ const tokens = await getStoredTokens ( ) ;
34+ if ( tokens ?. accessToken && ! isAccessTokenExpired ( tokens . accessToken ) ) {
35+ return { token : tokens . accessToken , source : 'oauth' } ;
36+ }
37+
38+ if ( tokens ?. refreshToken ) {
39+ try {
40+ const newTokens = await refreshTokens ( tokens . refreshToken ) ;
41+ await persistTokenResponse ( newTokens ) ;
42+ return { token : newTokens . access_token , source : 'oauth' } ;
43+ } catch ( error ) {
44+ debugLogger ( 'Token refresh failed: %O' , error ) ;
45+ }
46+ }
47+
48+ const ciToken = getCIToken ( ) ;
49+ if ( ciToken ) {
50+ const accessToken = await requireCIAccessToken ( ) ;
51+ return { token : accessToken , source : 'ci' } ;
52+ }
53+
54+ if ( ! tokens ?. accessToken ) {
55+ throw new AuthError ( AUTH_ERROR_MESSAGES . UNAUTHENTICATED , 'NOT_LOGGED_IN' ) ;
56+ }
57+
58+ throw new AuthError ( AUTH_ERROR_MESSAGES . SESSION_EXPIRED , 'SESSION_EXPIRED' ) ;
59+ }
60+
1361export class AuthError extends Error {
1462 readonly code : AuthErrorCode ;
1563
@@ -46,10 +94,17 @@ export async function getAccessToken(): Promise<string | undefined> {
4694 return refreshed . access_token ;
4795}
4896
97+ export function getTokenProvider ( preferOAuth ?: boolean ) : TokenProvider {
98+ return async ( _forceRefresh ?: boolean ) : Promise < string > => {
99+ const { token } = await getTokenForScanWithSource ( preferOAuth ) ;
100+ return token ;
101+ } ;
102+ }
103+
49104export async function requireAccessToken ( ) : Promise < string > {
50105 const token = await getAccessToken ( ) ;
51106 if ( ! token ) {
52- throw new Error ( 'You are not logged in. Please run an "auth login" command to authenticate.' ) ;
107+ throw new Error ( AUTH_ERROR_MESSAGES . NOT_LOGGED_IN_GENERIC ) ;
53108 }
54109
55110 return token ;
@@ -59,36 +114,4 @@ export async function logoutLocally() {
59114 await clearStoredTokens ( ) ;
60115}
61116
62- export async function requireAccessTokenForScan ( ) : Promise < string > {
63- if ( getCIToken ( ) ) {
64- return requireCIAccessToken ( ) ;
65- }
66-
67- const tokens = await getStoredTokens ( ) ;
68-
69- if ( ! tokens ?. accessToken ) {
70- throw new AuthError (
71- 'Please log in to perform a scan. To authenticate, please run an "auth login" command.' ,
72- 'NOT_LOGGED_IN' ,
73- ) ;
74- }
75-
76- if ( ! isAccessTokenExpired ( tokens . accessToken ) ) {
77- return tokens . accessToken ;
78- }
79-
80- if ( tokens . refreshToken ) {
81- try {
82- const newTokens = await refreshTokens ( tokens . refreshToken ) ;
83- await persistTokenResponse ( newTokens ) ;
84- return newTokens . access_token ;
85- } catch ( error ) {
86- debugLogger ( 'Token refresh failed: %O' , error ) ;
87- }
88- }
89-
90- throw new AuthError (
91- 'Your session is no longer valid. To re-authenticate, please run an "auth login" command.' ,
92- 'SESSION_EXPIRED' ,
93- ) ;
94- }
117+ export const requireAccessTokenForScan = getTokenProvider ( ) ;
0 commit comments