Просмотр исходного кода

Updated sample app to support auth_time (#555)

AkshatGandhi 6 месяцев назад
Родитель
Сommit
40b969295b

+ 7 - 2
Samples/Swift/DaysUntilBirthday/Shared/Services/GoogleSignInAuthenticator.swift

@@ -20,6 +20,7 @@ import GoogleSignIn
 /// An observable class for authenticating via Google.
 final class GoogleSignInAuthenticator: ObservableObject {
   private var authViewModel: AuthenticationViewModel
+  private var tokenClaims: Set<GIDTokenClaim> = Set([GIDTokenClaim.authTime()])
 
   /// Creates an instance of this authenticator.
   /// - parameter authViewModel: The view model this authenticator will set logged in status on.
@@ -41,7 +42,8 @@ final class GoogleSignInAuthenticator: ObservableObject {
       withPresenting: rootViewController,
       hint: nil,
       additionalScopes: nil,
-      nonce: manualNonce
+      nonce: manualNonce,
+      tokenClaims: tokenClaims
     ) { signInResult, error in
       guard let signInResult = signInResult else {
         print("Error! \(String(describing: error))")
@@ -66,7 +68,10 @@ final class GoogleSignInAuthenticator: ObservableObject {
       return
     }
 
-    GIDSignIn.sharedInstance.signIn(withPresenting: presentingWindow) { signInResult, error in
+    GIDSignIn.sharedInstance.signIn(
+      withPresenting: presentingWindow,
+      tokenClaims: tokenClaims
+    ) { signInResult, error in
       guard let signInResult = signInResult else {
         print("Error! \(String(describing: error))")
         return

+ 54 - 0
Samples/Swift/DaysUntilBirthday/Shared/ViewModels/AuthenticationViewModel.swift

@@ -25,6 +25,19 @@ final class AuthenticationViewModel: ObservableObject {
   private var authenticator: GoogleSignInAuthenticator {
     return GoogleSignInAuthenticator(authViewModel: self)
   }
+
+  /// The user's `auth_time` as found in `idToken`.
+  /// - note: If the user is logged out, then this will default to `nil`.
+  var authTime: Date? {
+    switch state {
+    case .signedIn(let user):
+      guard let idToken = user.idToken?.tokenString else { return nil }
+      return decodeAuthTime(fromJWT: idToken)
+    case .signedOut:
+      return nil
+    }
+  }
+
   /// The user-authorized scopes.
   /// - note: If the user is logged out, then this will default to empty.
   var authorizedScopes: [String] {
@@ -69,7 +82,48 @@ final class AuthenticationViewModel: ObservableObject {
   @MainActor func addBirthdayReadScope(completion: @escaping () -> Void) {
       authenticator.addBirthdayReadScope(completion: completion)
   }
+  
+  var formattedAuthTimeString: String? {
+    guard let date = authTime else { return nil }
+    let formatter = DateFormatter()
+    formatter.dateFormat = "MMM d, yyyy 'at' h:mm a"
+    return formatter.string(from: date)
+  }
+}
 
+private extension AuthenticationViewModel {
+  func decodeAuthTime(fromJWT jwt: String) -> Date? {
+    let segments = jwt.components(separatedBy: ".")
+    guard let parts = decodeJWTSegment(segments[1]),
+          let authTimeInterval = parts["auth_time"] as? TimeInterval else {
+      return nil
+    }
+    return Date(timeIntervalSince1970: authTimeInterval)
+  }
+
+  func decodeJWTSegment(_ segment: String) -> [String: Any]? {
+    guard let segmentData = base64UrlDecode(segment),
+          let segmentJSON = try? JSONSerialization.jsonObject(with: segmentData, options: []),
+          let payload = segmentJSON as? [String: Any] else {
+      return nil
+    }
+    return payload
+  }
+  
+  func base64UrlDecode(_ value: String) -> Data? {
+    var base64 = value
+      .replacingOccurrences(of: "-", with: "+")
+      .replacingOccurrences(of: "_", with: "/")
+
+    let length = Double(base64.lengthOfBytes(using: String.Encoding.utf8))
+    let requiredLength = 4 * ceil(length / 4.0)
+    let paddingLength = requiredLength - length
+    if paddingLength > 0 {
+      let padding = "".padding(toLength: Int(paddingLength), withPad: "=", startingAt: 0)
+      base64 = base64 + padding
+    }
+    return Data(base64Encoded: base64, options: .ignoreUnknownCharacters)
+  }
 }
 
 extension AuthenticationViewModel {

+ 3 - 0
Samples/Swift/DaysUntilBirthday/iOS/UserProfileView.swift

@@ -35,6 +35,9 @@ struct UserProfileView: View {
               Text(userProfile.name)
                 .font(.headline)
               Text(userProfile.email)
+              if let authTimeString = authViewModel.formattedAuthTimeString {
+                Text("Last sign-in date: \(authTimeString)")
+              }
             }
           }
           NavigationLink(NSLocalizedString("View Days Until Birthday", comment: "View birthday days"),

+ 3 - 0
Samples/Swift/DaysUntilBirthday/macOS/UserProfileView.swift

@@ -19,6 +19,9 @@ struct UserProfileView: View {
               Text(userProfile.name)
                 .font(.headline)
               Text(userProfile.email)
+              if let authTimeString = authViewModel.formattedAuthTimeString {
+                Text("Last sign-in date: \(authTimeString)")
+              }
             }
           }
           Button(NSLocalizedString("Sign Out", comment: "Sign out button"), action: signOut)