thiskevinwang/coffee-code-climb

View on GitHub
src/utils/Playground/useCognito.ts

Summary

Maintainability
D
2 days
Test Coverage
import { useCallback } from "react"
import { useDispatch, useSelector } from "react-redux"
import _ from "lodash"
import type { CognitoIdentityServiceProvider } from "aws-sdk"
import type { Dispatch } from "redux"

import { setCognito, RootState } from "_reduxState"

import { AWS, cognito } from "./AWS"

const CLIENT_ID = process.env.GATSBY_COGNITO_CLIENT_ID as string
const USER_POOL_ID = process.env.GATSBY_USER_POOL_ID as string

const makeSignUpWithEmail = (rdxDispatch: Dispatch) => async (
  email: string,
  password: string
) => {
  console.log("cogSignUpWithEmail")

  const params: CognitoIdentityServiceProvider.SignUpRequest = {
    ClientId: CLIENT_ID,
    Username: email,
    Password: password,
    UserAttributes: [
      {
        Name: "email",
        Value: email,
      },
    ],
  }

  try {
    const data = await cognito.signUp(params).promise()
    rdxDispatch(setCognito(data, null))
  } catch (err) {
    rdxDispatch(setCognito(null, err))
    throw err
  }

  console.log("\tend")
}

const makeConfirmSignUp = (rdxDispatch: Dispatch) => async (
  email: string,
  confirmationCode: string
) => {
  console.log("cogConfirmSignUp")

  const params: CognitoIdentityServiceProvider.ConfirmSignUpRequest = {
    ClientId: CLIENT_ID,
    Username: email,
    ConfirmationCode: confirmationCode,
  }

  try {
    const data = await cognito.confirmSignUp(params).promise()
  } catch (err) {
    rdxDispatch(setCognito(null, err))
  }

  console.log("\tend")
}

const makeInitateAuth = (rdxDispatch: Dispatch) => async (
  email: string,
  password: string
) => {
  console.log("cogInitateAuth")

  const params: CognitoIdentityServiceProvider.InitiateAuthRequest = {
    AuthFlow: "USER_PASSWORD_AUTH",
    ClientId: CLIENT_ID,
    AuthParameters: {
      USERNAME: email,
      PASSWORD: password,
    },
  }

  try {
    const data = await cognito.initiateAuth(params).promise()
    rdxDispatch(setCognito(data, null))
    return data
  } catch (err) {
    rdxDispatch(setCognito(null, err))
    throw err
  } finally {
    console.log("\tend")
  }
}

const makeInitateAuthForRefreshToken = (rdxDispatch: Dispatch) => async (
  email: string,
  refreshToken: string
) => {
  console.log("cogInitateAuth-RefreshToken")

  const params: CognitoIdentityServiceProvider.InitiateAuthRequest = {
    AuthFlow: "REFRESH_TOKEN",
    ClientId: CLIENT_ID,
    AuthParameters: {
      USERNAME: email,
      REFRESH_TOKEN: refreshToken,
    },
  }

  try {
    const data = await cognito.initiateAuth(params).promise()
    rdxDispatch(setCognito(data, null))
  } catch (err) {
    rdxDispatch(setCognito(null, err))
  }
  console.log("\tend")
}

const makeInitateAuthWithFacebook = (rdxDispatch: Dispatch) => async () => {
  return alert("FIXME")

  FB.login(function (response) {
    // Check if the user logged in successfully.
    if (response.authResponse) {
      console.log("You are now logged in.")
      console.log(response.authResponse.accessToken)
      // Add the Facebook access token to the Amazon Cognito credentials login map.
      AWS.config.credentials = new AWS.CognitoIdentityCredentials({
        IdentityPoolId: process.env.GATSBY_IDENTITY_POOL_ID as string,
        Logins: {
          "graph.facebook.com": response.authResponse.accessToken,
        },
      })

      // Obtain AWS credentials
      AWS.config.credentials.get(async (err) => {
        console.log("Error?:", err)

        const accessKeyId = AWS.config.credentials.accessKeyId
        const secretAccessKey = AWS.config.credentials.secretAccessKey
        const sessionToken = AWS.config.credentials.sessionToken
        const identityId = AWS.config.credentials.identityId

        console.log({ accessKeyId, secretAccessKey, sessionToken, identityId })

        // FB.api("/me", { fields: "email" }, async (response) => {
        //   console.log("me:", response)
        // })
      })
    } else {
      console.log("There was a problem logging you in.")
    }
  })
}

const makeAdminLinkProviderForUser = (rdxDispatch: Dispatch) => async (
  email: string
) => {
  return alert("FIXME")
  FB.login(function (response) {
    // Check if the user logged in successfully.
    if (response.authResponse) {
      console.log("You are now logged in.")
      console.log(response.authResponse.accessToken)
      // Add the Facebook access token to the Amazon Cognito credentials login map.
      AWS.config.credentials = new AWS.CognitoIdentityCredentials({
        IdentityPoolId: process.env.GATSBY_IDENTITY_POOL_ID as string,
        Logins: {
          "graph.facebook.com": response.authResponse.accessToken,
        },
      })

      // Obtain AWS credentials
      AWS.config.credentials.get(async (err) => {
        console.log("Error?:", err)

        const accessKeyId = AWS.config.credentials.accessKeyId
        const secretAccessKey = AWS.config.credentials.secretAccessKey
        const sessionToken = AWS.config.credentials.sessionToken
        const identityId = AWS.config.credentials.identityId

        console.log({ accessKeyId, secretAccessKey, sessionToken, identityId })
        const cog = new AWS.CognitoIdentityServiceProvider()

        const params: CognitoIdentityServiceProvider.AdminLinkProviderForUserRequest = {
          UserPoolId: USER_POOL_ID,
          DestinationUser: {
            ProviderName: "Cognito",
            // ProviderAttributeName: "", // --ignored
            ProviderAttributeValue: email,
          },
          SourceUser: {
            ProviderName: "Facebook",
            ProviderAttributeName: "Cognito_Subject",
            // ProviderAttributeValue: "",
          },
        }
        await cog.adminLinkProviderForUser(params).promise()
        // FB.api("/me", { fields: "email" }, async (response) => {
        //   console.log("me:", response)
        // })
      })
    } else {
      console.log("There was a problem logging you in.")
    }
  })
}

const makeForgotPassword = (rdxDispatch: Dispatch) => async (email: string) => {
  const params: CognitoIdentityServiceProvider.ForgotPasswordRequest = {
    ClientId: CLIENT_ID,
    Username: email,
  }

  try {
    const data = await cognito.forgotPassword(params).promise()
    rdxDispatch(setCognito(data, null))
  } catch (err) {
    rdxDispatch(setCognito(null, err))
  }
}

const makeConfirmForgotPassword = (rdxDispatch: Dispatch) => async (
  email: string,
  password: string,
  code: string
) => {
  const params: CognitoIdentityServiceProvider.ConfirmForgotPasswordRequest = {
    ClientId: CLIENT_ID,
    Username: email,
    Password: password,
    ConfirmationCode: code,
  }

  try {
    const data = await cognito.confirmForgotPassword(params).promise()
    rdxDispatch(setCognito(data, null))
  } catch (err) {
    rdxDispatch(setCognito(null, err))
  }
}

const makeInitateAuthCustom = (rdxDispatch: Dispatch) => async (
  email: string
) => {
  console.log("cogInitateAuthCustom")

  const params: CognitoIdentityServiceProvider.InitiateAuthRequest = {
    AuthFlow: "CUSTOM_AUTH",
    ClientId: CLIENT_ID,
    AuthParameters: {
      USERNAME: email,
    },
  }

  try {
    const data = await cognito.initiateAuth(params).promise()
    rdxDispatch(setCognito(data, null))
    return data
  } catch (err) {
    rdxDispatch(setCognito(null, err))
    throw err
  } finally {
    console.log("\tend")
  }
}

/**
 * Don't use this. It works but I haven't figured out an easy
 * way to guarantee attribute uniques, unless I "admin-query"
 * or list all the users first, and check their usernames.
 */
const makeUpdatePreferredUsername = (rdxDispatch: Dispatch) => async (
  accessToken: string,
  refreshToken: string,
  preferredUsername: string,
  email: string
) => {
  console.log("cogUpdateUserAttributes")

  const updateUserAttributesParams: CognitoIdentityServiceProvider.UpdateUserAttributesRequest = {
    AccessToken: accessToken,
    UserAttributes: [{ Name: "preferred_username", Value: preferredUsername }],
  }

  try {
    const data = await cognito
      .updateUserAttributes(updateUserAttributesParams)
      .promise()

    const initiateAuthParams: CognitoIdentityServiceProvider.InitiateAuthRequest = {
      AuthFlow: "REFRESH_TOKEN",
      ClientId: CLIENT_ID,
      AuthParameters: {
        USERNAME: email,
        REFRESH_TOKEN: refreshToken,
      },
    }

    try {
      const data2 = await cognito.initiateAuth(initiateAuthParams).promise()
      // copy over current refreshToken because the 'REFRESH_TOKEN' flow will not return a new one

      const newTokenSet = {
        ...data2,
        AuthenticationResult: {
          ...data2.AuthenticationResult,
          RefreshToken: refreshToken,
        },
      }
      rdxDispatch(setCognito(newTokenSet, null))
    } catch (err2) {
      rdxDispatch(setCognito(null, err2))
      throw err2
    }

    // rdxDispatch(setCognito(data, null))
    return data
  } catch (err) {
    // rdxDispatch(setCognito(null, err))
    throw err
  } finally {
    console.log("\tend")
  }
}

const makeRespondToAuthChallenge = (rdxDispatch: Dispatch) => async (
  username: string,
  answer: string,
  session: string
) => {
  console.log("cogRespondToAuthChallenge")

  const params: CognitoIdentityServiceProvider.RespondToAuthChallengeRequest = {
    ClientId: CLIENT_ID,
    ChallengeName: "CUSTOM_CHALLENGE",
    Session: session,
    ChallengeResponses: {
      ["USERNAME"]: username,
      ["ANSWER"]: answer,
    },
  }

  try {
    const data = await cognito.respondToAuthChallenge(params).promise()
    rdxDispatch(setCognito(data, null))
    return data
  } catch (err) {
    rdxDispatch(setCognito(null, err))
    throw err
  } finally {
    console.log("\tend")
  }
}

const THROTTLE_INTERVAL = 1500

/**
 * A wrapper hook to
 */
const useThrottle = <T extends (...args: any) => any>(
  func: T
): T & _.Cancelable => {
  return useCallback(_.throttle(func, THROTTLE_INTERVAL), [])
}

/**
 * React hook for various Cognito API methods
 */
export const useCognito = () => {
  const rdxDispatch = useDispatch()
  const errorMessage = useSelector(
    (state: RootState) => state.cognito?.error?.message
  )

  // Throttle cognito-calling methods
  const signUpWithEmail = useThrottle(makeSignUpWithEmail(rdxDispatch))
  const confirmSignUp = useThrottle(makeConfirmSignUp(rdxDispatch))
  const initiateAuth = useThrottle(makeInitateAuth(rdxDispatch))
  const initiateAuthForRefreshToken = useThrottle(
    makeInitateAuthForRefreshToken(rdxDispatch)
  )
  const initiateAuthWithFacebook = useThrottle(
    makeInitateAuthWithFacebook(rdxDispatch)
  )
  const adminLinkProviderForUser = useThrottle(
    makeAdminLinkProviderForUser(rdxDispatch)
  )
  const forgotPassword = useThrottle(makeForgotPassword(rdxDispatch))
  const confirmForgotPassword = useThrottle(
    makeConfirmForgotPassword(rdxDispatch)
  )
  const initiateAuthCustom = useThrottle(makeInitateAuthCustom(rdxDispatch))
  const respondToAuthChallenge = useThrottle(
    makeRespondToAuthChallenge(rdxDispatch)
  )
  const updateUserAttributes = useThrottle(
    makeUpdatePreferredUsername(rdxDispatch)
  )

  return {
    // cognito methods
    signUpWithEmail,
    confirmSignUp,
    initiateAuth,
    initiateAuthForRefreshToken,
    initiateAuthWithFacebook,
    initiateAuthCustom,
    respondToAuthChallenge,
    adminLinkProviderForUser,
    forgotPassword,
    confirmForgotPassword,
    updateUserAttributes,
    //
    errorMessage,
  }
}