97 lines
3.2 KiB
TypeScript
97 lines
3.2 KiB
TypeScript
/* eslint camelcase: 0 */
|
|
import {OAuth2LoginProvider, OAuth2LoginProviderConfig} from './OAuth2LoginProvider'
|
|
import {Authenticatable} from '../../types'
|
|
import {Request} from '../../../http/lifecycle/Request'
|
|
import {ErrorWithContext, uuid4, fetch} from '../../../util'
|
|
|
|
/**
|
|
* OAuth2LoginProvider implementation that authenticates users against a
|
|
* Starship CoreID server.
|
|
*/
|
|
export class CoreIDLoginProvider extends OAuth2LoginProvider<OAuth2LoginProviderConfig> {
|
|
protected async callback(request: Request): Promise<Authenticatable> {
|
|
// Get authentication_code from the request
|
|
const code = request.safe('code').string()
|
|
|
|
// Get OAuth2 token from CoreID
|
|
const token = await this.getToken(code)
|
|
|
|
// Get user from endpoint
|
|
const userData = await this.getUserData(token)
|
|
|
|
// Return authenticatable instance
|
|
const existing = await this.security.repository.getByIdentifier(userData.uid)
|
|
if ( existing ) {
|
|
this.updateUser(existing, userData)
|
|
return existing
|
|
}
|
|
|
|
const user = await this.security.repository.createFromCredentials(userData.uid, uuid4())
|
|
this.updateUser(user, userData)
|
|
return user
|
|
}
|
|
|
|
/** Given an access token, look up the associated user's information. */
|
|
protected async getUserData(token: string): Promise<any> {
|
|
const userResponse = await fetch(
|
|
this.config.userUrl,
|
|
{
|
|
method: 'GET',
|
|
headers: {
|
|
Authorization: `Bearer ${token}`,
|
|
},
|
|
},
|
|
)
|
|
|
|
const userData: any = await userResponse.json()
|
|
if ( !userData?.data?.uid ) {
|
|
throw new ErrorWithContext('Unable to extract user from response', {
|
|
userData,
|
|
})
|
|
}
|
|
|
|
return userData.data
|
|
}
|
|
|
|
/** Given a login code, redeem it for an access token. */
|
|
protected async getToken(code: string): Promise<string> {
|
|
const body: string[] = [
|
|
'code=' + encodeURIComponent(code),
|
|
'client_id=' + encodeURIComponent(this.config.clientId),
|
|
'client_secret=' + encodeURIComponent(this.config.clientSecret),
|
|
'grant_type=authorization_code',
|
|
]
|
|
|
|
const response = await fetch(
|
|
this.config.tokenUrl,
|
|
{
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/x-www-form-urlencoded',
|
|
},
|
|
body: body.join('&'),
|
|
},
|
|
)
|
|
|
|
const data = await response.json()
|
|
const token = (data as any).access_token
|
|
if ( !token ) {
|
|
throw new ErrorWithContext('Unable to obtain access token from response', {
|
|
data,
|
|
})
|
|
}
|
|
|
|
return String(token)
|
|
}
|
|
|
|
/** Update values on the Authenticatable from user data. */
|
|
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
|
|
protected updateUser(user: any, data: any): void {
|
|
user.firstName = data.first_name
|
|
user.lastName = data.last_name
|
|
user.email = data.email
|
|
user.tagline = data.tagline
|
|
user.photoUrl = data.profile_photo
|
|
}
|
|
}
|