import { assert } from "@fluidframework/common-utils" ; import { getAadTenant , getAadUrl , getSiteUrl } from "./odspDocLibUtils" ; import { throwOdspNetworkError } from "./odspErrorUtils" ; import { unauthPostAsync } from "./odspRequest" ; export interface IOdspTokens { readonly accessToken : string ; readonly refreshToken : string ; } export interface IClientConfig { clientId : string ; clientSecret : string ; } export interface IOdspAuthRequestInfo { accessToken : string ; refreshTokenFn ? : ( ) => Promise < string > ; } export type TokenRequestCredentials = { grant_type : "authorization_code" ; code : string ; redirect_uri : string ; } | { grant_type : "refresh_token" ; refresh_token : string ; } | { grant_type : "password" ; username : string ; password : string ; } ; type TokenRequestBody = TokenRequestCredentials & { client_id : string ; client_secret : string ; scope : string ; } ; export const getOdspScope = ( server ) => `offline_access ${ getSiteUrl ( server ) }/AllSites.Write` ; export const pushScope = "offline_access https://pushchannel.1drv.ms/PushChannel.ReadWrite.All" ; export function getFetchTokenUrl ( server ) { return `${ getAadUrl ( server ) }/${ getAadTenant ( server ) }/oauth2/v2.0/token` ; } export function getLoginPageUrl ( server , clientConfig , scope , odspAuthRedirectUri ) { return `${ getAadUrl ( server ) }/${ getAadTenant ( server ) }/oauth2/v2.0/authorize?` + `client_id=${ clientConfig . clientId }` + `&scope=${ scope }` + `&response_type=code` + `&redirect_uri=${ odspAuthRedirectUri }` ; } export const getOdspRefreshTokenFn = ( server , clientConfig , tokens ) => getRefreshTokenFn ( getOdspScope ( server ) , server , clientConfig , tokens ) ; export const getPushRefreshTokenFn = ( server , clientConfig , tokens ) => getRefreshTokenFn ( pushScope , server , clientConfig , tokens ) ; export const getRefreshTokenFn = ( scope , server , clientConfig , tokens ) => async ( ) => { const newTokens = await refreshTokens ( server , scope , clientConfig , tokens ) ; return newTokens . accessToken ; } ; param server param scope param clientConfig param credentials export async function fetchTokens ( server , scope , clientConfig , credentials ) { const body = { scope , client_id : clientConfig . clientId , client_secret : clientConfig . clientSecret , ... credentials } ; const result = await unauthPostAsync ( getFetchTokenUrl ( server ) , new URLSearchParams ( body ) ) ; const tokens = await result . json ( ) ; const accessToken = tokens . access_token ; const refreshToken = tokens . refresh_token ; if ( accessToken === undefined || refreshToken === undefined ) { throwOdspNetworkError ( "unableToGetAccessToken" , tokens . error === "invalid_grant" ? 401 : result . status , result ) ; } return { accessToken , refreshToken } ; } param server param scope param clientConfig param tokens returns export async function refreshTokens ( server , scope , clientConfig , tokens ) { const refresh_token = tokens . refreshToken ; assert ( refresh_token . length > 0 , 0x1ec ) ; const credentials = { grant_type : "refresh_token" , refresh_token } ; const newTokens = await fetchTokens ( server , scope , clientConfig , credentials ) ; return { accessToken : newTokens . accessToken , refreshToken : newTokens . refreshToken } ; } /**
 * Issue the requestCallback, providing the proper auth header based on authRequestInfo,
 * and retrying with a refreshed token if necessary.
 */ export async function authRequestWithRetry ( authRequestInfo , requestCallback ) { const createConfig = ( token ) => ( { headers : { Authorization : `Bearer ${ token }` } } ) ; const result = await requestCallback ( createConfig ( authRequestInfo . accessToken ) ) ; if ( authRequestInfo . refreshTokenFn && ( result . status === 401 || result . status === 403 ) ) { const refreshedAccessToken = await authRequestInfo . refreshTokenFn ( ) ; return requestCallback ( createConfig ( refreshedAccessToken ) ) ; } return result ; }