0
Fork 0
mirror of https://github.com/logto-io/logto.git synced 2025-01-06 20:40:08 -05:00
logto/packages/connector-google/src/index.ts
Darcy Ye 097aade2e2
feat(connectors): handle authorization callback parameters in each connector respectively (#1166)
* feat(connectors): handle authorization callback parameters in each connector respectively
2022-06-26 18:03:53 +08:00

134 lines
3.9 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* The Implementation of OpenID Connect of Google Identity Platform.
* https://developers.google.com/identity/protocols/oauth2/openid-connect
*/
import {
ConnectorError,
ConnectorErrorCodes,
GetAuthorizationUri,
GetUserInfo,
ConnectorMetadata,
ValidateConfig,
SocialConnector,
GetConnectorConfig,
codeWithRedirectDataGuard,
} from '@logto/connector-types';
import { conditional, assert } from '@silverhand/essentials';
import got, { RequestError as GotRequestError } from 'got';
import {
accessTokenEndpoint,
authorizationEndpoint,
scope,
userInfoEndpoint,
defaultMetadata,
defaultTimeout,
} from './constant';
import {
googleConfigGuard,
GoogleConfig,
accessTokenResponseGuard,
userInfoResponseGuard,
} from './types';
export default class GoogleConnector implements SocialConnector {
public metadata: ConnectorMetadata = defaultMetadata;
constructor(public readonly getConfig: GetConnectorConfig<GoogleConfig>) {}
public validateConfig: ValidateConfig = async (config: unknown) => {
const result = googleConfigGuard.safeParse(config);
if (!result.success) {
throw new ConnectorError(ConnectorErrorCodes.InvalidConfig, result.error.message);
}
};
public getAuthorizationUri: GetAuthorizationUri = async ({ state, redirectUri }) => {
const config = await this.getConfig(this.metadata.id);
const queryParameters = new URLSearchParams({
client_id: config.clientId,
redirect_uri: redirectUri,
response_type: 'code',
state,
scope,
});
return `${authorizationEndpoint}?${queryParameters.toString()}`;
};
public getAccessToken = async (code: string, redirectUri: string) => {
const { clientId, clientSecret } = await this.getConfig(this.metadata.id);
// NoteNeed to decodeURIComponent on code
// https://stackoverflow.com/questions/51058256/google-api-node-js-invalid-grant-malformed-auth-code
const httpResponse = await got.post(accessTokenEndpoint, {
form: {
code: decodeURIComponent(code),
client_id: clientId,
client_secret: clientSecret,
redirect_uri: redirectUri,
grant_type: 'authorization_code',
},
timeout: defaultTimeout,
});
const result = accessTokenResponseGuard.safeParse(JSON.parse(httpResponse.body));
if (!result.success) {
throw new ConnectorError(ConnectorErrorCodes.InvalidResponse, result.error.message);
}
const { access_token: accessToken } = result.data;
assert(accessToken, new ConnectorError(ConnectorErrorCodes.SocialAuthCodeInvalid));
return { accessToken };
};
public getUserInfo: GetUserInfo = async (data) => {
const { code, redirectUri } = await this.authorizationCallbackHandler(data);
const { accessToken } = await this.getAccessToken(code, redirectUri);
try {
const httpResponse = await got.post(userInfoEndpoint, {
headers: {
authorization: `Bearer ${accessToken}`,
},
timeout: defaultTimeout,
});
const result = userInfoResponseGuard.safeParse(JSON.parse(httpResponse.body));
if (!result.success) {
throw new ConnectorError(ConnectorErrorCodes.InvalidResponse, result.error.message);
}
const { sub: id, picture: avatar, email, email_verified, name } = result.data;
return {
id,
avatar,
email: conditional(email_verified && email),
name,
};
} catch (error: unknown) {
assert(
!(error instanceof GotRequestError && error.response?.statusCode === 401),
new ConnectorError(ConnectorErrorCodes.SocialAccessTokenInvalid)
);
throw error;
}
};
private readonly authorizationCallbackHandler = async (parameterObject: unknown) => {
const result = codeWithRedirectDataGuard.safeParse(parameterObject);
if (!result.success) {
throw new ConnectorError(ConnectorErrorCodes.General, JSON.stringify(parameterObject));
}
return result.data;
};
}