11// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
22// SPDX-License-Identifier: Apache-2.0
33
4+ // snippet-start:[siwa-viewmodel.swift.imports]
45import SwiftUI
56import AuthenticationServices
7+ import SimpleKeychain
68
79// Import the AWS SDK for Swift modules the app requires.
8- //
9- // snippet-start:[siwa-viewmodel.swift.imports]
1010import AWSS3
1111import AWSSDKIdentity
1212// snippet-end:[siwa-viewmodel.swift.imports]
@@ -34,49 +34,53 @@ class ViewModel: ObservableObject {
3434 /// This is only returned by SIWA if the user has just created
3535 /// the app's SIWA account link. Otherwise, it's returned as `nil`
3636 /// by SIWA and must be retrieved from local storage if needed.
37- @ AppStorage ( " email " ) var email = " "
37+ var email = " "
3838
3939 /// The user's family (last) name.
4040 ///
4141 /// This is only returned by SIWA if the user has just created
4242 /// the app's SIWA account link. Otherwise, it's returned as `nil`
4343 /// by SIWA and must be retrieved from local storage if needed.
44- @ AppStorage ( " family-name " ) var familyName = " "
45-
44+ var familyName = " "
45+
4646 /// The user's given (first) name.
4747 ///
4848 /// This is only returned by SIWA if the user has just created
4949 /// the app's SIWA account link. Otherwise, it's returned as `nil` by SIWA
5050 /// and must be retrieved from local storage if needed.
51- @ AppStorage ( " given-name " ) var givenName = " "
52-
51+ var givenName = " "
52+
5353 /// The AWS account number provided by the user.
54- @ AppStorage ( " account-number " ) var awsAccountNumber = " "
54+ var awsAccountNumber = " "
5555
5656 /// The AWS IAM role name given by the user.
57- @ AppStorage ( " iam-role " ) var awsIAMRoleName = " "
58-
57+ var awsIAMRoleName = " "
58+
5959 /// The credential identity resolver created by the AWS SDK for
6060 /// Swift. This resolves temporary credentials using
6161 /// `AssumeRoleWithWebIdentity`.
6262 var identityResolver : STSWebIdentityAWSCredentialIdentityResolver ? = nil
6363 // snippet-end:[siwa-auth-properties.swift]
64-
64+
6565 //****** The var iables below this point aren't involved in Sign in With
6666 //****** Apple or AWS authentication.
67-
67+
6868 /// An array of the user's bucket names.
6969 ///
7070 /// This is filled out once the user is signed into AWS.
7171 @Published var bucketList : [ IDString] = [ ]
72-
72+
7373 /// An error string to present in an alert panel if needed. If this is
7474 /// `nil`, no error alert is presented.
7575 @Published var error : Swift . Error ?
7676
7777 /// The title of the error alert's close button.
7878 @Published var errorButtonTitle : String = " Continue "
79-
79+
80+ init ( ) {
81+ readUserData ( )
82+ }
83+
8084 // MARK: - Authentication
8185
8286 // snippet-start:[siwa-handle.swift]
@@ -102,13 +106,13 @@ class ViewModel: ObservableObject {
102106 else {
103107 throw BucketsAppError . credentialsIncomplete
104108 }
105-
109+
106110 userID = credential. user
107-
111+
108112 // If the email field has a value, set the user's recorded email
109113 // address. Otherwise, keep the existing one.
110114 email = credential. email ?? self . email
111-
115+
112116 // Similarly, if the name is present in the credentials, use it.
113117 // Otherwise, the last known name is retained.
114118 if let name = credential. fullName {
@@ -140,7 +144,7 @@ class ViewModel: ObservableObject {
140144 }
141145 }
142146 // snippet-end:[siwa-handle.swift]
143-
147+
144148 // snippet-start:[siwa-authenticate.swift]
145149 /// Convert the given JWT identity token string into the temporary
146150 /// AWS credentials needed to allow this application to operate, as
@@ -170,13 +174,12 @@ class ViewModel: ObservableObject {
170174 do {
171175 try tokenString. write ( to: tokenFileURL, atomically: true , encoding: . utf8)
172176 } catch {
173- //print("Error writing token to disk: \(error)")
174177 throw BucketsAppError . tokenFileError ( )
175178 }
176179
177180 // Create an identity resolver that uses the JWT token received
178181 // from Apple to create AWS credentials.
179-
182+
180183 do {
181184 identityResolver = try STSWebIdentityAWSCredentialIdentityResolver (
182185 region: region,
@@ -187,9 +190,14 @@ class ViewModel: ObservableObject {
187190 } catch {
188191 throw BucketsAppError . assumeRoleFailed
189192 }
193+
194+ // Save the user's data securely to local storage so it's available
195+ // in the future.
196+
197+ saveUserData ( )
190198 }
191199 // snippet-end:[siwa-authenticate.swift]
192-
200+
193201 // snippet-start:[siwa-sign-out.swift]
194202 /// "Sign out" of the user's account.
195203 ///
@@ -218,7 +226,7 @@ class ViewModel: ObservableObject {
218226 return userID != " "
219227 }
220228 // snippet-end:[siwa-check-signed-in.swift]
221-
229+
222230 // MARK: - AWS access
223231 // snippet-start:[siwa-use-credentials.swift]
224232 /// Fetches a list of the user's Amazon S3 buckets.
@@ -230,11 +238,11 @@ class ViewModel: ObservableObject {
230238 // credential identity resolver created from the JWT token
231239 // returned by Sign In With Apple.
232240 let config = try await S3Client . S3ClientConfiguration (
233- awsCredentialIdentityResolver: identityResolver,
234- region: " us-east-1 "
241+ awsCredentialIdentityResolver: identityResolver,
242+ region: " us-east-1 "
235243 )
236244 let s3 = S3Client ( config: config)
237-
245+
238246 let output = try await s3. listBuckets (
239247 input: ListBucketsInput ( )
240248 )
@@ -251,7 +259,7 @@ class ViewModel: ObservableObject {
251259 }
252260 }
253261 // snippet-end:[siwa-use-credentials.swift]
254-
262+
255263 // MARK: - Token file management
256264
257265 // snippet-start:[siwa-create-token-file.swift]
@@ -264,7 +272,7 @@ class ViewModel: ObservableObject {
264272 return tempDirURL. appendingPathComponent ( " example-siwa-token.jwt " )
265273 }
266274 // snippet-end:[siwa-create-token-file.swift]
267-
275+
268276 // snippet-start:[siwa-delete-token-file.swift]
269277 /// Delete the local JWT token file.
270278 func deleteTokenFile( ) throws {
@@ -275,4 +283,81 @@ class ViewModel: ObservableObject {
275283 }
276284 }
277285 // snippet-end:[siwa-delete-token-file.swift]
286+
287+
288+ // MARK: - Secure storage
289+
290+ let KEY_USER_ID = " buckets-user-id "
291+ let KEY_USER_EMAIL = " buckets-user-email "
292+ let KEY_USER_NAME_GIVEN = " buckets-user-given-name "
293+ let KEY_USER_NAME_FAMILY = " buckets-user-family-name "
294+ let KEY_AWS_ACCOUNT = " buckets-aws-account "
295+ let KEY_IAM_ROLE = " buckets-iam-role "
296+
297+ /// Securely save user and account information to the Keychain.
298+ func saveUserData( ) {
299+ // Create a `SimpleKeychain` object to use for Keychain access. The
300+ // default service name (the bundle ID) is used.
301+ let simpleKeychain = SimpleKeychain ( )
302+
303+ do {
304+ //try simpleKeychain.set(userID, forKey: KEY_USER_ID)
305+ try simpleKeychain. set ( email, forKey: KEY_USER_EMAIL)
306+ try simpleKeychain. set ( givenName, forKey: KEY_USER_NAME_GIVEN)
307+ try simpleKeychain. set ( familyName, forKey: KEY_USER_NAME_FAMILY)
308+ try simpleKeychain. set ( awsAccountNumber, forKey: KEY_AWS_ACCOUNT)
309+ try simpleKeychain. set ( awsIAMRoleName, forKey: KEY_IAM_ROLE)
310+ } catch {
311+ // The way this example is written, if the data doesn't get saved,
312+ // it just means it won't be available later. So silently log
313+ // the problem and continue.
314+ print ( " Unable to save user data to keychain. " )
315+ }
316+ }
317+
318+ /// Read user data from the keychain.
319+ func readUserData( ) {
320+ // Create a `SimpleKeychain` object to use for Keychain access. The
321+ // default service name (the bundle ID) is used. Any item that isn't
322+ // found is set to an empty string.
323+ let simpleKeychain = SimpleKeychain ( )
324+
325+ /*
326+ do {
327+ userID = try simpleKeychain.string(forKey: KEY_USER_ID)
328+ } catch {
329+ userID = ""
330+ }
331+ */
332+
333+ do {
334+ email = try simpleKeychain. string ( forKey: KEY_USER_EMAIL)
335+ } catch {
336+ email = " "
337+ }
338+
339+ do {
340+ givenName = try simpleKeychain. string ( forKey: KEY_USER_NAME_GIVEN)
341+ } catch {
342+ givenName = " "
343+ }
344+
345+ do {
346+ familyName = try simpleKeychain. string ( forKey: KEY_USER_NAME_FAMILY)
347+ } catch {
348+ familyName = " "
349+ }
350+
351+ do {
352+ awsAccountNumber = try simpleKeychain. string ( forKey: KEY_AWS_ACCOUNT)
353+ } catch {
354+ awsAccountNumber = " "
355+ }
356+
357+ do {
358+ awsIAMRoleName = try simpleKeychain. string ( forKey: KEY_IAM_ROLE)
359+ } catch {
360+ awsIAMRoleName = " "
361+ }
362+ }
278363}
0 commit comments