Skip to content

Instantly share code, notes, and snippets.

@ubbcou
Forked from soerenmartius/module.ts
Last active July 29, 2021 04:00
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save ubbcou/98443c32733c7e5956404232b522db31 to your computer and use it in GitHub Desktop.
Save ubbcou/98443c32733c7e5956404232b522db31 to your computer and use it in GitHub Desktop.
vuex 4 cognito module
import {
ActionContext,
ActionTree,
GetterTree,
MutationTree,
Module,
Store as VuexStore,
CommitOptions,
DispatchOptions,
} from 'vuex'
import { SignUpParams } from '@aws-amplify/auth/lib-esm/types'
import { CognitoUserAttribute as Attribute } from 'amazon-cognito-identity-js'
import Auth from '@aws-amplify/auth'
import { State as RootState } from '@/store'
import { AccountService } from '@/modules/auth/services/account'
// Declare types
export type AuthenticationStatus = {
state?: string
message?: string
variant?: string
}
export type Credentials = {
username: string
password: string
}
export type ConfirmationParams = {
username: string
code: string
}
export enum AttributeNames {
NAME = 'name',
EMAIL = 'email',
EMAIL_VERIFIED = 'email_verified',
SUB = 'sub',
}
// Declare state
export type State = {
teamId?: string
isAuthenticated: boolean
authenticationStatus?: AuthenticationStatus
passwordForgetUsername?: string
attributes?: Attribute[]
}
// Create initial state
const state: State = {
isAuthenticated: false,
}
// mutations enums
export enum MutationTypes {
AUTHENTICATION_ERROR = 'AUTHENTICATION_ERROR',
CLEAR_AUTHENTICATION_STATUS = 'CLEAR_AUTHENTICATION_STATUS',
CLEAR_AUTHENTICATION = 'CLEAR_AUTHENTICATION',
SET_USER_AUTHENTICATED = 'SET_USER_AUTHENTICATED',
SET_TEAM_ID = 'SET_TEAM_ID',
PASSWORD_FORGET_SET_USERNAME = 'PASSWORD_FORGET_SET_USERNAME',
CLEAR_PASSWORD_FORGET_USERNAME = 'CLEAR_PASSWORD_FORGET_SET_USERNAME',
SET_ATTRIBUTES = 'SET_ATTRIBUTES',
}
// Mutation contracts
export type Mutations<S = State> = {
[MutationTypes.AUTHENTICATION_ERROR](state: S, err: Error): void
[MutationTypes.CLEAR_AUTHENTICATION_STATUS](state: S): void
[MutationTypes.CLEAR_AUTHENTICATION](state: S): void
[MutationTypes.SET_USER_AUTHENTICATED](state: S): void
[MutationTypes.SET_TEAM_ID](state: S, teamId: string): void
[MutationTypes.PASSWORD_FORGET_SET_USERNAME](state: S, username: string): void
[MutationTypes.CLEAR_PASSWORD_FORGET_USERNAME](state: S): void
[MutationTypes.SET_ATTRIBUTES](state: S, attributes: Attribute[]): void
}
// Define mutations
const mutations: MutationTree<State> & Mutations = {
[MutationTypes.AUTHENTICATION_ERROR](state: State, err: Error) {
state.isAuthenticated = false
state.authenticationStatus = {
state: 'failed',
message: err.message,
variant: 'danger',
}
},
[MutationTypes.CLEAR_AUTHENTICATION_STATUS](state: State) {
state.authenticationStatus = undefined
},
[MutationTypes.CLEAR_AUTHENTICATION](state: State) {
state.isAuthenticated = false
state.authenticationStatus = undefined
},
[MutationTypes.SET_USER_AUTHENTICATED](state: State) {
state.isAuthenticated = true
},
[MutationTypes.SET_TEAM_ID](state: State, teamId: string) {
state.teamId = teamId
},
[MutationTypes.PASSWORD_FORGET_SET_USERNAME](state: State, username: string) {
state.passwordForgetUsername = username
},
[MutationTypes.CLEAR_PASSWORD_FORGET_USERNAME](state: State) {
state.passwordForgetUsername = undefined
},
[MutationTypes.SET_ATTRIBUTES](state: State, attributes: Attribute[]) {
state.attributes = attributes
},
}
// Action enums
export enum ActionTypes {
SIGNUP = 'SIGNUP',
CONFIRM_SIGNUP = 'CONFIRM_SIGNUP',
SIGNIN = 'SIGNIN',
SIGNOUT = 'SIGNOUT',
INIT_PASSWORD_FORGET = 'INIT_PASSWORD_FORGET',
PASSWORD_FORGET_SUBMIT = 'PASSWORD_FORGET_SUBMIT',
FETCH_ATTRIBUTES = 'FETCH_ATTRIBUTES',
CHANGE_EMAIL = 'CHANGE_EMAIL',
}
// Actions context
type AugmentedActionContext = {
commit<K extends keyof Mutations>(
key: K,
payload: Parameters<Mutations[K]>[1],
): ReturnType<Mutations[K]>
getters<K extends keyof Getters>(
key: K,
payload: Parameters<Getters[K]>[1],
): ReturnType<Getters[K]>
} & Omit<ActionContext<State, RootState>, 'commit'>
// Actions contracts
export interface Actions {
[ActionTypes.SIGNUP](
{ commit }: AugmentedActionContext,
payload: SignUpParams,
): void
[ActionTypes.CONFIRM_SIGNUP](
{ commit }: AugmentedActionContext,
payload: ConfirmationParams,
): void
[ActionTypes.SIGNIN](
{ commit }: AugmentedActionContext,
payload: Credentials,
): void
[ActionTypes.SIGNOUT](
{ commit }: AugmentedActionContext,
payload: undefined,
): void
[ActionTypes.INIT_PASSWORD_FORGET](
{ commit }: AugmentedActionContext,
username: string,
): void
[ActionTypes.PASSWORD_FORGET_SUBMIT](
{ commit }: AugmentedActionContext,
payload: { username: string; code: string; password: string },
): void
[ActionTypes.FETCH_ATTRIBUTES](
{ commit }: AugmentedActionContext,
payload: undefined,
): void
[ActionTypes.CHANGE_EMAIL](
{ commit, getters }: AugmentedActionContext,
email: string,
): void
}
// Define actions
export const actions: ActionTree<State, RootState> & Actions = {
async [ActionTypes.SIGNUP]({ commit }, payload: SignUpParams) {
commit(MutationTypes.CLEAR_AUTHENTICATION_STATUS, undefined)
try {
await Auth.signUp(payload)
commit(MutationTypes.CLEAR_AUTHENTICATION, undefined)
} catch (err) {
commit(MutationTypes.AUTHENTICATION_ERROR, err)
}
},
async [ActionTypes.CONFIRM_SIGNUP]({ commit }, payload: ConfirmationParams) {
commit(MutationTypes.CLEAR_AUTHENTICATION_STATUS, undefined)
try {
await Auth.confirmSignUp(payload.username, payload.code)
} catch (err) {
commit(MutationTypes.AUTHENTICATION_ERROR, err)
}
},
async [ActionTypes.SIGNIN]({ commit }, payload: Credentials) {
commit(MutationTypes.CLEAR_AUTHENTICATION_STATUS, undefined)
try {
await Auth.signIn(payload.username, payload.password)
commit(MutationTypes.SET_USER_AUTHENTICATED, undefined)
// toDo: Temporary solution for setting team id
const accountService = new AccountService()
const account = await accountService.getAccount()
commit(MutationTypes.SET_TEAM_ID, account.teams[0].team_id)
} catch (err) {
commit(MutationTypes.AUTHENTICATION_ERROR, err)
}
},
async [ActionTypes.SIGNOUT]({ commit }) {
try {
await Auth.signOut()
} catch (err) {
commit(MutationTypes.AUTHENTICATION_ERROR, err)
}
commit(MutationTypes.CLEAR_AUTHENTICATION, undefined)
},
async [ActionTypes.INIT_PASSWORD_FORGET]({ commit }, username: string) {
commit(MutationTypes.CLEAR_AUTHENTICATION_STATUS, undefined)
try {
await Auth.forgotPassword(username)
} catch (err) {
commit(MutationTypes.AUTHENTICATION_ERROR, err)
}
},
async [ActionTypes.PASSWORD_FORGET_SUBMIT](
{ commit },
payload: { username: string; code: string; password: string },
) {
const { username, code, password } = payload
commit(MutationTypes.CLEAR_AUTHENTICATION_STATUS, undefined)
try {
await Auth.forgotPasswordSubmit(username, code, password)
} catch (err) {
commit(MutationTypes.AUTHENTICATION_ERROR, err)
}
},
async [ActionTypes.FETCH_ATTRIBUTES]({ commit }) {
const user = await Auth.currentUserPoolUser()
try {
const attributes: Attribute[] = await Auth.userAttributes(user)
commit(MutationTypes.SET_ATTRIBUTES, attributes)
} catch (err) {
commit(MutationTypes.AUTHENTICATION_ERROR, err)
}
},
async [ActionTypes.CHANGE_EMAIL]({ commit, getters }, email) {
const emailAttribute = getters.getAttribute(AttributeNames.EMAIL)
try {
const user = await Auth.currentUserPoolUser()
await Auth.updateUserAttributes(user, { ...emailAttribute, Value: email })
} catch (err) {
commit(MutationTypes.AUTHENTICATION_ERROR, err)
}
},
}
// getters types
export type Getters = {
isAuthenticated(state: State): boolean
hasAuthenticationStatus(state: State): boolean
getAuthenticationStatus(state: State): AuthenticationStatus | undefined
getTeamId(state: State): string | undefined
getPasswordForgetUsername(state: State): string | undefined
getAttributes(state: State): Attribute[] | undefined
getAttribute(state: State): (name: AttributeNames) => Attribute | undefined
getFullName(state: State, getters: Getters): string | undefined
getEmail(state: State, getters: Getters): string | undefined
isEmailVerified(state: State, getters: Getters): string | undefined
}
// getters
export const getters: GetterTree<State, RootState> & Getters = {
isAuthenticated: (state) => state.isAuthenticated,
hasAuthenticationStatus: (state) => !!state.authenticationStatus,
getAuthenticationStatus: (state) => state.authenticationStatus,
getTeamId: (state) => state.teamId,
getPasswordForgetUsername: (state) => state.passwordForgetUsername,
getAttributes: (state) => state.attributes,
getAttribute: (state) => (name: AttributeNames) => {
if (state.attributes === undefined) return undefined
return state.attributes.find((attr) => attr.getName() === name)
},
getFullName: (state, getters) => {
return getters.getAttribute(AttributeNames.NAME)?.getValue()
},
getEmail: (state, getters) => {
return getters.getAttribute(AttributeNames.EMAIL)?.getValue()
},
isEmailVerified: (state, getters) => {
return getters.getAttribute(AttributeNames.EMAIL_VERIFIED)?.getValue()
},
}
//setup store type
export type Store<S = State> = Omit<
VuexStore<S>,
'commit' | 'getters' | 'dispatch'
> & {
commit<K extends keyof Mutations, P extends Parameters<Mutations[K]>[1]>(
key: K,
payload: P,
options?: CommitOptions,
): ReturnType<Mutations[K]>
} & {
getters: {
[K in keyof Getters]: ReturnType<Getters[K]>
}
} & {
dispatch<K extends keyof Actions>(
key: K,
payload: Parameters<Actions[K]>[1],
options?: DispatchOptions,
): ReturnType<Actions[K]>
}
export const AuthModule: Module<State, RootState> = {
state,
mutations,
actions,
// Namespacing Vuex modules is tricky and hard to type check with typescript.
// Instead of namespacing, we could create our own namespacing mechanism by
// prefixing the value of the TypeScript enum with the namespace, e.g.
// enum TodoActions {
// AddTodo = 'TODO__ADD_TODO'
// }
// namespaced: true,
getters,
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment