Skip to content

Commit 2eb3533

Browse files
BatuevIOБатуев Илья Олегович
andauthored
App token storage (#297)
## Изменения - Добавил хранилище токенов миниаппов с проверкой экспирации ## Детали реализации - В appStore появился массив appTokens, в котором хранится id миниаппа, токен для него и дата экспирации (в миллисекундах, чтобы совмещать с LocalStorage) - Функция checkAppToken вызывается при попытке войти в миниапп: -- Если в хранилище есть токен и он не истек, то возвращает его -- Если в хранилище нет токена или он истек, то возвращаем undefined - Функция addAppToken вызывается, если не было найдено валидного токена для миниаппа: -- Сгенерированный на сервере токен записывается в хранилище - Функция getTokensFromStorage вызывается при попытке войти в миниапп: -- Достает из хранилища массив токенов и присваивает его appTokens ## Check-List <!-- После сохранения у следующих полей появятся галочки, которые нужно проставить мышкой --> - [x] Вы проверили свой код перед отправкой запроса? - [ ] Вы написали тесты к реализованным функциям? - [x] Вы не забыли применить форматирование `black` и `isort` для _Back-End_ или `Prettier` для _Front-End_? --------- Co-authored-by: Батуев Илья Олегович <batuevio@it.profcomff.com>
1 parent 75e1f93 commit 2eb3533

5 files changed

Lines changed: 82 additions & 32 deletions

File tree

src/models/LocalStorage.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ export enum LocalStorageItem {
44
TokenScopes = 'token-scopes',
55
MarketingId = 'marketing-id',
66
SuperappAuth = 'superapp-auth',
7+
AppToken = 'app-token',
78
}
89

910
export class LocalStorage {

src/models/index.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,13 @@ export type Lecturer = timetableComponents['schemas']['LecturerGet'];
9090
export type Room = timetableComponents['schemas']['RoomGet'];
9191
export type StudyGroup = timetableComponents['schemas']['GroupGet'];
9292

93+
// app models
94+
export interface AppToken {
95+
appId: number;
96+
token: string | undefined;
97+
expire: number;
98+
}
99+
93100
// general models
94101
export interface Entity {
95102
id: number;

src/store/apps.ts

Lines changed: 41 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,48 @@
11
import { defineStore } from 'pinia';
22
import { ref } from 'vue';
3-
import { Category } from '@/models';
3+
import { Category, AppToken } from '@/models';
4+
import { LocalStorage, LocalStorageItem } from '@/models/LocalStorage';
45

56
export const useAppsStore = defineStore('apps', () => {
67
const categories = ref<Category[]>([]);
8+
const appTokens = ref<AppToken[]>([]);
79

8-
return { categories };
10+
const addAppToken = (appId: number, token: string | undefined, expire: number) => {
11+
appTokens.value.push({
12+
appId,
13+
token,
14+
expire,
15+
});
16+
LocalStorage.set(LocalStorageItem.AppToken, appTokens.value);
17+
};
18+
19+
const checkAppToken = (appId: number) => {
20+
const appToken = appTokens.value.find(item => item.appId === appId);
21+
const currentDate = new Date();
22+
23+
if (!appToken) {
24+
return undefined;
25+
} else {
26+
if (appToken.expire < currentDate.getTime()) {
27+
appTokens.value.splice(appTokens.value.indexOf(appToken), 1);
28+
LocalStorage.set(LocalStorageItem.AppToken, appTokens.value);
29+
return undefined;
30+
} else {
31+
return appToken.token;
32+
}
33+
}
34+
};
35+
36+
const getTokensFromStorage = () => {
37+
appTokens.value = LocalStorage.getObject(LocalStorageItem.AppToken) ?? [];
38+
};
39+
40+
return {
41+
categories,
42+
appTokens,
43+
44+
addAppToken,
45+
checkAppToken,
46+
getTokensFromStorage,
47+
};
948
});

src/views/apps/ApplicationFrame.vue

Lines changed: 33 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,14 @@ import { AuthApi } from '@/api/controllers/auth/AuthApi';
88
import { ServiceData } from '@/models';
99
import apiClient from '@/api/';
1010
import { msInHour } from '@/utils/time';
11+
import { useAppsStore } from '@/store/apps';
1112
1213
const route = useRoute();
1314
const toolbar = useToolbar();
1415
const router = useRouter();
1516
const profileStore = useProfileStore();
17+
const appStore = useAppsStore();
18+
appStore.getTokensFromStorage();
1619
1720
enum AppState {
1821
WaitLoad = 1,
@@ -47,14 +50,15 @@ const composeUrl = async (url: URL, token: string | null, scopes: string[]) => {
4750
return url;
4851
};
4952
50-
function showApproveScopesScreen() {
51-
appState.value = AppState.WaitApprove;
52-
// immediately return a Promise
53-
// return new Promise(resolve => {
54-
// watch(userScopeApproved, value => resolve(value));
55-
// });
56-
return true;
57-
}
53+
// function showApproveScopesScreen() {
54+
// appState.value = AppState.WaitApprove;
55+
// // immediately return a Promise
56+
// // Раскомментить, если появятся сторонние приложения
57+
// // return new Promise(resolve => {
58+
// // watch(userScopeApproved, value => resolve(value));
59+
// // });
60+
// return true;
61+
// }
5862
5963
const getToken = async () => {
6064
// Запрашиваем токен. Для этого:
@@ -67,14 +71,10 @@ const getToken = async () => {
6771
return;
6872
}
6973
70-
console.log(scopes.value);
7174
const valuesToSearch = new Set(scopes.value);
72-
console.log(valuesToSearch);
7375
7476
userInfo.user_scopes.forEach(item => {
75-
console.log(item);
7677
if (valuesToSearch.has(item.name)) {
77-
console.log(' found');
7878
// TODO: Поменять name на comment, когда допилю ручку me (и, возможно, поменять /me на /scope)
7979
if (item.name !== undefined && item.name !== null) {
8080
scopeNamesToRequest.value.push(item.name);
@@ -85,27 +85,33 @@ const getToken = async () => {
8585
// 2. Если нужен токен без скоупов, то пропускаем запрос на разрешение у пользователя
8686
if (scopes.value.length != 0) {
8787
// 2.1 Показываем пользователю список прав, которые приложение запрашивает, и кнопки "разрешить"/"запретить"
88-
const scopesApproved = await showApproveScopesScreen();
88+
const scopesApproved = true; // await showApproveScopesScreen();
8989
9090
// 2.2 Если пользователь не разрешает – возваращаем undefined
9191
if (!scopesApproved) return undefined;
9292
}
9393
9494
// 3. Если пользователь разрешает – запрашиваем токен на Auth api и возвращаем его
95-
const expiresDate = new Date(Date.now() + msInHour);
96-
const { data } = await apiClient.POST('/auth/session', {
97-
body: {
98-
scopes: scopes.value.length == 0 ? [] : scopes.value,
99-
expires: expiresDate.toISOString(),
100-
},
101-
});
102-
if (!data) {
103-
appState.value = AppState.Error;
104-
return;
105-
}
106-
profileStore.id = data.user_id;
95+
const storageToken = appStore.checkAppToken(appId);
96+
if (storageToken) {
97+
return storageToken;
98+
} else {
99+
const expiresDate = new Date(Date.now() + msInHour / 60);
100+
const { data } = await apiClient.POST('/auth/session', {
101+
body: {
102+
scopes: scopes.value.length == 0 ? [] : scopes.value,
103+
expires: expiresDate.toISOString(),
104+
},
105+
});
106+
if (!data) {
107+
appState.value = AppState.Error;
108+
return;
109+
}
110+
profileStore.id = data.user_id;
111+
appStore.addAppToken(appId, data.token ?? undefined, expiresDate.getTime());
107112
108-
return data.token;
113+
return data.token;
114+
}
109115
};
110116
111117
const openApp = async (data: ServiceData) => {
@@ -114,6 +120,7 @@ const openApp = async (data: ServiceData) => {
114120
appState.value = AppState.Error;
115121
return;
116122
}
123+
117124
// Приложения открываем только по https
118125
if (data.link === undefined || !data.link?.startsWith('https://')) {
119126
appState.value = AppState.Error;

src/views/auth/AuthView.vue

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,6 @@ import ForgotPassordForm from '@/components/ForgotPassordForm.vue';
88
import IrdomAuthButton from '@/components/IrdomAuthButton.vue';
99
import { authButtons } from '@/constants/authButtons';
1010
import { ref } from 'vue';
11-
import { useProfileStore } from '@/store/profile';
12-
13-
const profileStore = useProfileStore();
1411
1512
const toolbar = useToolbar();
1613
@@ -41,7 +38,6 @@ const switchState = async (to: Step) => {
4138
}
4239
};
4340
44-
console.log(profileStore.token);
4541
toolbar.setup({ title: 'Вход в профиль' });
4642
</script>
4743

0 commit comments

Comments
 (0)