Added totp/mail otp, split up dto and api into multiple files
This commit is contained in:
parent
af1df3e508
commit
cd0d25ba4f
75
dto/index.ts
75
dto/index.ts
@ -1,73 +1,2 @@
|
||||
//
|
||||
// Responses
|
||||
//
|
||||
|
||||
export interface BaseResponse {
|
||||
statusCode: number;
|
||||
}
|
||||
|
||||
export interface ErrorResponse extends BaseResponse {
|
||||
statusCode: 400 | 401 | 403;
|
||||
message?: string;
|
||||
}
|
||||
|
||||
export type RefreshResponse = LoginResponse;
|
||||
export interface LoginResponse extends BaseResponse {
|
||||
statusCode: 200;
|
||||
jwt: string;
|
||||
}
|
||||
|
||||
export interface GetRootResponse extends BaseResponse {
|
||||
statusCode: 200;
|
||||
rootId: number;
|
||||
}
|
||||
|
||||
export interface GetNodeResponse extends BaseResponse {
|
||||
statusCode: 200;
|
||||
id: number;
|
||||
name: string;
|
||||
isFile: boolean;
|
||||
parent: number | null;
|
||||
children?: number[];
|
||||
size?: number;
|
||||
}
|
||||
|
||||
export interface GetPathResponse extends BaseResponse {
|
||||
statusCode: 200;
|
||||
path: string;
|
||||
}
|
||||
|
||||
export type CreateFileResponse = CreateFolderResponse;
|
||||
export interface CreateFolderResponse extends BaseResponse {
|
||||
statusCode: 200;
|
||||
id: number;
|
||||
}
|
||||
|
||||
export type SignupResponse = DeleteResponse;
|
||||
export type UploadFileResponse = DeleteResponse;
|
||||
export interface DeleteResponse extends BaseResponse {
|
||||
statusCode: 200;
|
||||
}
|
||||
|
||||
//
|
||||
// Requests
|
||||
//
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-empty-interface
|
||||
export interface BaseRequest {}
|
||||
|
||||
export type AuthSignUpRequest = AuthLoginRequest;
|
||||
export interface AuthLoginRequest extends BaseRequest {
|
||||
username: string;
|
||||
password: string;
|
||||
}
|
||||
|
||||
export type CreateFileRequest = CreateFolderRequest;
|
||||
export interface CreateFolderRequest extends BaseRequest {
|
||||
parent: number;
|
||||
name: string;
|
||||
}
|
||||
|
||||
export interface DeleteRequest extends BaseRequest {
|
||||
node: number;
|
||||
}
|
||||
export * as Requests from './requests';
|
||||
export * as Responses from './responses';
|
||||
|
10
dto/requests/auth.ts
Normal file
10
dto/requests/auth.ts
Normal file
@ -0,0 +1,10 @@
|
||||
import { BaseRequest } from './base';
|
||||
|
||||
export interface AuthSignUpRequest extends BaseRequest {
|
||||
username: string;
|
||||
password: string;
|
||||
}
|
||||
|
||||
export interface AuthLoginRequest extends AuthSignUpRequest {
|
||||
otp?: string;
|
||||
}
|
2
dto/requests/base.ts
Normal file
2
dto/requests/base.ts
Normal file
@ -0,0 +1,2 @@
|
||||
// eslint-disable-next-line @typescript-eslint/no-empty-interface
|
||||
export interface BaseRequest {}
|
12
dto/requests/fs.ts
Normal file
12
dto/requests/fs.ts
Normal file
@ -0,0 +1,12 @@
|
||||
import { BaseRequest } from './base';
|
||||
|
||||
export type CreateFileRequest = CreateFolderRequest;
|
||||
|
||||
export interface CreateFolderRequest extends BaseRequest {
|
||||
parent: number;
|
||||
name: string;
|
||||
}
|
||||
|
||||
export interface DeleteRequest extends BaseRequest {
|
||||
node: number;
|
||||
}
|
3
dto/requests/index.ts
Normal file
3
dto/requests/index.ts
Normal file
@ -0,0 +1,3 @@
|
||||
export * from './base';
|
||||
export * as Auth from './auth';
|
||||
export * as FS from './fs';
|
19
dto/responses/auth.ts
Normal file
19
dto/responses/auth.ts
Normal file
@ -0,0 +1,19 @@
|
||||
import { BaseResponse, SuccessResponse } from './base';
|
||||
|
||||
export type TfaRequiredResponse = SuccessResponse;
|
||||
export type RemoveTfaResponse = SuccessResponse;
|
||||
export type RequestEmailTfaResponse = SuccessResponse;
|
||||
export type TfaCompletedResponse = SuccessResponse;
|
||||
export type SignupResponse = SuccessResponse;
|
||||
export type RefreshResponse = LoginResponse;
|
||||
|
||||
export interface LoginResponse extends BaseResponse {
|
||||
statusCode: 200;
|
||||
jwt: string;
|
||||
}
|
||||
|
||||
export interface RequestTotpTfaResponse extends BaseResponse {
|
||||
statusCode: 200;
|
||||
qrCode: string;
|
||||
secret: string;
|
||||
}
|
12
dto/responses/base.ts
Normal file
12
dto/responses/base.ts
Normal file
@ -0,0 +1,12 @@
|
||||
export interface BaseResponse {
|
||||
statusCode: number;
|
||||
}
|
||||
|
||||
export interface SuccessResponse extends BaseResponse {
|
||||
statusCode: 200;
|
||||
}
|
||||
|
||||
export interface ErrorResponse extends BaseResponse {
|
||||
statusCode: 400 | 401 | 403;
|
||||
message?: string;
|
||||
}
|
30
dto/responses/fs.ts
Normal file
30
dto/responses/fs.ts
Normal file
@ -0,0 +1,30 @@
|
||||
import { BaseResponse, SuccessResponse } from './base';
|
||||
|
||||
export type UploadFileResponse = SuccessResponse;
|
||||
export type DeleteResponse = SuccessResponse;
|
||||
export type CreateFileResponse = CreateFolderResponse;
|
||||
|
||||
export interface GetRootResponse extends BaseResponse {
|
||||
statusCode: 200;
|
||||
rootId: number;
|
||||
}
|
||||
|
||||
export interface GetNodeResponse extends BaseResponse {
|
||||
statusCode: 200;
|
||||
id: number;
|
||||
name: string;
|
||||
isFile: boolean;
|
||||
parent: number | null;
|
||||
children?: number[];
|
||||
size?: number;
|
||||
}
|
||||
|
||||
export interface GetPathResponse extends BaseResponse {
|
||||
statusCode: 200;
|
||||
path: string;
|
||||
}
|
||||
|
||||
export interface CreateFolderResponse extends BaseResponse {
|
||||
statusCode: 200;
|
||||
id: number;
|
||||
}
|
3
dto/responses/index.ts
Normal file
3
dto/responses/index.ts
Normal file
@ -0,0 +1,3 @@
|
||||
export * from './base';
|
||||
export * as Auth from './auth';
|
||||
export * as FS from './fs';
|
@ -1,7 +1,7 @@
|
||||
<script setup async lang="ts">
|
||||
import { provide, ref } from 'vue';
|
||||
import { useRouter } from 'vue-router';
|
||||
import { refresh_token, TokenInjectType, isErrorResponse } from '@/api';
|
||||
import { Auth, TokenInjectType, isErrorResponse } from '@/api';
|
||||
|
||||
const router = useRouter();
|
||||
|
||||
@ -21,7 +21,7 @@ function logout() {
|
||||
jwt.value = localStorage.getItem('token');
|
||||
if (jwt.value == null) await router.push({ name: 'login' });
|
||||
else {
|
||||
const new_token = await refresh_token(jwt.value ?? '');
|
||||
const new_token = await Auth.refresh_token(jwt.value ?? '');
|
||||
if (isErrorResponse(new_token)) logout();
|
||||
else setToken(new_token.jwt);
|
||||
}
|
||||
|
@ -1,221 +0,0 @@
|
||||
import axios from 'axios';
|
||||
import {
|
||||
AuthLoginRequest,
|
||||
AuthSignUpRequest,
|
||||
BaseRequest,
|
||||
BaseResponse,
|
||||
CreateFileRequest,
|
||||
CreateFileResponse,
|
||||
CreateFolderRequest,
|
||||
CreateFolderResponse,
|
||||
DeleteRequest,
|
||||
DeleteResponse,
|
||||
ErrorResponse,
|
||||
GetNodeResponse,
|
||||
GetPathResponse,
|
||||
GetRootResponse,
|
||||
LoginResponse,
|
||||
RefreshResponse,
|
||||
SignupResponse,
|
||||
UploadFileResponse
|
||||
} from '../../dto';
|
||||
import jwtDecode, { JwtPayload } from 'jwt-decode';
|
||||
import { Ref, UnwrapRef } from 'vue';
|
||||
|
||||
export * from '../../dto';
|
||||
|
||||
const post = <T extends BaseRequest>(url: string, data: T) =>
|
||||
axios
|
||||
.post(url, data, {
|
||||
headers: { 'Content-type': 'application/json' }
|
||||
})
|
||||
.then((res) => res.data)
|
||||
.catch((err) => err.response.data);
|
||||
|
||||
const post_token = <T extends BaseRequest>(
|
||||
url: string,
|
||||
data: T,
|
||||
token: string
|
||||
) =>
|
||||
axios
|
||||
.post(url, data, {
|
||||
headers: {
|
||||
Authorization: 'Bearer ' + token,
|
||||
'Content-type': 'application/json'
|
||||
}
|
||||
})
|
||||
.then((res) => res.data)
|
||||
.catch((err) => err.response.data);
|
||||
|
||||
const post_token_form = (
|
||||
url: string,
|
||||
data: FormData,
|
||||
token: string,
|
||||
onProgress: (progressEvent: ProgressEvent) => void
|
||||
) =>
|
||||
axios
|
||||
.post(url, data, {
|
||||
headers: {
|
||||
Authorization: 'Bearer ' + token,
|
||||
'Content-type': 'multipart/form-data'
|
||||
},
|
||||
onUploadProgress: onProgress
|
||||
})
|
||||
.then((res) => res.data)
|
||||
.catch((err) => err.response.data);
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
const get = (url: string) =>
|
||||
axios
|
||||
.get(url)
|
||||
.then((res) => res.data)
|
||||
.catch((err) => err.response.data);
|
||||
|
||||
const get_token = (url: string, token: string) =>
|
||||
axios
|
||||
.get(url, {
|
||||
headers: { Authorization: 'Bearer ' + token }
|
||||
})
|
||||
.then((res) => res.data)
|
||||
.catch((err) => err.response.data);
|
||||
|
||||
//
|
||||
// Api Requests
|
||||
//
|
||||
|
||||
export const auth_login = (
|
||||
username: string,
|
||||
password: string
|
||||
): Promise<LoginResponse | ErrorResponse> =>
|
||||
post<AuthLoginRequest>('/api/auth/login', {
|
||||
username: username,
|
||||
password: password
|
||||
});
|
||||
|
||||
export const auth_signup = (
|
||||
username: string,
|
||||
password: string
|
||||
): Promise<SignupResponse | ErrorResponse> =>
|
||||
post<AuthSignUpRequest>('/api/auth/signup', {
|
||||
username: username,
|
||||
password: password
|
||||
});
|
||||
|
||||
export const get_root = (
|
||||
token: string
|
||||
): Promise<GetRootResponse | ErrorResponse> => get_token('/api/fs/root', token);
|
||||
|
||||
export const get_node = (
|
||||
token: string,
|
||||
node: number
|
||||
): Promise<GetNodeResponse | ErrorResponse> =>
|
||||
get_token(`/api/fs/node/${node}`, token);
|
||||
|
||||
export const get_path = (
|
||||
token: string,
|
||||
node: number
|
||||
): Promise<GetPathResponse | ErrorResponse> =>
|
||||
get_token(`/api/fs/path/${node}`, token);
|
||||
|
||||
export const create_folder = (
|
||||
token: string,
|
||||
parent: number,
|
||||
name: string
|
||||
): Promise<CreateFolderResponse | ErrorResponse> =>
|
||||
post_token<CreateFolderRequest>(
|
||||
'/api/fs/createFolder',
|
||||
{
|
||||
parent: parent,
|
||||
name: name
|
||||
},
|
||||
token
|
||||
);
|
||||
|
||||
export const create_file = (
|
||||
token: string,
|
||||
parent: number,
|
||||
name: string
|
||||
): Promise<CreateFileResponse | ErrorResponse> =>
|
||||
post_token<CreateFileRequest>(
|
||||
'/api/fs/createFile',
|
||||
{
|
||||
parent: parent,
|
||||
name: name
|
||||
},
|
||||
token
|
||||
);
|
||||
|
||||
export const delete_node = (
|
||||
token: string,
|
||||
node: number
|
||||
): Promise<DeleteResponse | ErrorResponse> =>
|
||||
post_token<DeleteRequest>(
|
||||
'/api/fs/delete',
|
||||
{
|
||||
node: node
|
||||
},
|
||||
token
|
||||
);
|
||||
|
||||
export const upload_file = async (
|
||||
token: string,
|
||||
parent: number,
|
||||
file: File,
|
||||
onProgress: (progressEvent: ProgressEvent) => void
|
||||
): Promise<UploadFileResponse | ErrorResponse> => {
|
||||
const node = await create_file(token, parent, file.name);
|
||||
if (isErrorResponse(node)) return node;
|
||||
|
||||
const form = new FormData();
|
||||
form.set('file', file);
|
||||
return post_token_form(
|
||||
`/api/fs/upload/${node.id}`,
|
||||
form,
|
||||
token,
|
||||
onProgress
|
||||
);
|
||||
};
|
||||
|
||||
export function download_file(token: string, id: number) {
|
||||
const form = document.createElement('form');
|
||||
form.method = 'post';
|
||||
form.target = '_blank';
|
||||
form.action = '/api/fs/download';
|
||||
form.innerHTML = `<input type="hidden" name="jwtToken" value="${token}"><input type="hidden" name="id" value="${id}">`;
|
||||
document.body.appendChild(form);
|
||||
form.submit();
|
||||
document.body.removeChild(form);
|
||||
}
|
||||
|
||||
export const refresh_token = (
|
||||
token: string
|
||||
): Promise<RefreshResponse | ErrorResponse> =>
|
||||
post_token('/api/auth/refresh', '', token);
|
||||
|
||||
//
|
||||
// Utilities
|
||||
//
|
||||
|
||||
export async function check_token(
|
||||
token: TokenInjectType
|
||||
): Promise<string | void> {
|
||||
if (!token.jwt.value) return token.logout();
|
||||
const payload = jwtDecode<JwtPayload>(token.jwt.value);
|
||||
if (!payload) return token.logout();
|
||||
// Expires in more than 60 Minute
|
||||
if (payload.exp && payload.exp > Math.floor(Date.now() / 1000 + 60 * 60))
|
||||
return token.jwt.value;
|
||||
const new_token = await refresh_token(token.jwt.value);
|
||||
if (isErrorResponse(new_token)) return token.logout();
|
||||
token.setToken(new_token.jwt);
|
||||
return new_token.jwt;
|
||||
}
|
||||
|
||||
export const isErrorResponse = (res: BaseResponse): res is ErrorResponse =>
|
||||
res.statusCode != 200;
|
||||
|
||||
export type TokenInjectType = {
|
||||
jwt: Ref<UnwrapRef<string | null>>;
|
||||
setToken: (token: string) => void;
|
||||
logout: () => void;
|
||||
};
|
30
frontend/src/api/auth.ts
Normal file
30
frontend/src/api/auth.ts
Normal file
@ -0,0 +1,30 @@
|
||||
import { Responses, Requests, post, post_token } from './base';
|
||||
|
||||
export const auth_login = (
|
||||
username: string,
|
||||
password: string,
|
||||
otp?: string
|
||||
): Promise<
|
||||
| Responses.Auth.LoginResponse
|
||||
| Responses.Auth.TfaRequiredResponse
|
||||
| Responses.ErrorResponse
|
||||
> =>
|
||||
post<Requests.Auth.AuthLoginRequest>('/api/auth/login', {
|
||||
username: username,
|
||||
password: password,
|
||||
otp: otp
|
||||
});
|
||||
|
||||
export const auth_signup = (
|
||||
username: string,
|
||||
password: string
|
||||
): Promise<Responses.Auth.SignupResponse | Responses.ErrorResponse> =>
|
||||
post<Requests.Auth.AuthSignUpRequest>('/api/auth/signup', {
|
||||
username: username,
|
||||
password: password
|
||||
});
|
||||
|
||||
export const refresh_token = (
|
||||
token: string
|
||||
): Promise<Responses.Auth.RefreshResponse | Responses.ErrorResponse> =>
|
||||
post_token('/api/auth/refresh', '', token);
|
63
frontend/src/api/base.ts
Normal file
63
frontend/src/api/base.ts
Normal file
@ -0,0 +1,63 @@
|
||||
import axios from 'axios';
|
||||
import { Requests, Responses } from '../../../dto';
|
||||
|
||||
export * from '../../../dto';
|
||||
|
||||
export const post = <T extends Requests.BaseRequest>(url: string, data: T) =>
|
||||
axios
|
||||
.post(url, data, {
|
||||
headers: { 'Content-type': 'application/json' }
|
||||
})
|
||||
.then((res) => res.data)
|
||||
.catch((err) => err.response.data);
|
||||
|
||||
export const post_token = <T extends Requests.BaseRequest>(
|
||||
url: string,
|
||||
data: T,
|
||||
token: string
|
||||
) =>
|
||||
axios
|
||||
.post(url, data, {
|
||||
headers: {
|
||||
Authorization: 'Bearer ' + token,
|
||||
'Content-type': 'application/json'
|
||||
}
|
||||
})
|
||||
.then((res) => res.data)
|
||||
.catch((err) => err.response.data);
|
||||
|
||||
export const post_token_form = (
|
||||
url: string,
|
||||
data: FormData,
|
||||
token: string,
|
||||
onProgress: (progressEvent: ProgressEvent) => void
|
||||
) =>
|
||||
axios
|
||||
.post(url, data, {
|
||||
headers: {
|
||||
Authorization: 'Bearer ' + token,
|
||||
'Content-type': 'multipart/form-data'
|
||||
},
|
||||
onUploadProgress: onProgress
|
||||
})
|
||||
.then((res) => res.data)
|
||||
.catch((err) => err.response.data);
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
export const get = (url: string) =>
|
||||
axios
|
||||
.get(url)
|
||||
.then((res) => res.data)
|
||||
.catch((err) => err.response.data);
|
||||
|
||||
export const get_token = (url: string, token: string) =>
|
||||
axios
|
||||
.get(url, {
|
||||
headers: { Authorization: 'Bearer ' + token }
|
||||
})
|
||||
.then((res) => res.data)
|
||||
.catch((err) => err.response.data);
|
||||
|
||||
export const isErrorResponse = (
|
||||
res: Responses.BaseResponse
|
||||
): res is Responses.ErrorResponse => res.statusCode != 200;
|
95
frontend/src/api/fs.ts
Normal file
95
frontend/src/api/fs.ts
Normal file
@ -0,0 +1,95 @@
|
||||
import {
|
||||
Responses,
|
||||
Requests,
|
||||
get_token,
|
||||
post_token,
|
||||
post_token_form,
|
||||
isErrorResponse
|
||||
} from './base';
|
||||
|
||||
export const get_root = (
|
||||
token: string
|
||||
): Promise<Responses.FS.GetRootResponse | Responses.ErrorResponse> =>
|
||||
get_token('/api/fs/root', token);
|
||||
|
||||
export const get_node = (
|
||||
token: string,
|
||||
node: number
|
||||
): Promise<Responses.FS.GetNodeResponse | Responses.ErrorResponse> =>
|
||||
get_token(`/api/fs/node/${node}`, token);
|
||||
|
||||
export const get_path = (
|
||||
token: string,
|
||||
node: number
|
||||
): Promise<Responses.FS.GetPathResponse | Responses.ErrorResponse> =>
|
||||
get_token(`/api/fs/path/${node}`, token);
|
||||
|
||||
export const create_folder = (
|
||||
token: string,
|
||||
parent: number,
|
||||
name: string
|
||||
): Promise<Responses.FS.CreateFolderResponse | Responses.ErrorResponse> =>
|
||||
post_token<Requests.FS.CreateFolderRequest>(
|
||||
'/api/fs/createFolder',
|
||||
{
|
||||
parent: parent,
|
||||
name: name
|
||||
},
|
||||
token
|
||||
);
|
||||
|
||||
export const create_file = (
|
||||
token: string,
|
||||
parent: number,
|
||||
name: string
|
||||
): Promise<Responses.FS.CreateFileResponse | Responses.ErrorResponse> =>
|
||||
post_token<Requests.FS.CreateFileRequest>(
|
||||
'/api/fs/createFile',
|
||||
{
|
||||
parent: parent,
|
||||
name: name
|
||||
},
|
||||
token
|
||||
);
|
||||
|
||||
export const delete_node = (
|
||||
token: string,
|
||||
node: number
|
||||
): Promise<Responses.FS.DeleteResponse | Responses.ErrorResponse> =>
|
||||
post_token<Requests.FS.DeleteRequest>(
|
||||
'/api/fs/delete',
|
||||
{
|
||||
node: node
|
||||
},
|
||||
token
|
||||
);
|
||||
|
||||
export const upload_file = async (
|
||||
token: string,
|
||||
parent: number,
|
||||
file: File,
|
||||
onProgress: (progressEvent: ProgressEvent) => void
|
||||
): Promise<Responses.FS.UploadFileResponse | Responses.ErrorResponse> => {
|
||||
const node = await create_file(token, parent, file.name);
|
||||
if (isErrorResponse(node)) return node;
|
||||
|
||||
const form = new FormData();
|
||||
form.set('file', file);
|
||||
return post_token_form(
|
||||
`/api/fs/upload/${node.id}`,
|
||||
form,
|
||||
token,
|
||||
onProgress
|
||||
);
|
||||
};
|
||||
|
||||
export function download_file(token: string, id: number) {
|
||||
const form = document.createElement('form');
|
||||
form.method = 'post';
|
||||
form.target = '_blank';
|
||||
form.action = '/api/fs/download';
|
||||
form.innerHTML = `<input type="hidden" name="jwtToken" value="${token}"><input type="hidden" name="id" value="${id}">`;
|
||||
document.body.appendChild(form);
|
||||
form.submit();
|
||||
document.body.removeChild(form);
|
||||
}
|
4
frontend/src/api/index.ts
Normal file
4
frontend/src/api/index.ts
Normal file
@ -0,0 +1,4 @@
|
||||
export { Requests, Responses, isErrorResponse } from './base';
|
||||
export * as Auth from './auth';
|
||||
export * as FS from './fs';
|
||||
export * from './util';
|
25
frontend/src/api/util.ts
Normal file
25
frontend/src/api/util.ts
Normal file
@ -0,0 +1,25 @@
|
||||
import jwtDecode, { JwtPayload } from 'jwt-decode';
|
||||
import { Ref, UnwrapRef } from 'vue';
|
||||
import { isErrorResponse } from './base';
|
||||
import { refresh_token } from './auth';
|
||||
|
||||
export async function check_token(
|
||||
token: TokenInjectType
|
||||
): Promise<string | void> {
|
||||
if (!token.jwt.value) return token.logout();
|
||||
const payload = jwtDecode<JwtPayload>(token.jwt.value);
|
||||
if (!payload) return token.logout();
|
||||
// Expires in more than 60 Minute
|
||||
if (payload.exp && payload.exp > Math.floor(Date.now() / 1000 + 60 * 60))
|
||||
return token.jwt.value;
|
||||
const new_token = await refresh_token(token.jwt.value);
|
||||
if (isErrorResponse(new_token)) return token.logout();
|
||||
token.setToken(new_token.jwt);
|
||||
return new_token.jwt;
|
||||
}
|
||||
|
||||
export type TokenInjectType = {
|
||||
jwt: Ref<UnwrapRef<string | null>>;
|
||||
setToken: (token: string) => void;
|
||||
logout: () => void;
|
||||
};
|
@ -1,16 +1,10 @@
|
||||
<script setup lang="ts">
|
||||
import { defineEmits, defineProps, inject } from 'vue';
|
||||
import {
|
||||
check_token,
|
||||
delete_node,
|
||||
download_file,
|
||||
GetNodeResponse,
|
||||
TokenInjectType
|
||||
} from '@/api';
|
||||
import { check_token, FS, Responses, TokenInjectType } from '@/api';
|
||||
|
||||
const jwt = inject<TokenInjectType>('jwt') as TokenInjectType;
|
||||
const props = defineProps<{
|
||||
node: GetNodeResponse;
|
||||
node: Responses.FS.GetNodeResponse;
|
||||
}>();
|
||||
|
||||
const emit = defineEmits<{
|
||||
@ -20,14 +14,14 @@ const emit = defineEmits<{
|
||||
async function del() {
|
||||
const token = await check_token(jwt);
|
||||
if (!token) return;
|
||||
await delete_node(token, props.node.id);
|
||||
await FS.delete_node(token, props.node.id);
|
||||
emit('reloadNode');
|
||||
}
|
||||
|
||||
async function download() {
|
||||
const token = await check_token(jwt);
|
||||
if (!token) return;
|
||||
download_file(token, props.node.id);
|
||||
FS.download_file(token, props.node.id);
|
||||
}
|
||||
</script>
|
||||
|
||||
|
@ -1,18 +1,12 @@
|
||||
<script setup lang="ts">
|
||||
import { defineEmits, defineProps, inject, reactive, ref, watch } from 'vue';
|
||||
import {
|
||||
GetNodeResponse,
|
||||
create_folder,
|
||||
get_node,
|
||||
check_token,
|
||||
TokenInjectType
|
||||
} from '@/api';
|
||||
import { FS, Responses, check_token, TokenInjectType } from '@/api';
|
||||
import DirEntry from '@/components/FSView/DirEntry.vue';
|
||||
import UploadFileDialog from '@/components/UploadDialog/UploadFileDialog.vue';
|
||||
import { NModal } from 'naive-ui';
|
||||
|
||||
const props = defineProps<{
|
||||
node: GetNodeResponse;
|
||||
node: Responses.FS.GetNodeResponse;
|
||||
}>();
|
||||
|
||||
const jwt = inject<TokenInjectType>('jwt') as TokenInjectType;
|
||||
@ -28,9 +22,9 @@ const uploadDialogShow = ref(false);
|
||||
|
||||
const new_folder_name = ref('');
|
||||
const files = ref<File[]>([]);
|
||||
const nodes = ref<GetNodeResponse[]>([]);
|
||||
const nodes = ref<Responses.FS.GetNodeResponse[]>([]);
|
||||
const hasParent = ref(false);
|
||||
const parentNode = reactive<GetNodeResponse>({
|
||||
const parentNode = reactive<Responses.FS.GetNodeResponse>({
|
||||
id: 0,
|
||||
statusCode: 200,
|
||||
isFile: false,
|
||||
@ -49,7 +43,10 @@ watch(
|
||||
await Promise.all(
|
||||
to.children?.map(async (child) => {
|
||||
nodes.value.push(
|
||||
(await get_node(token, child)) as GetNodeResponse
|
||||
(await FS.get_node(
|
||||
token,
|
||||
child
|
||||
)) as Responses.FS.GetNodeResponse
|
||||
);
|
||||
}) ?? []
|
||||
);
|
||||
@ -60,7 +57,7 @@ watch(
|
||||
async function newFolder() {
|
||||
const token = await check_token(jwt);
|
||||
if (!token) return;
|
||||
await create_folder(token, props.node.id, new_folder_name.value);
|
||||
await FS.create_folder(token, props.node.id, new_folder_name.value);
|
||||
emit('reloadNode');
|
||||
}
|
||||
|
||||
|
@ -1,15 +1,9 @@
|
||||
<script setup lang="ts">
|
||||
import { defineProps, inject } from 'vue';
|
||||
import {
|
||||
check_token,
|
||||
delete_node,
|
||||
download_file,
|
||||
GetNodeResponse,
|
||||
TokenInjectType
|
||||
} from '@/api';
|
||||
import { check_token, FS, Responses, TokenInjectType } from '@/api';
|
||||
|
||||
const props = defineProps<{
|
||||
node: GetNodeResponse;
|
||||
node: Responses.FS.GetNodeResponse;
|
||||
}>();
|
||||
|
||||
const jwt = inject<TokenInjectType>('jwt') as TokenInjectType;
|
||||
@ -17,13 +11,13 @@ const jwt = inject<TokenInjectType>('jwt') as TokenInjectType;
|
||||
async function del() {
|
||||
const token = await check_token(jwt);
|
||||
if (!token) return;
|
||||
await delete_node(token, props.node.id);
|
||||
await FS.delete_node(token, props.node.id);
|
||||
}
|
||||
|
||||
async function download() {
|
||||
const token = await check_token(jwt);
|
||||
if (!token) return;
|
||||
download_file(token, props.node.id);
|
||||
FS.download_file(token, props.node.id);
|
||||
}
|
||||
</script>
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
<script setup lang="ts">
|
||||
import { defineProps, defineExpose, ref } from 'vue';
|
||||
import { isErrorResponse, upload_file } from '@/api';
|
||||
import { isErrorResponse, FS } from '@/api';
|
||||
import { NProgress } from 'naive-ui';
|
||||
import filesize from 'filesize';
|
||||
|
||||
@ -14,7 +14,7 @@ const err = ref('');
|
||||
const status = ref('info');
|
||||
|
||||
async function startUpload(parent: number, token: string) {
|
||||
const resp = await upload_file(token, parent, props.file, (e) => {
|
||||
const resp = await FS.upload_file(token, parent, props.file, (e) => {
|
||||
progress.value = e.loaded;
|
||||
percentage.value = (e.loaded / e.total) * 100;
|
||||
});
|
||||
|
@ -3,10 +3,8 @@ import { onBeforeRouteUpdate, useRoute, useRouter } from 'vue-router';
|
||||
import { inject, onBeforeMount, ref } from 'vue';
|
||||
import {
|
||||
check_token,
|
||||
get_node,
|
||||
get_path,
|
||||
get_root,
|
||||
GetNodeResponse,
|
||||
FS,
|
||||
Responses,
|
||||
isErrorResponse,
|
||||
TokenInjectType
|
||||
} from '@/api';
|
||||
@ -18,14 +16,14 @@ const route = useRoute();
|
||||
const jwt = inject<TokenInjectType>('jwt') as TokenInjectType;
|
||||
|
||||
const path = ref('');
|
||||
const node = ref<GetNodeResponse | null>(null);
|
||||
const node = ref<Responses.FS.GetNodeResponse | null>(null);
|
||||
|
||||
async function fetch_node(node_id: number) {
|
||||
const token = await check_token(jwt);
|
||||
if (!token) return;
|
||||
let [p, n] = [
|
||||
await get_path(token, node_id),
|
||||
await get_node(token, node_id)
|
||||
await FS.get_path(token, node_id),
|
||||
await FS.get_node(token, node_id)
|
||||
];
|
||||
if (isErrorResponse(p)) return gotoRoot();
|
||||
if (isErrorResponse(n)) return gotoRoot();
|
||||
@ -46,7 +44,7 @@ onBeforeMount(async () => {
|
||||
async function gotoRoot() {
|
||||
const token = await check_token(jwt);
|
||||
if (!token) return;
|
||||
const rootRes = await get_root(token);
|
||||
const rootRes = await FS.get_root(token);
|
||||
if (isErrorResponse(rootRes)) return jwt.logout();
|
||||
const root = rootRes.rootId;
|
||||
await router.replace({
|
||||
|
@ -3,7 +3,7 @@
|
||||
<script setup lang="ts">
|
||||
import { onBeforeRouteUpdate, useRouter } from 'vue-router';
|
||||
import { inject, onBeforeMount } from 'vue';
|
||||
import { check_token, get_root, isErrorResponse, TokenInjectType } from '@/api';
|
||||
import { FS, check_token, isErrorResponse, TokenInjectType } from '@/api';
|
||||
|
||||
const router = useRouter();
|
||||
const jwt = inject<TokenInjectType>('jwt') as TokenInjectType;
|
||||
@ -11,7 +11,7 @@ const jwt = inject<TokenInjectType>('jwt') as TokenInjectType;
|
||||
async function start_redirect() {
|
||||
const token = await check_token(jwt);
|
||||
if (!token) return;
|
||||
const root = await get_root(token);
|
||||
const root = await FS.get_root(token);
|
||||
if (isErrorResponse(root)) return jwt.logout();
|
||||
await router.push({
|
||||
name: 'fs',
|
||||
|
@ -1,25 +1,32 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, inject } from 'vue';
|
||||
import { auth_login, get_root, isErrorResponse, TokenInjectType } from '@/api';
|
||||
import { Auth, FS, isErrorResponse, TokenInjectType } from '@/api';
|
||||
import { useRouter } from 'vue-router';
|
||||
|
||||
const router = useRouter();
|
||||
|
||||
let username = ref('');
|
||||
let password = ref('');
|
||||
const username = ref('');
|
||||
const password = ref('');
|
||||
const otp = ref('');
|
||||
|
||||
const error = ref('');
|
||||
|
||||
const requestOtp = ref(false);
|
||||
|
||||
const jwt = inject<TokenInjectType>('jwt') as TokenInjectType;
|
||||
|
||||
async function login() {
|
||||
error.value = '';
|
||||
if (username.value === '' || password.value === '') {
|
||||
error.value = 'Email and/or Password missing';
|
||||
return;
|
||||
}
|
||||
const res = await auth_login(username.value, password.value);
|
||||
const res = await (requestOtp.value
|
||||
? Auth.auth_login(username.value, password.value, otp.value)
|
||||
: Auth.auth_login(username.value, password.value));
|
||||
if (isErrorResponse(res)) error.value = 'Login failed: ' + res.message;
|
||||
else {
|
||||
const root = await get_root(res.jwt);
|
||||
else if ('jwt' in res) {
|
||||
const root = await FS.get_root(res.jwt);
|
||||
if (isErrorResponse(root)) {
|
||||
error.value = 'Get root failed: ' + root.message;
|
||||
return;
|
||||
@ -29,14 +36,23 @@ async function login() {
|
||||
name: 'fs',
|
||||
params: { node_id: root.rootId }
|
||||
});
|
||||
} else {
|
||||
error.value = '';
|
||||
requestOtp.value = true;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div v-if="error !== ''" v-text="error"></div>
|
||||
<input type="email" placeholder="Email" v-model="username" />
|
||||
<input type="password" placeholder="Password" v-model="password" />
|
||||
<template v-if="!requestOtp">
|
||||
<input type="email" placeholder="Email" v-model="username" />
|
||||
<input type="password" placeholder="Password" v-model="password" />
|
||||
</template>
|
||||
<template v-else>
|
||||
<div>Please input your 2 factor authentication code</div>
|
||||
<input type="text" placeholder="Code" v-model="otp" />
|
||||
</template>
|
||||
<button @click="login()">Login</button>
|
||||
<router-link to="signup">Signup instead?</router-link>
|
||||
</template>
|
||||
|
@ -1,6 +1,6 @@
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue';
|
||||
import { auth_signup, isErrorResponse } from '@/api';
|
||||
import { Auth, isErrorResponse } from '@/api';
|
||||
|
||||
let username = ref('');
|
||||
let password = ref('');
|
||||
@ -16,7 +16,7 @@ async function signup() {
|
||||
error.value = "Passwords don't match";
|
||||
return;
|
||||
}
|
||||
const res = await auth_signup(username.value, password.value);
|
||||
const res = await Auth.auth_signup(username.value, password.value);
|
||||
error.value = isErrorResponse(res)
|
||||
? 'Signup failed: ' + res.message
|
||||
: 'Signup successful, please wait till an admin unlocks your account.';
|
||||
|
@ -41,6 +41,7 @@
|
||||
"reflect-metadata": "^0.1.13",
|
||||
"rxjs": "^7.5.6",
|
||||
"sqlite3": "^5.0.11",
|
||||
"thirty-two": "^1.0.2",
|
||||
"typeorm": "^0.3.7"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
@ -2,20 +2,21 @@ import {
|
||||
BadRequestException,
|
||||
Body,
|
||||
Controller,
|
||||
Get,
|
||||
HttpCode,
|
||||
ParseBoolPipe,
|
||||
Post,
|
||||
Request,
|
||||
UnauthorizedException,
|
||||
UseGuards
|
||||
} from '@nestjs/common';
|
||||
import { AuthService } from '../services/auth';
|
||||
import { AuthGuard } from '@nestjs/passport';
|
||||
import { Public } from '../authguards';
|
||||
import {
|
||||
ErrorResponse,
|
||||
LoginResponse,
|
||||
RefreshResponse,
|
||||
SignupResponse
|
||||
} from 'dto';
|
||||
import { Responses } from 'dto';
|
||||
import { tfaTypes } from '../entities';
|
||||
import { toDataURL } from 'qrcode';
|
||||
import * as base32 from 'thirty-two';
|
||||
|
||||
@Controller('api/auth')
|
||||
export default class AuthController {
|
||||
@ -25,19 +26,90 @@ export default class AuthController {
|
||||
@UseGuards(AuthGuard('local'))
|
||||
@Post('login')
|
||||
@HttpCode(200)
|
||||
async login(@Request() req): Promise<LoginResponse> {
|
||||
async login(
|
||||
@Request() req,
|
||||
@Body('otp') otp?: string
|
||||
): Promise<
|
||||
Responses.Auth.LoginResponse | Responses.Auth.TfaRequiredResponse
|
||||
> {
|
||||
if (this.authService.requiresTfa(req.user)) {
|
||||
if (!otp) {
|
||||
if (req.user.tfaType == tfaTypes.EMAIL)
|
||||
await this.authService.sendTfaMail(req.user);
|
||||
return {
|
||||
statusCode: 200
|
||||
};
|
||||
}
|
||||
if (!(await this.authService.verifyTfa(req.user, otp)))
|
||||
throw new UnauthorizedException('Incorrect 2fa');
|
||||
}
|
||||
return {
|
||||
statusCode: 200,
|
||||
jwt: await this.authService.login(req.user)
|
||||
};
|
||||
}
|
||||
|
||||
async tfa(
|
||||
req,
|
||||
code: string,
|
||||
type: tfaTypes
|
||||
): Promise<Responses.Auth.TfaCompletedResponse> {
|
||||
if (!(await this.authService.verifyTfa(req.user, code, type))) {
|
||||
throw new UnauthorizedException('Incorrect 2fa');
|
||||
}
|
||||
await this.authService.setTfaType(req.user, type);
|
||||
await this.authService.revokeAll(req.user);
|
||||
return {
|
||||
statusCode: 200
|
||||
};
|
||||
}
|
||||
|
||||
@Post('2fa/complete/mail')
|
||||
async tfaMail(
|
||||
@Request() req,
|
||||
@Body('code') code: string
|
||||
): Promise<Responses.Auth.TfaCompletedResponse> {
|
||||
return await this.tfa(req, code, tfaTypes.EMAIL);
|
||||
}
|
||||
|
||||
@Post('2fa/complete/totp')
|
||||
async tfaTotp(
|
||||
@Request() req,
|
||||
@Body('code') code: string
|
||||
): Promise<Responses.Auth.TfaCompletedResponse> {
|
||||
return await this.tfa(req, code, tfaTypes.TOTP);
|
||||
}
|
||||
|
||||
@Get('2fa/setup')
|
||||
async setupTotp(
|
||||
@Request() req,
|
||||
@Body('mail', ParseBoolPipe) mail: boolean
|
||||
): Promise<
|
||||
| Responses.Auth.RequestTotpTfaResponse
|
||||
| Responses.Auth.RequestEmailTfaResponse
|
||||
> {
|
||||
const secret = await this.authService.setupTfa(req.user);
|
||||
if (mail)
|
||||
return {
|
||||
statusCode: 200
|
||||
};
|
||||
return {
|
||||
statusCode: 200,
|
||||
qrCode: await toDataURL(
|
||||
`otpauth://totp/MFileserver:${req.user.name}?secret=${base32
|
||||
.encode(secret)
|
||||
.toString()}&issuer=MFileserver`
|
||||
),
|
||||
secret
|
||||
};
|
||||
}
|
||||
|
||||
@Public()
|
||||
@Post('signup')
|
||||
async signup(
|
||||
@Body('username') username,
|
||||
@Body('password') password
|
||||
): Promise<SignupResponse | ErrorResponse> {
|
||||
): Promise<Responses.Auth.SignupResponse> {
|
||||
if ((await this.authService.findUser(username)) != null)
|
||||
throw new BadRequestException('Username already taken');
|
||||
await this.authService.signup(username, password);
|
||||
@ -47,7 +119,7 @@ export default class AuthController {
|
||||
}
|
||||
|
||||
@Post('refresh')
|
||||
async refresh(@Request() req): Promise<RefreshResponse | ErrorResponse> {
|
||||
async refresh(@Request() req): Promise<Responses.Auth.RefreshResponse> {
|
||||
const token = await this.authService.login(req.user);
|
||||
await this.authService.revoke(req.token);
|
||||
return {
|
||||
|
@ -8,15 +8,7 @@ import {
|
||||
Request,
|
||||
StreamableFile
|
||||
} from '@nestjs/common';
|
||||
import {
|
||||
CreateFileResponse,
|
||||
CreateFolderResponse,
|
||||
DeleteResponse,
|
||||
GetNodeResponse,
|
||||
GetPathResponse,
|
||||
GetRootResponse,
|
||||
UploadFileResponse
|
||||
} from 'dto/index';
|
||||
import { Responses } from 'dto';
|
||||
import FileSystemService from '../services/filesystem';
|
||||
import { UserRole } from '../entities';
|
||||
import { Role } from '../authguards';
|
||||
@ -27,7 +19,7 @@ export default class FileSystemController {
|
||||
|
||||
@Get('root')
|
||||
@Role(UserRole.USER)
|
||||
async getRoot(@Request() req): Promise<GetRootResponse> {
|
||||
async getRoot(@Request() req): Promise<Responses.FS.GetRootResponse> {
|
||||
return {
|
||||
statusCode: 200,
|
||||
rootId: req.user.rootId
|
||||
@ -39,9 +31,9 @@ export default class FileSystemController {
|
||||
async getNode(
|
||||
@Request() req,
|
||||
@Param('node', ParseIntPipe) nodeId
|
||||
): Promise<GetNodeResponse> {
|
||||
): Promise<Responses.FS.GetNodeResponse> {
|
||||
const node = await this.fsService.getNodeAndValidate(nodeId, req.user);
|
||||
const data: GetNodeResponse = {
|
||||
const data: Responses.FS.GetNodeResponse = {
|
||||
id: nodeId,
|
||||
statusCode: 200,
|
||||
name: node.name,
|
||||
@ -61,7 +53,7 @@ export default class FileSystemController {
|
||||
async getPath(
|
||||
@Request() req,
|
||||
@Param('node', ParseIntPipe) nodeId
|
||||
): Promise<GetPathResponse> {
|
||||
): Promise<Responses.FS.GetPathResponse> {
|
||||
return {
|
||||
statusCode: 200,
|
||||
path: await this.fsService.generatePath(
|
||||
@ -76,7 +68,7 @@ export default class FileSystemController {
|
||||
@Request() req,
|
||||
@Body('parent', ParseIntPipe) parent,
|
||||
@Body('name') name
|
||||
): Promise<CreateFolderResponse> {
|
||||
): Promise<Responses.FS.CreateFolderResponse> {
|
||||
const newChild = await this.fsService.create(
|
||||
await this.fsService.getNodeAndValidate(parent, req.user),
|
||||
name,
|
||||
@ -95,7 +87,7 @@ export default class FileSystemController {
|
||||
@Request() req,
|
||||
@Body('parent', ParseIntPipe) parent,
|
||||
@Body('name') name
|
||||
): Promise<CreateFileResponse> {
|
||||
): Promise<Responses.FS.CreateFileResponse> {
|
||||
const newChild = await this.fsService.create(
|
||||
await this.fsService.getNodeAndValidate(parent, req.user),
|
||||
name,
|
||||
@ -113,7 +105,7 @@ export default class FileSystemController {
|
||||
async delete(
|
||||
@Request() req,
|
||||
@Body('node', ParseIntPipe) node_id
|
||||
): Promise<DeleteResponse> {
|
||||
): Promise<Responses.FS.DeleteResponse> {
|
||||
await this.fsService.delete(
|
||||
await this.fsService.getNodeAndValidate(node_id, req.user)
|
||||
);
|
||||
@ -125,7 +117,7 @@ export default class FileSystemController {
|
||||
async upload(
|
||||
@Request() req,
|
||||
@Param('node', ParseIntPipe) nodeId
|
||||
): Promise<UploadFileResponse> {
|
||||
): Promise<Responses.FS.UploadFileResponse> {
|
||||
await this.fsService.uploadFile(await req.file(), nodeId, req.user);
|
||||
return { statusCode: 200 };
|
||||
}
|
||||
|
@ -76,7 +76,6 @@ export class User {
|
||||
})
|
||||
tfaType: tfaTypes;
|
||||
|
||||
// base32 string
|
||||
@Column({ nullable: true })
|
||||
tfaSecret: string;
|
||||
}
|
||||
|
@ -4,17 +4,27 @@ import {
|
||||
UnauthorizedException
|
||||
} from '@nestjs/common';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
import { JWTToken, User, UserRole } from '../entities';
|
||||
import { Repository, LessThanOrEqual } from 'typeorm';
|
||||
import { JWTToken, tfaTypes, User, UserRole } from '../entities';
|
||||
import { LessThanOrEqual, Repository } from 'typeorm';
|
||||
import * as argon2 from 'argon2';
|
||||
import { PassportStrategy } from '@nestjs/passport';
|
||||
import { Strategy as LocalStrategy } from 'passport-local';
|
||||
import { ExtractJwt, Strategy as JWTStrategy } from 'passport-jwt';
|
||||
import FileSystemService from './filesystem';
|
||||
import * as jwt from 'jsonwebtoken';
|
||||
import { createTransport } from 'nodemailer';
|
||||
import * as notp from 'notp';
|
||||
import { randomBytes } from 'crypto';
|
||||
|
||||
const jwtSecret = 'CUM';
|
||||
|
||||
const mailAccount = createTransport({
|
||||
host: 'smtp.web.de',
|
||||
port: 587,
|
||||
secure: false,
|
||||
auth: require('M:/projects/file_server/dist/auth.json')
|
||||
});
|
||||
|
||||
interface jwtPayload {
|
||||
sub: number;
|
||||
jti: number;
|
||||
@ -32,6 +42,16 @@ export class AuthService {
|
||||
private fsService: FileSystemService
|
||||
) {}
|
||||
|
||||
generateTfaSecret(): string {
|
||||
const set =
|
||||
'0123456789ABCDEFGHIJKLMNOPQRSTUVWXTZabcdefghiklmnopqrstuvwxyz!@#$%^&*()<>?/[]{},.:;';
|
||||
return randomBytes(32)
|
||||
.map((b) =>
|
||||
set.charCodeAt(Math.floor((b / 255.0) * (set.length - 1)))
|
||||
)
|
||||
.toString();
|
||||
}
|
||||
|
||||
async getUser(userId: number): Promise<User | null> {
|
||||
return this.userRepo.findOneBy({
|
||||
id: userId
|
||||
@ -67,6 +87,52 @@ export class AuthService {
|
||||
});
|
||||
}
|
||||
|
||||
requiresTfa(user: User): boolean {
|
||||
return user.tfaType != tfaTypes.NONE;
|
||||
}
|
||||
|
||||
async verifyTfa(
|
||||
user: User,
|
||||
token: string,
|
||||
type?: tfaTypes
|
||||
): Promise<boolean> {
|
||||
if (!type) type = user.tfaType;
|
||||
const delta = notp.totp.verify(token, user.tfaSecret, {
|
||||
window: 10
|
||||
});
|
||||
return (
|
||||
delta &&
|
||||
(type == tfaTypes.EMAIL ? delta.delta <= 0 : delta.delta == 0)
|
||||
);
|
||||
}
|
||||
|
||||
async sendTfaMail(user: User) {
|
||||
await mailAccount.sendMail({
|
||||
from: 'matthiasveigel@web.de',
|
||||
to: user.name,
|
||||
subject: 'Fileserver - EMail 2fa code',
|
||||
text: `Your code is: ${notp.totp.gen(
|
||||
user.tfaSecret
|
||||
)}\nIt is valid for 5 Minutes`
|
||||
});
|
||||
}
|
||||
|
||||
async setupTfa(user: User): Promise<string> {
|
||||
if (user.tfaType != tfaTypes.NONE)
|
||||
throw new BadRequestException(
|
||||
'2 Factor authentication is already setup'
|
||||
);
|
||||
const secret = this.generateTfaSecret();
|
||||
user.tfaSecret = secret;
|
||||
await this.userRepo.save(user);
|
||||
return secret;
|
||||
}
|
||||
|
||||
async setTfaType(user: User, type: tfaTypes) {
|
||||
user.tfaType = type;
|
||||
await this.userRepo.save(user);
|
||||
}
|
||||
|
||||
async login(user: User) {
|
||||
const token = new JWTToken();
|
||||
token.ownerId = user.id;
|
||||
@ -101,6 +167,12 @@ export class AuthService {
|
||||
id: token.id
|
||||
});
|
||||
}
|
||||
|
||||
async revokeAll(user: User) {
|
||||
await this.tokenRepo.delete({
|
||||
ownerId: user.id
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@Injectable()
|
||||
|
@ -5389,6 +5389,11 @@ thenify-all@^1.0.0:
|
||||
dependencies:
|
||||
any-promise "^1.0.0"
|
||||
|
||||
thirty-two@^1.0.2:
|
||||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/thirty-two/-/thirty-two-1.0.2.tgz#4ca2fffc02a51290d2744b9e3f557693ca6b627a"
|
||||
integrity sha512-OEI0IWCe+Dw46019YLl6V10Us5bi574EvlJEOcAkB29IzQ/mYD1A6RyNHLjZPiHCmuodxvgF6U+vZO1L15lxVA==
|
||||
|
||||
thread-stream@^2.0.0:
|
||||
version "2.1.0"
|
||||
resolved "https://registry.yarnpkg.com/thread-stream/-/thread-stream-2.1.0.tgz#d560dd8b9d09482b0e2e876a96c229c374870836"
|
||||
|
Loading…
Reference in New Issue
Block a user