iT邦幫忙

2023 iThome 鐵人賽

DAY 25
0

前言

  • 在本系列的 Day 23 有提到上傳檔案,而預設的放法是無法滿足多使用者上傳同名魚的場景。
  • 這邊會說明如何調整 S3 內的資料放法,使得可以滿足多使用者、同名魚的問題。

本文大綱

  1. 如何辨識使用者
  2. 如何進行登出原始碼撰寫
  3. 如何調整 S3 的結構

辨識使用者

  • 參考資料:https://docs.amplify.aws/lib/auth/user-attributes/q/platform/ios/#resend-verification-code
  • 在開發完登入的功能後,可以在連線沒有中斷前,呼叫 Amplify.Auth.fetchUserAttributes 以取得使用者的登入狀態 (見下圖)
    https://ithelp.ithome.com.tw/upload/images/20230928/20130149AHnMmhwSJM.png
  • 目前使用的是使用者的 Email 作為使用者名稱

撰寫取得登入資訊的函式

func fetchUserAttr() async -> [String:String] {
    var userAttr: [String:String] = [:]
    do {
        let curAuthMsg = try await Amplify.Auth.fetchUserAttributes()
        for attr in curAuthMsg {
            userAttr[attr.key.rawValue] = attr.value
        }
    } catch {
        print("Failed to fetch user attributes")
    }
    return userAttr
}
  • 特別需要注意的一點
    • Amplify.Auth.fetchUserAttributes() 拿回來的是一個 Array,裡面由 Amplify.AuthUserAttributes 所構成
    • Amplify.AuthUserAttribute 包含了 Key 和 Value
    • Key 的資料型別 不是 String,而是 Amplify.AuthUserAttributeKey
    • 這邊我直覺用 String() 包 key 是行不通的,所以才會用到 key.rawValue

Auth

Event 有哪幾種

  • 官方提供的 case
  • 從這些狀態可以知道光是要做 Auth 功能,會有哪幾種動作
import { Hub, Logger } from 'aws-amplify';

const logger = new Logger('My-Logger');

const listener = (data) => {
  switch (data?.payload?.event) {
    case 'configured':
      logger.info('the Auth module is configured');
      break;
    case 'signIn':
      logger.info('user signed in');
      break;
    case 'signIn_failure':
      logger.error('user sign in failed');
      break;
    case 'signUp':
      logger.info('user signed up');
      break;
    case 'signUp_failure':
      logger.error('user sign up failed');
      break;
    case 'confirmSignUp':
      logger.info('user confirmation successful');
      break;
    case 'completeNewPassword_failure':
      logger.error('user did not complete new password flow');
      break;
    case 'autoSignIn':
      logger.info('auto sign in successful');
      break;
    case 'autoSignIn_failure':
      logger.error('auto sign in failed');
      break;
    case 'forgotPassword':
      logger.info('password recovery initiated');
      break;
    case 'forgotPassword_failure':
      logger.error('password recovery failed');
      break;
    case 'forgotPasswordSubmit':
      logger.info('password confirmation successful');
      break;
    case 'forgotPasswordSubmit_failure':
      logger.error('password confirmation failed');
      break;
    case 'verify':
      logger.info('TOTP token verification successful');
      break;
    case 'tokenRefresh':
      logger.info('token refresh succeeded');
      break;
    case 'tokenRefresh_failure':
      logger.error('token refresh failed');
      break;
    case 'cognitoHostedUI':
      logger.info('Cognito Hosted UI sign in successful');
      break;
    case 'cognitoHostedUI_failure':
      logger.error('Cognito Hosted UI sign in failed');
      break;
    case 'customOAuthState':
      logger.info('custom state returned from CognitoHosted UI');
      break;
    case 'customState_failure':
      logger.error('custom state failure');
      break;
    case 'parsingCallbackUrl':
      logger.info('Cognito Hosted UI OAuth url parsing initiated');
      break;
    case 'userDeleted':
      logger.info('user deletion successful');
      break;
    case 'updateUserAttributes':
      logger.info('user attributes update successful');
      break;
    case 'updateUserAttributes_failure':
      logger.info('user attributes update failed');
      break;
    case 'signOut':
      logger.info('user signed out');
      break;
    default:
      logger.info('unknown event type');
      break;
  }
};

Hub.listen('auth', listener);
  • 看了這一堆,要做登出的話,望文生義,直接看就是 SignOut 這一項
  • 去參考官方給的代碼後,開始下方設計

開發登出功能

  • 截至目前為止,設計在 APP 打開的時候 BaoAnGongFishingApp.swift 先判斷有無登入,沒登入的話會顯示註冊/登入畫面 (AuthView.swift),在登入後才會跳去 MainListView.swift (底下) + 圖鑑 (上部)。
  • 而使用者可能會透過底下 MainListView 切換上部分頁,所以我想在多新增一個設定功能的分頁。
  • 在切換到有登出按鈕的分頁的 View 內,加入登出函式。
func signOutLocally() async {
    let result = await Amplify.Auth.signOut()
    guard let signOutResult = result as? AWSCognitoSignOutResult
    else {
        print("Signout failed")
        return
    }

    // Return Login View
    print("Log out ~~")
    self.isSignOut = true

    print("Local signout successful: \(signOutResult.signedOutLocally)")
    switch signOutResult {
    case .complete:
        // Sign Out completed fully and without errors.
        print("Signed out successfully")

    case let .partial(revokeTokenError, globalSignOutError, hostedUIError):
        // Sign Out completed with some errors. User is signed out of the device.

        if let hostedUIError = hostedUIError {
            print("HostedUI error  \(String(describing: hostedUIError))")
        }

        if let globalSignOutError = globalSignOutError {
            // Optional: Use escape hatch to retry revocation of globalSignOutError.accessToken.
            print("GlobalSignOut error  \(String(describing: globalSignOutError))")
        }

        if let revokeTokenError = revokeTokenError {
            // Optional: Use escape hatch to retry revocation of revokeTokenError.accessToken.
            print("Revoke token error  \(String(describing: revokeTokenError))")
        }

    case .failed(let error):
        // Sign Out failed with an exception, leaving the user signed in.
        print("SignOut failed with \(error)")
    }
}

調整 S3 結構

  • 在相片五部曲之四,討論了上傳圖片,光是亂丟一通是不夠的!!馬上在五部曲之五就吃鱉了
  • 所以在開發五部曲之五(有效率地取回「會員曾經上傳過」的圖片前),要先搞定圖床的樹狀結構
func uploadStamp() async {
    print("Sync local pic -> Amplify S3")
    let attrs = await fetchUserAttr()
    let username = attrs["name"] ?? "guest"
    for picUrl in uploadFilenameQueue {
        let fileNameKey = picUrl.lastPathComponent
        print("Upload -- \(fileNameKey)")
        let uploadTask = Amplify.Storage.uploadFile(
            key: "\(username)/\(fileNameKey)",
            local: picUrl
        )
        //Task {
        //    for await progress in await uploadTask.progress {
        //        print("Progress: \(progress)")
        //    }
        //}
        do {
            let data = try await uploadTask.value
            print("Completed: \(data)")
        } catch {
            print("Couldn't upload file: \(picUrl)")
        }
    }
    print("Clean queue")
    uploadFilenameQueue.removeAll()
}
  • 在上傳行為開始的時候,先確認登入的使用者是誰,沒有的話就先叫他 guest
  • 在呼叫 Amplify.Storage.uploadFile 的參數, key 值加入使用者的名稱(這邊 user name 是 user 的 email)
  • 寫好了,來看結果!!

成果

  • 以下是上傳我今天的午餐「滑蛋蝦仁」魚
    https://ithelp.ithome.com.tw/upload/images/20230928/201301497KTEVkOBUF.jpg
  • 上傳的 log ...
    https://ithelp.ithome.com.tw/upload/images/20230928/20130149yada2UB5om.png
  • 在 S3 Bucket 裡面的樣子會是 ...
    https://ithelp.ithome.com.tw/upload/images/20230928/20130149iluFaE45O5.png
  • 可以看出擺放的路徑,多了「信箱」這樣的唯一可識別出使用者差異的值

參考資料

  1. https://docs.amplify.aws/lib/auth/getting-started/q/platform/ios/
  2. https://docs.amplify.aws/lib/auth/guest_access/q/platform/ios/
  3. https://docs.amplify.aws/lib/auth/auth-events/q/platform/js/
  4. https://developer.apple.com/documentation/swift/dictionary

上一篇
【Day 24】 相片五部曲之五:從 S3 拿回圖檔
下一篇
【Day 26】 Storage 基礎設施 S3 裡的安全措施
系列文
依然無法成為釣魚大師也要努力摸魚!!辣個吃魚神器 APP 第二彈33
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言