Преглед на файлове

Force change password for new users

master
Jared преди 1 година
родител
ревизия
d79ed3f6de
променени са 12 файла, в които са добавени 153 реда и са изтрити 25 реда
  1. +1
    -1
      src/App.vue
  2. +13
    -0
      src/api/composed.ts
  3. +2
    -2
      src/api/requests.ts
  4. +17
    -3
      src/api/types.ts
  5. +0
    -0
      src/components/ButtonWithConfirmation.vue
  6. +2
    -2
      src/lib/auth.ts
  7. +15
    -1
      src/router/index.ts
  8. +16
    -3
      src/store/index.ts
  9. +53
    -0
      src/views/ChangePasswordView.vue
  10. +9
    -9
      src/views/FundView.vue
  11. +6
    -1
      src/views/LoginView.vue
  12. +19
    -3
      src/views/ModifyUserView.vue

+ 1
- 1
src/App.vue Целия файл

@@ -76,7 +76,7 @@ if (state.value.token) {
userData.value = jwtDecode<Claims>(state.value.token);
}

const hasToken = computed(() => !!store.getters.getToken);
const hasToken = computed(() => !!store.getters.token);

interface LogoElement {
letter: string;


+ 13
- 0
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<GetUsersResponse, null>('GetUsers', null);

export const changePrivileges = (userID: number, privileges: Privileges) => controller.post<SuccessResponse, EscalatePrivilegesRequest>('ChangePrivileges', {
userID,
privileges,
});

export const changePassword = (userID: number, password: string) => controller.post<SuccessResponse, ChangePasswordRequest>('ChangePassword', {
userID,
password,
});

+ 2
- 2
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,
),
},
);


+ 17
- 3
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;
}

src/components/ModifyWithConfirmation.vue → src/components/ButtonWithConfirmation.vue Целия файл


+ 2
- 2
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<Claims>(store.getters.getToken);
const decoded = jwtDecode<Claims>(store.getters.token);
const expired = luxon.DateTime.now()
.toUnixInteger() > decoded.exp;
jwtDecode(jwt, { header: true });


+ 15
- 1
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<RouteRecordRaw> = [
{
@@ -34,6 +36,12 @@ const routes: Array<RouteRecordRaw> = [
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;

+ 16
- 3
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: {},
});

+ 53
- 0
src/views/ChangePasswordView.vue Целия файл

@@ -0,0 +1,53 @@
<template>
<section class="section is-medium">
<div
class="is-flex is-flex-direction-column is-justify-content-space-around change-password-body">
<div>
<span class="is-size-3-desktop is-size-4-mobile">
Welcome, {{ user.username }}
</span>
<p>It's time to change your password.</p>
</div>
<div>
<input type="password" class="input is-medium" aria-label="Change Password"
v-model="password"/>
<div class="is-flex is-flex-direction-row is-justify-content-flex-end my-3">
<button class="button is-success" @click="changeUserPassword">Submit</button>
</div>
</div>
</div>
</section>
</template>

<script setup lang="ts">
import jwtDecode from 'jwt-decode';
import { Claims } from '@/api/types';
import store from '@/store';
import { useRouter } from 'vue-router';
import { ref } from 'vue';
import { changePassword } from '@/api/composed';

const router = useRouter();

const password = ref<string>();

const user = ref<Claims>();
if (store.getters.token) {
user.value = jwtDecode<Claims>(store.getters.token);
} else {
await router.push('/');
}

const changeUserPassword = async () => {
if (!user.value) throw new Error('There is no user!');
if (!password.value) throw new Error('You need to type a password!');
const resp = await changePassword(user.value?.id, password.value);
await store.dispatch('userHasChangedPassword');
if (resp?.success) await router.push('/');
};
</script>

<style scoped lang="stylus">
.change-password-body
height 45vh
</style>

+ 9
- 9
src/views/FundView.vue Целия файл

@@ -122,7 +122,7 @@
</label>
</div>
</div>
<p class="py-2" v-if="store.getters.getToken && hasPermission(Privileges.Admin)">
<p class="py-2" v-if="store.getters.token && hasPermission(Privileges.Admin)">
Enable contribution consolidation in order to select.
Click to select a row in order to mark a wallet to receive rewards.
</p>
@@ -154,8 +154,8 @@
</tbody>
</table>
</div>
<div class="my-4" v-if="store.getters.getToken && hasPermission(Privileges.Admin)">
<ModifyWithConfirmation
<div class="my-4" v-if="store.getters.token && hasPermission(Privileges.Admin)">
<ButtonWithConfirmation
:condition="selectedContributions.length === 0"
:inputs="{ button: { label: 'Distribute Rewards', style: 'is-success' },
checkbox: { label: 'Allow Distribution' }}"
@@ -165,22 +165,22 @@
</div>
</section>
<section class="section is-small px-0"
v-if="store.getters.getToken && hasPermission(Privileges.Admin)">
v-if="store.getters.token && hasPermission(Privileges.Admin)">
<div class="title is-size-4 has-text-white-ter has-text-centered">
Submit Group Fund
</div>
<ModifyWithConfirmation
<ButtonWithConfirmation
:inputs="{ button: { label: 'Submit Fund', style: 'is-success' },
checkbox: { label: 'Allow Submission' }}"
:modification="submitFund"
@confirmed="setAllowSubmit"
/>
</section>
<section v-if="store.getters.getToken && hasPermission(Privileges.AdminPlus)">
<section v-if="store.getters.token && hasPermission(Privileges.AdminPlus)">
<div class="title is-size-4 has-text-white-ter has-text-centered">
Close Group Fund
</div>
<ModifyWithConfirmation
<ButtonWithConfirmation
:inputs="{ button: { label: 'Close Fund', style: 'is-danger' },
checkbox: { label: 'Allow Closing' }}"
:modification="deleteFund"
@@ -230,7 +230,7 @@ import {
submitRewardFund,
} from '@/api/composed';
import Decimal from 'decimal.js';
import ModifyWithConfirmation from '@/components/ModifyWithConfirmation.vue';
import ButtonWithConfirmation from '@/components/ButtonWithConfirmation.vue';

const route = useRoute();
const router = useRouter();
@@ -389,7 +389,7 @@ const calculateReward = (bought: Decimal) => {

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);


+ 6
- 1
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('/');
}
}
};
</script>


+ 19
- 3
src/views/ModifyUserView.vue Целия файл

@@ -26,7 +26,9 @@
</td>
<td class="p-2">
<div class="select is-small">
<select name="" id="" aria-label="User Privilege">
<select name="" id=""
aria-label="User Privilege"
@change="setNewUserPermissions(user.id, $event)">
<option :value="privilege"
:selected="getPrivilege(user.admin) === privilege"
v-for="(privilege, i) in Object.values(privileges)" :key="i"
@@ -52,7 +54,10 @@ import {
User,
} from '@/api/types';
import { ref } from 'vue';
import { getUsers } from '@/api/composed';
import {
changePrivileges,
getUsers,
} from '@/api/composed';
import jwtDecode from 'jwt-decode';
import store from '@/store';

@@ -64,12 +69,18 @@ try {
users.value = undefined;
}

const getSelectedPrivilege = (evt: Event) => {
const target = evt.target as HTMLSelectElement;
const privilege = target.options[target.selectedIndex].value as 'SuperUser' | 'AdminPlus' | 'Admin';
return Privileges[privilege];
};

const userData = ref<Claims>({
username: '',
privileges: -1,
exp: -1,
});
userData.value = jwtDecode<Claims>(store.getters.getToken);
userData.value = jwtDecode<Claims>(store.getters.token);

const getPrivilege = (privilege: number) => Privileges[privilege];

@@ -81,6 +92,11 @@ const getPrivileges = () => Object.fromEntries(

const privileges = getPrivileges();

const setNewUserPermissions = (userID: number, evt: Event) => changePrivileges(
userID,
getSelectedPrivilege(evt),
);

</script>

<style scoped lang="stylus">


Зареждане…
Отказ
Запис