blob: 05ed6a877d1893779e075bbe54e85bde4d7bfb9c [file] [log] [blame]
// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'dart:convert';
import 'package:google_identity_services_web/id.dart';
import 'package:google_identity_services_web/oauth2.dart';
import 'package:google_sign_in_platform_interface/google_sign_in_platform_interface.dart';
/// A codec that can encode/decode JWT payloads.
///
/// See https://www.rfc-editor.org/rfc/rfc7519#section-3
final Codec<Object?, String> jwtCodec = json.fuse(utf8).fuse(base64);
/// A RegExp that can match, and extract parts from a JWT Token.
///
/// A JWT token consists of 3 base-64 encoded parts of data separated by periods:
///
/// header.payload.signature
///
/// More info: https://regexr.com/789qc
final RegExp jwtTokenRegexp = RegExp(
r'^(?<header>[^\.\s]+)\.(?<payload>[^\.\s]+)\.(?<signature>[^\.\s]+)$');
/// Decodes the `claims` of a JWT token and returns them as a Map.
///
/// JWT `claims` are stored as a JSON object in the `payload` part of the token.
///
/// (This method does not validate the signature of the token.)
///
/// See https://www.rfc-editor.org/rfc/rfc7519#section-3
Map<String, Object?>? getJwtTokenPayload(String? token) {
if (token != null) {
final RegExpMatch? match = jwtTokenRegexp.firstMatch(token);
if (match != null) {
return decodeJwtPayload(match.namedGroup('payload'));
}
}
return null;
}
/// Decodes a JWT payload using the [jwtCodec].
Map<String, Object?>? decodeJwtPayload(String? payload) {
try {
// Payload must be normalized before passing it to the codec
return jwtCodec.decode(base64.normalize(payload!)) as Map<String, Object?>?;
} catch (_) {
// Do nothing, we always return null for any failure.
}
return null;
}
/// Returns the payload of a [CredentialResponse].
Map<String, Object?>? getResponsePayload(CredentialResponse? response) {
if (response?.credential == null) {
return null;
}
return getJwtTokenPayload(response!.credential);
}
/// Converts a [CredentialResponse] into a [GoogleSignInUserData].
///
/// May return `null`, if the `credentialResponse` is null, or its `credential`
/// cannot be decoded.
GoogleSignInUserData? gisResponsesToUserData(
CredentialResponse? credentialResponse) {
final Map<String, Object?>? payload = getResponsePayload(credentialResponse);
if (payload == null) {
return null;
}
assert(credentialResponse?.credential != null,
'The CredentialResponse cannot be null and have a payload.');
return GoogleSignInUserData(
email: payload['email']! as String,
id: payload['sub']! as String,
displayName: payload['name'] as String?,
photoUrl: payload['picture'] as String?,
idToken: credentialResponse!.credential,
);
}
/// Returns the expiration timestamp ('exp') of a [CredentialResponse].
///
/// May return `null` if the `credentialResponse` is null, its `credential`
/// cannot be decoded, or the `exp` field is not set on the JWT payload.
DateTime? getCredentialResponseExpirationTimestamp(
CredentialResponse? credentialResponse) {
final Map<String, Object?>? payload = getResponsePayload(credentialResponse);
// Get the 'exp' field from the payload, if present.
final int? exp = (payload != null) ? payload['exp'] as int? : null;
// Return 'exp' (a timestamp in seconds since Epoch) as a DateTime.
return (exp != null) ? DateTime.fromMillisecondsSinceEpoch(exp * 1000) : null;
}
/// Converts responses from the GIS library into TokenData for the plugin.
GoogleSignInTokenData gisResponsesToTokenData(
CredentialResponse? credentialResponse,
TokenResponse? tokenResponse, [
CodeResponse? codeResponse,
]) {
return GoogleSignInTokenData(
idToken: credentialResponse?.credential,
accessToken: tokenResponse?.access_token,
serverAuthCode: codeResponse?.code,
);
}