diff --git a/src/App.vue b/src/App.vue index 33cbb25..cd0009c 100644 --- a/src/App.vue +++ b/src/App.vue @@ -76,7 +76,7 @@ if (state.value.token) { userData.value = jwtDecode(state.value.token); } -const hasToken = computed(() => !!store.getters.getToken); +const hasToken = computed(() => !!store.getters.token); interface LogoElement { letter: string; diff --git a/src/api/composed.ts b/src/api/composed.ts index 29db448..5d5db74 100644 --- a/src/api/composed.ts +++ b/src/api/composed.ts @@ -2,6 +2,7 @@ import SignetRequestController from '@/api/requests'; import { AuthenticationRequest, Bonus, + ChangePasswordRequest, CloseRewardFundRequest, ContributeRequest, CreateQueueRequest, @@ -9,6 +10,7 @@ import { CreateRewardFundRequest, DistributeRewardsRequest, EditQueueRequest, + EscalatePrivilegesRequest, GetBalanceRequest, GetBalanceResponse, GetContributionsRequest, @@ -24,6 +26,7 @@ import { LoginResponse, NearlyCompleteFundsRequest, NearlyCompleteFundsResponse, + Privileges, QueueMember, RewardDistributionInfo, SubmitRewardFundRequest, @@ -133,3 +136,13 @@ export const distributeRewardFund = (rewardFundID: number, payments: RewardDistr }); export const getUsers = () => controller.post('GetUsers', null); + +export const changePrivileges = (userID: number, privileges: Privileges) => controller.post('ChangePrivileges', { + userID, + privileges, +}); + +export const changePassword = (userID: number, password: string) => controller.post('ChangePassword', { + userID, + password, +}); diff --git a/src/api/requests.ts b/src/api/requests.ts index 0df5dcb..5f94436 100644 --- a/src/api/requests.ts +++ b/src/api/requests.ts @@ -24,7 +24,7 @@ class SignetRequestController { method: 'GET', headers: setHeaders( undefined, - store.getters.getToken, + store.getters.token, ), }, ); @@ -42,7 +42,7 @@ class SignetRequestController { body: JSON.stringify(payload), headers: setHeaders( { 'Content-Type': 'application/json' }, - store.getters.getToken, + store.getters.token, ), }, ); diff --git a/src/api/types.ts b/src/api/types.ts index e714a13..844dcfd 100644 --- a/src/api/types.ts +++ b/src/api/types.ts @@ -1,5 +1,6 @@ // eslint-disable-next-line no-shadow import Decimal from 'decimal.js'; +import { DateTime } from 'luxon'; // eslint-disable-next-line no-shadow export enum Privileges { @@ -131,6 +132,7 @@ export interface AuthenticationRequest { export interface LoginResponse { token: string | null; + lastLogin: DateTime | null; } export interface GetQueueMembersRequest { @@ -151,6 +153,7 @@ export interface GetRewardFundsResponse { } export interface Claims { + id: number; username: string; privileges: Privileges; exp: number; @@ -210,11 +213,22 @@ export interface DistributeRewardsRequest { } export interface User { - username: string, - password: string, - admin: number, + id: number; + username: string; + password: string; + admin: number; } export interface GetUsersResponse { users: User[]; } + +export interface EscalatePrivilegesRequest { + userID: number; + privileges: Privileges; +} + +export interface ChangePasswordRequest { + userID: number; + password: string; +} diff --git a/src/components/ModifyWithConfirmation.vue b/src/components/ButtonWithConfirmation.vue similarity index 100% rename from src/components/ModifyWithConfirmation.vue rename to src/components/ButtonWithConfirmation.vue diff --git a/src/lib/auth.ts b/src/lib/auth.ts index 59a3805..4824aa0 100644 --- a/src/lib/auth.ts +++ b/src/lib/auth.ts @@ -8,10 +8,10 @@ const removeToken = () => { store.commit('clearToken'); }; const hasPermission = (requiredRights: number) => { - const jwt = store.getters.getToken; + const jwt = store.getters.token; if (jwt !== undefined && requiredRights !== undefined) { try { - const decoded = jwtDecode(store.getters.getToken); + const decoded = jwtDecode(store.getters.token); const expired = luxon.DateTime.now() .toUnixInteger() > decoded.exp; jwtDecode(jwt, { header: true }); diff --git a/src/router/index.ts b/src/router/index.ts index d9053dd..9075361 100644 --- a/src/router/index.ts +++ b/src/router/index.ts @@ -14,6 +14,8 @@ import ModifyQueueView from '@/views/ModifyQueueView.vue'; import AdminDashboardView from '@/views/AdminDashboardView.vue'; import ModifyUserView from '@/views/ModifyUserView.vue'; import LogoutView from '@/views/LogoutView.vue'; +import ChangePasswordView from '@/views/ChangePasswordView.vue'; +import store from '@/store'; const routes: Array = [ { @@ -34,6 +36,12 @@ const routes: Array = [ component: LoginView, meta: { title: 'Login' }, }, + { + path: '/changepassword', + name: 'changepassword', + component: ChangePasswordView, + meta: { title: 'Change Password' }, + }, { path: '/logout', name: 'logout', @@ -129,7 +137,13 @@ const router = createRouter({ router.beforeEach(async (to, from, next) => { document.title = `Beignet - ${to.meta.title}`; - return next(); + if (!store.getters.passwordChangeRequired) { + return next(); + } + if (to.name !== 'changepassword') { + return next('changepassword'); + } + return next(undefined); }); export default router; diff --git a/src/store/index.ts b/src/store/index.ts index 6a94671..4e851ec 100644 --- a/src/store/index.ts +++ b/src/store/index.ts @@ -3,9 +3,11 @@ import { createStore } from 'vuex'; export default createStore({ state: { token: undefined as string | undefined, + passwordChangeRequired: false, }, getters: { - getToken: (state) => state.token, + token: (state) => state.token, + passwordChangeRequired: (state) => state.passwordChangeRequired, }, mutations: { setToken: (state, token) => { @@ -14,6 +16,12 @@ export default createStore({ clearToken: (state) => { state.token = undefined; }, + userNeedsToChangePassword: (state) => { + state.passwordChangeRequired = true; + }, + userHasChangedPassword: (state) => { + state.passwordChangeRequired = false; + }, }, actions: { setToken: (context) => { @@ -22,7 +30,12 @@ export default createStore({ clearToken: (context) => { context.commit('clearToken'); }, + userNeedsToChangePassword: (context) => { + context.commit('userNeedsToChangePassword'); + }, + userHasChangedPassword: (context) => { + context.commit('userHasChangedPassword'); + }, }, - modules: { - }, + modules: {}, }); diff --git a/src/views/ChangePasswordView.vue b/src/views/ChangePasswordView.vue new file mode 100644 index 0000000..9e24a30 --- /dev/null +++ b/src/views/ChangePasswordView.vue @@ -0,0 +1,53 @@ + + + + + diff --git a/src/views/FundView.vue b/src/views/FundView.vue index 06c1555..4e4b05e 100644 --- a/src/views/FundView.vue +++ b/src/views/FundView.vue @@ -122,7 +122,7 @@ -

+

Enable contribution consolidation in order to select. Click to select a row in order to mark a wallet to receive rewards.

@@ -154,8 +154,8 @@ -
- +
+ v-if="store.getters.token && hasPermission(Privileges.Admin)">
Submit Group Fund
-
-
+
Close Group Fund
- { const selectedContributions = ref([] as Contribution[]); const selectContribution = (contribution: SelectableContribution) => { - if (!store.getters.getToken || !hasPermission(Privileges.Admin)) return; + if (!store.getters.token || !hasPermission(Privileges.Admin)) return; if (enableConsolidation.value) { if (!contribution.selected) { selectedContributions.value.push(contribution); diff --git a/src/views/LoginView.vue b/src/views/LoginView.vue index 50117f5..2d00bcc 100644 --- a/src/views/LoginView.vue +++ b/src/views/LoginView.vue @@ -34,7 +34,12 @@ const submit = async () => { if (resp.token !== null) { sessionStorage.setItem('jwt', JSON.stringify({ token: resp.token })); store.commit('setToken', resp.token); - await router.push('/'); + if (resp.lastLogin === null) { + await store.dispatch('userNeedsToChangePassword'); + await router.push('/changepassword'); + } else { + await router.push('/'); + } } }; diff --git a/src/views/ModifyUserView.vue b/src/views/ModifyUserView.vue index 91fe1fc..56a6dc8 100644 --- a/src/views/ModifyUserView.vue +++ b/src/views/ModifyUserView.vue @@ -26,7 +26,9 @@
-