-
-
Notifications
You must be signed in to change notification settings - Fork 2.9k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
9d9bf1c
commit a34efb6
Showing
8 changed files
with
167 additions
and
121 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
import { UserinfoResponse } from 'openid-client'; | ||
|
||
export const IOAuthRepository = 'IOAuthRepository'; | ||
|
||
export type OAuthConfig = { | ||
clientId: string; | ||
clientSecret: string; | ||
issuerUrl: string; | ||
mobileOverrideEnabled: boolean; | ||
mobileRedirectUri: string; | ||
profileSigningAlgorithm: string; | ||
scope: string; | ||
signingAlgorithm: string; | ||
}; | ||
export type OAuthProfile = UserinfoResponse; | ||
|
||
export interface IOAuthRepository { | ||
init(): void; | ||
authorize(config: OAuthConfig, redirectUrl: string): Promise<string>; | ||
getLogoutEndpoint(config: OAuthConfig): Promise<string | undefined>; | ||
getProfile(config: OAuthConfig, url: string): Promise<OAuthProfile>; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,84 @@ | ||
import { Inject, Injectable, InternalServerErrorException } from '@nestjs/common'; | ||
import { custom, generators, Issuer } from 'openid-client'; | ||
import { ILoggerRepository } from 'src/interfaces/logger.interface'; | ||
import { IOAuthRepository, OAuthConfig, OAuthProfile } from 'src/interfaces/oauth.interface'; | ||
import { Instrumentation } from 'src/utils/instrumentation'; | ||
|
||
@Instrumentation() | ||
@Injectable() | ||
export class OAuthRepository implements IOAuthRepository { | ||
constructor(@Inject(ILoggerRepository) private logger: ILoggerRepository) { | ||
this.logger.setContext(OAuthRepository.name); | ||
} | ||
|
||
init() { | ||
custom.setHttpOptionsDefaults({ timeout: 30_000 }); | ||
} | ||
|
||
async authorize(config: OAuthConfig, redirectUrl: string) { | ||
const client = await this.getClient(config); | ||
return client.authorizationUrl({ | ||
redirect_uri: redirectUrl, | ||
scope: config.scope, | ||
state: generators.state(), | ||
}); | ||
} | ||
|
||
async getLogoutEndpoint(config: OAuthConfig) { | ||
const client = await this.getClient(config); | ||
return client.issuer.metadata.end_session_endpoint; | ||
} | ||
|
||
async getProfile(config: OAuthConfig, url: string): Promise<OAuthProfile> { | ||
const client = await this.getClient(config); | ||
const params = client.callbackParams(url); | ||
try { | ||
const tokens = await client.callback(this.normalize(config, url.split('?')[0]), params, { state: params.state }); | ||
return await client.userinfo<OAuthProfile>(tokens.access_token || ''); | ||
} catch (error: Error | any) { | ||
if (error.message.includes('unexpected JWT alg received')) { | ||
this.logger.warn( | ||
[ | ||
'Algorithm mismatch. Make sure the signing algorithm is set correctly in the OAuth settings.', | ||
'Or, that you have specified a signing key in your OAuth provider.', | ||
].join(' '), | ||
); | ||
} | ||
|
||
throw error; | ||
} | ||
} | ||
|
||
private async getClient({ | ||
issuerUrl, | ||
clientId, | ||
clientSecret, | ||
profileSigningAlgorithm, | ||
signingAlgorithm, | ||
}: OAuthConfig) { | ||
try { | ||
const issuer = await Issuer.discover(issuerUrl); | ||
return new issuer.Client({ | ||
client_id: clientId, | ||
client_secret: clientSecret, | ||
response_types: ['code'], | ||
userinfo_signed_response_alg: profileSigningAlgorithm === 'none' ? undefined : profileSigningAlgorithm, | ||
id_token_signed_response_alg: signingAlgorithm, | ||
}); | ||
} catch (error: any | AggregateError) { | ||
this.logger.error(`Error in OAuth discovery: ${error}`, error?.stack, error?.errors); | ||
throw new InternalServerErrorException(`Error in OAuth discovery: ${error}`, { cause: error }); | ||
} | ||
} | ||
|
||
private normalize( | ||
{ mobileRedirectUri, mobileOverrideEnabled }: { mobileRedirectUri: string; mobileOverrideEnabled: boolean }, | ||
redirectUri: string, | ||
) { | ||
const isMobile = redirectUri.startsWith('app.immich:/'); | ||
if (isMobile && mobileOverrideEnabled && mobileRedirectUri) { | ||
return mobileRedirectUri; | ||
} | ||
return redirectUri; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.