0
Fork 0
mirror of https://github.com/logto-io/logto.git synced 2025-02-24 22:05:56 -05:00
logto/packages/connector-google/src/index.ts

120 lines
3.3 KiB
TypeScript
Raw Normal View History

/**
* The Implementation of OpenID Connect of Google Identity Platform.
* https://developers.google.com/identity/protocols/oauth2/openid-connect
*/
import {
ConnectorError,
ConnectorErrorCodes,
GetAccessToken,
GetAuthorizationUri,
GetUserInfo,
ConnectorMetadata,
ValidateConfig,
SocialConnector,
GetConnectorConfig,
} 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, AccessTokenResponse, GoogleConfig, UserInfoResponse } from './types';
export class GoogleConnector implements SocialConnector {
public metadata: ConnectorMetadata = defaultMetadata;
public readonly getConfig: GetConnectorConfig<GoogleConfig>;
constructor(getConnectorConfig: GetConnectorConfig<GoogleConfig>) {
this.getConfig = getConnectorConfig;
}
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 (redirectUri, state) => {
const config = await this.getConfig(this.metadata.target, this.metadata.platform);
const queryParameters = new URLSearchParams({
client_id: config.clientId,
redirect_uri: redirectUri,
response_type: 'code',
state,
scope,
});
return `${authorizationEndpoint}?${queryParameters.toString()}`;
};
public getAccessToken: GetAccessToken = async (code, redirectUri) => {
const { clientId, clientSecret } = await this.getConfig(
this.metadata.target,
this.metadata.platform
);
// NoteNeed to decodeURIComponent on code
// https://stackoverflow.com/questions/51058256/google-api-node-js-invalid-grant-malformed-auth-code
const { access_token: accessToken } = await got
.post(accessTokenEndpoint, {
form: {
code: decodeURIComponent(code),
client_id: clientId,
client_secret: clientSecret,
redirect_uri: redirectUri,
grant_type: 'authorization_code',
},
timeout: defaultTimeout,
followRedirect: true,
})
.json<AccessTokenResponse>();
assert(accessToken, new ConnectorError(ConnectorErrorCodes.SocialAuthCodeInvalid));
return { accessToken };
};
public getUserInfo: GetUserInfo = async (accessTokenObject) => {
const { accessToken } = accessTokenObject;
try {
const {
sub: id,
picture: avatar,
email,
email_verified,
name,
} = await got
.post(userInfoEndpoint, {
headers: {
authorization: `Bearer ${accessToken}`,
},
timeout: defaultTimeout,
})
.json<UserInfoResponse>();
return {
id,
avatar,
email: conditional(email_verified && email),
name,
};
} catch (error: unknown) {
if (error instanceof GotRequestError && error.response?.statusCode === 401) {
throw new ConnectorError(ConnectorErrorCodes.SocialAccessTokenInvalid);
}
throw error;
}
};
}