Skip to content

Instantly share code, notes, and snippets.

@soerenmartius
Last active April 27, 2025 12:53
Show Gist options
  • Save soerenmartius/5f69fc92c29cd8c3989ca57e6ce3ac27 to your computer and use it in GitHub Desktop.
Save soerenmartius/5f69fc92c29cd8c3989ca57e6ce3ac27 to your computer and use it in GitHub Desktop.

Revisions

  1. soerenmartius revised this gist Oct 27, 2020. 1 changed file with 1 addition and 1 deletion.
    2 changes: 1 addition & 1 deletion module.ts
    Original file line number Diff line number Diff line change
    @@ -134,7 +134,7 @@ type AugmentedActionContext = {
    key: K,
    payload: Parameters<Getters[K]>[1],
    ): ReturnType<Getters[K]>
    }
    } & Omit<ActionContext<State, RootState>, 'commit'>

    // Actions contracts
    export interface Actions {
  2. soerenmartius created this gist Oct 27, 2020.
    340 changes: 340 additions & 0 deletions module.ts
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,340 @@
    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]>
    }

    // 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,
    }