Last active
October 12, 2023 09:30
-
-
Save soerenmartius/5f69fc92c29cd8c3989ca57e6ce3ac27 to your computer and use it in GitHub Desktop.
vuex 4 cognito module
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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