Added totp/mail otp, split up dto and api into multiple files
This commit is contained in:
		
							
								
								
									
										75
									
								
								dto/index.ts
									
									
									
									
									
								
							
							
						
						
									
										75
									
								
								dto/index.ts
									
									
									
									
									
								
							@@ -1,73 +1,2 @@
 | 
				
			|||||||
//
 | 
					export * as Requests from './requests';
 | 
				
			||||||
// Responses
 | 
					export * as Responses from './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;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										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">
 | 
					<script setup async lang="ts">
 | 
				
			||||||
import { provide, ref } from 'vue';
 | 
					import { provide, ref } from 'vue';
 | 
				
			||||||
import { useRouter } from 'vue-router';
 | 
					import { useRouter } from 'vue-router';
 | 
				
			||||||
import { refresh_token, TokenInjectType, isErrorResponse } from '@/api';
 | 
					import { Auth, TokenInjectType, isErrorResponse } from '@/api';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const router = useRouter();
 | 
					const router = useRouter();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -21,7 +21,7 @@ function logout() {
 | 
				
			|||||||
jwt.value = localStorage.getItem('token');
 | 
					jwt.value = localStorage.getItem('token');
 | 
				
			||||||
if (jwt.value == null) await router.push({ name: 'login' });
 | 
					if (jwt.value == null) await router.push({ name: 'login' });
 | 
				
			||||||
else {
 | 
					else {
 | 
				
			||||||
	const new_token = await refresh_token(jwt.value ?? '');
 | 
						const new_token = await Auth.refresh_token(jwt.value ?? '');
 | 
				
			||||||
	if (isErrorResponse(new_token)) logout();
 | 
						if (isErrorResponse(new_token)) logout();
 | 
				
			||||||
	else setToken(new_token.jwt);
 | 
						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">
 | 
					<script setup lang="ts">
 | 
				
			||||||
import { defineEmits, defineProps, inject } from 'vue';
 | 
					import { defineEmits, defineProps, inject } from 'vue';
 | 
				
			||||||
import {
 | 
					import { check_token, FS, Responses, TokenInjectType } from '@/api';
 | 
				
			||||||
	check_token,
 | 
					 | 
				
			||||||
	delete_node,
 | 
					 | 
				
			||||||
	download_file,
 | 
					 | 
				
			||||||
	GetNodeResponse,
 | 
					 | 
				
			||||||
	TokenInjectType
 | 
					 | 
				
			||||||
} from '@/api';
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
const jwt = inject<TokenInjectType>('jwt') as TokenInjectType;
 | 
					const jwt = inject<TokenInjectType>('jwt') as TokenInjectType;
 | 
				
			||||||
const props = defineProps<{
 | 
					const props = defineProps<{
 | 
				
			||||||
	node: GetNodeResponse;
 | 
						node: Responses.FS.GetNodeResponse;
 | 
				
			||||||
}>();
 | 
					}>();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const emit = defineEmits<{
 | 
					const emit = defineEmits<{
 | 
				
			||||||
@@ -20,14 +14,14 @@ const emit = defineEmits<{
 | 
				
			|||||||
async function del() {
 | 
					async function del() {
 | 
				
			||||||
	const token = await check_token(jwt);
 | 
						const token = await check_token(jwt);
 | 
				
			||||||
	if (!token) return;
 | 
						if (!token) return;
 | 
				
			||||||
	await delete_node(token, props.node.id);
 | 
						await FS.delete_node(token, props.node.id);
 | 
				
			||||||
	emit('reloadNode');
 | 
						emit('reloadNode');
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
async function download() {
 | 
					async function download() {
 | 
				
			||||||
	const token = await check_token(jwt);
 | 
						const token = await check_token(jwt);
 | 
				
			||||||
	if (!token) return;
 | 
						if (!token) return;
 | 
				
			||||||
	download_file(token, props.node.id);
 | 
						FS.download_file(token, props.node.id);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
</script>
 | 
					</script>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,18 +1,12 @@
 | 
				
			|||||||
<script setup lang="ts">
 | 
					<script setup lang="ts">
 | 
				
			||||||
import { defineEmits, defineProps, inject, reactive, ref, watch } from 'vue';
 | 
					import { defineEmits, defineProps, inject, reactive, ref, watch } from 'vue';
 | 
				
			||||||
import {
 | 
					import { FS, Responses, check_token, TokenInjectType } from '@/api';
 | 
				
			||||||
	GetNodeResponse,
 | 
					 | 
				
			||||||
	create_folder,
 | 
					 | 
				
			||||||
	get_node,
 | 
					 | 
				
			||||||
	check_token,
 | 
					 | 
				
			||||||
	TokenInjectType
 | 
					 | 
				
			||||||
} from '@/api';
 | 
					 | 
				
			||||||
import DirEntry from '@/components/FSView/DirEntry.vue';
 | 
					import DirEntry from '@/components/FSView/DirEntry.vue';
 | 
				
			||||||
import UploadFileDialog from '@/components/UploadDialog/UploadFileDialog.vue';
 | 
					import UploadFileDialog from '@/components/UploadDialog/UploadFileDialog.vue';
 | 
				
			||||||
import { NModal } from 'naive-ui';
 | 
					import { NModal } from 'naive-ui';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const props = defineProps<{
 | 
					const props = defineProps<{
 | 
				
			||||||
	node: GetNodeResponse;
 | 
						node: Responses.FS.GetNodeResponse;
 | 
				
			||||||
}>();
 | 
					}>();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const jwt = inject<TokenInjectType>('jwt') as TokenInjectType;
 | 
					const jwt = inject<TokenInjectType>('jwt') as TokenInjectType;
 | 
				
			||||||
@@ -28,9 +22,9 @@ const uploadDialogShow = ref(false);
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
const new_folder_name = ref('');
 | 
					const new_folder_name = ref('');
 | 
				
			||||||
const files = ref<File[]>([]);
 | 
					const files = ref<File[]>([]);
 | 
				
			||||||
const nodes = ref<GetNodeResponse[]>([]);
 | 
					const nodes = ref<Responses.FS.GetNodeResponse[]>([]);
 | 
				
			||||||
const hasParent = ref(false);
 | 
					const hasParent = ref(false);
 | 
				
			||||||
const parentNode = reactive<GetNodeResponse>({
 | 
					const parentNode = reactive<Responses.FS.GetNodeResponse>({
 | 
				
			||||||
	id: 0,
 | 
						id: 0,
 | 
				
			||||||
	statusCode: 200,
 | 
						statusCode: 200,
 | 
				
			||||||
	isFile: false,
 | 
						isFile: false,
 | 
				
			||||||
@@ -49,7 +43,10 @@ watch(
 | 
				
			|||||||
		await Promise.all(
 | 
							await Promise.all(
 | 
				
			||||||
			to.children?.map(async (child) => {
 | 
								to.children?.map(async (child) => {
 | 
				
			||||||
				nodes.value.push(
 | 
									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() {
 | 
					async function newFolder() {
 | 
				
			||||||
	const token = await check_token(jwt);
 | 
						const token = await check_token(jwt);
 | 
				
			||||||
	if (!token) return;
 | 
						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');
 | 
						emit('reloadNode');
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,15 +1,9 @@
 | 
				
			|||||||
<script setup lang="ts">
 | 
					<script setup lang="ts">
 | 
				
			||||||
import { defineProps, inject } from 'vue';
 | 
					import { defineProps, inject } from 'vue';
 | 
				
			||||||
import {
 | 
					import { check_token, FS, Responses, TokenInjectType } from '@/api';
 | 
				
			||||||
	check_token,
 | 
					 | 
				
			||||||
	delete_node,
 | 
					 | 
				
			||||||
	download_file,
 | 
					 | 
				
			||||||
	GetNodeResponse,
 | 
					 | 
				
			||||||
	TokenInjectType
 | 
					 | 
				
			||||||
} from '@/api';
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
const props = defineProps<{
 | 
					const props = defineProps<{
 | 
				
			||||||
	node: GetNodeResponse;
 | 
						node: Responses.FS.GetNodeResponse;
 | 
				
			||||||
}>();
 | 
					}>();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const jwt = inject<TokenInjectType>('jwt') as TokenInjectType;
 | 
					const jwt = inject<TokenInjectType>('jwt') as TokenInjectType;
 | 
				
			||||||
@@ -17,13 +11,13 @@ const jwt = inject<TokenInjectType>('jwt') as TokenInjectType;
 | 
				
			|||||||
async function del() {
 | 
					async function del() {
 | 
				
			||||||
	const token = await check_token(jwt);
 | 
						const token = await check_token(jwt);
 | 
				
			||||||
	if (!token) return;
 | 
						if (!token) return;
 | 
				
			||||||
	await delete_node(token, props.node.id);
 | 
						await FS.delete_node(token, props.node.id);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
async function download() {
 | 
					async function download() {
 | 
				
			||||||
	const token = await check_token(jwt);
 | 
						const token = await check_token(jwt);
 | 
				
			||||||
	if (!token) return;
 | 
						if (!token) return;
 | 
				
			||||||
	download_file(token, props.node.id);
 | 
						FS.download_file(token, props.node.id);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
</script>
 | 
					</script>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,6 +1,6 @@
 | 
				
			|||||||
<script setup lang="ts">
 | 
					<script setup lang="ts">
 | 
				
			||||||
import { defineProps, defineExpose, ref } from 'vue';
 | 
					import { defineProps, defineExpose, ref } from 'vue';
 | 
				
			||||||
import { isErrorResponse, upload_file } from '@/api';
 | 
					import { isErrorResponse, FS } from '@/api';
 | 
				
			||||||
import { NProgress } from 'naive-ui';
 | 
					import { NProgress } from 'naive-ui';
 | 
				
			||||||
import filesize from 'filesize';
 | 
					import filesize from 'filesize';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -14,7 +14,7 @@ const err = ref('');
 | 
				
			|||||||
const status = ref('info');
 | 
					const status = ref('info');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
async function startUpload(parent: number, token: string) {
 | 
					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;
 | 
							progress.value = e.loaded;
 | 
				
			||||||
		percentage.value = (e.loaded / e.total) * 100;
 | 
							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 { inject, onBeforeMount, ref } from 'vue';
 | 
				
			||||||
import {
 | 
					import {
 | 
				
			||||||
	check_token,
 | 
						check_token,
 | 
				
			||||||
	get_node,
 | 
						FS,
 | 
				
			||||||
	get_path,
 | 
						Responses,
 | 
				
			||||||
	get_root,
 | 
					 | 
				
			||||||
	GetNodeResponse,
 | 
					 | 
				
			||||||
	isErrorResponse,
 | 
						isErrorResponse,
 | 
				
			||||||
	TokenInjectType
 | 
						TokenInjectType
 | 
				
			||||||
} from '@/api';
 | 
					} from '@/api';
 | 
				
			||||||
@@ -18,14 +16,14 @@ const route = useRoute();
 | 
				
			|||||||
const jwt = inject<TokenInjectType>('jwt') as TokenInjectType;
 | 
					const jwt = inject<TokenInjectType>('jwt') as TokenInjectType;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const path = ref('');
 | 
					const path = ref('');
 | 
				
			||||||
const node = ref<GetNodeResponse | null>(null);
 | 
					const node = ref<Responses.FS.GetNodeResponse | null>(null);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
async function fetch_node(node_id: number) {
 | 
					async function fetch_node(node_id: number) {
 | 
				
			||||||
	const token = await check_token(jwt);
 | 
						const token = await check_token(jwt);
 | 
				
			||||||
	if (!token) return;
 | 
						if (!token) return;
 | 
				
			||||||
	let [p, n] = [
 | 
						let [p, n] = [
 | 
				
			||||||
		await get_path(token, node_id),
 | 
							await FS.get_path(token, node_id),
 | 
				
			||||||
		await get_node(token, node_id)
 | 
							await FS.get_node(token, node_id)
 | 
				
			||||||
	];
 | 
						];
 | 
				
			||||||
	if (isErrorResponse(p)) return gotoRoot();
 | 
						if (isErrorResponse(p)) return gotoRoot();
 | 
				
			||||||
	if (isErrorResponse(n)) return gotoRoot();
 | 
						if (isErrorResponse(n)) return gotoRoot();
 | 
				
			||||||
@@ -46,7 +44,7 @@ onBeforeMount(async () => {
 | 
				
			|||||||
async function gotoRoot() {
 | 
					async function gotoRoot() {
 | 
				
			||||||
	const token = await check_token(jwt);
 | 
						const token = await check_token(jwt);
 | 
				
			||||||
	if (!token) return;
 | 
						if (!token) return;
 | 
				
			||||||
	const rootRes = await get_root(token);
 | 
						const rootRes = await FS.get_root(token);
 | 
				
			||||||
	if (isErrorResponse(rootRes)) return jwt.logout();
 | 
						if (isErrorResponse(rootRes)) return jwt.logout();
 | 
				
			||||||
	const root = rootRes.rootId;
 | 
						const root = rootRes.rootId;
 | 
				
			||||||
	await router.replace({
 | 
						await router.replace({
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -3,7 +3,7 @@
 | 
				
			|||||||
<script setup lang="ts">
 | 
					<script setup lang="ts">
 | 
				
			||||||
import { onBeforeRouteUpdate, useRouter } from 'vue-router';
 | 
					import { onBeforeRouteUpdate, useRouter } from 'vue-router';
 | 
				
			||||||
import { inject, onBeforeMount } from 'vue';
 | 
					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 router = useRouter();
 | 
				
			||||||
const jwt = inject<TokenInjectType>('jwt') as TokenInjectType;
 | 
					const jwt = inject<TokenInjectType>('jwt') as TokenInjectType;
 | 
				
			||||||
@@ -11,7 +11,7 @@ const jwt = inject<TokenInjectType>('jwt') as TokenInjectType;
 | 
				
			|||||||
async function start_redirect() {
 | 
					async function start_redirect() {
 | 
				
			||||||
	const token = await check_token(jwt);
 | 
						const token = await check_token(jwt);
 | 
				
			||||||
	if (!token) return;
 | 
						if (!token) return;
 | 
				
			||||||
	const root = await get_root(token);
 | 
						const root = await FS.get_root(token);
 | 
				
			||||||
	if (isErrorResponse(root)) return jwt.logout();
 | 
						if (isErrorResponse(root)) return jwt.logout();
 | 
				
			||||||
	await router.push({
 | 
						await router.push({
 | 
				
			||||||
		name: 'fs',
 | 
							name: 'fs',
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,25 +1,32 @@
 | 
				
			|||||||
<script setup lang="ts">
 | 
					<script setup lang="ts">
 | 
				
			||||||
import { ref, inject } from 'vue';
 | 
					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';
 | 
					import { useRouter } from 'vue-router';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const router = useRouter();
 | 
					const router = useRouter();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
let username = ref('');
 | 
					const username = ref('');
 | 
				
			||||||
let password = ref('');
 | 
					const password = ref('');
 | 
				
			||||||
 | 
					const otp = ref('');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const error = ref('');
 | 
					const error = ref('');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const requestOtp = ref(false);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const jwt = inject<TokenInjectType>('jwt') as TokenInjectType;
 | 
					const jwt = inject<TokenInjectType>('jwt') as TokenInjectType;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
async function login() {
 | 
					async function login() {
 | 
				
			||||||
 | 
						error.value = '';
 | 
				
			||||||
	if (username.value === '' || password.value === '') {
 | 
						if (username.value === '' || password.value === '') {
 | 
				
			||||||
		error.value = 'Email and/or Password missing';
 | 
							error.value = 'Email and/or Password missing';
 | 
				
			||||||
		return;
 | 
							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;
 | 
						if (isErrorResponse(res)) error.value = 'Login failed: ' + res.message;
 | 
				
			||||||
	else {
 | 
						else if ('jwt' in res) {
 | 
				
			||||||
		const root = await get_root(res.jwt);
 | 
							const root = await FS.get_root(res.jwt);
 | 
				
			||||||
		if (isErrorResponse(root)) {
 | 
							if (isErrorResponse(root)) {
 | 
				
			||||||
			error.value = 'Get root failed: ' + root.message;
 | 
								error.value = 'Get root failed: ' + root.message;
 | 
				
			||||||
			return;
 | 
								return;
 | 
				
			||||||
@@ -29,14 +36,23 @@ async function login() {
 | 
				
			|||||||
			name: 'fs',
 | 
								name: 'fs',
 | 
				
			||||||
			params: { node_id: root.rootId }
 | 
								params: { node_id: root.rootId }
 | 
				
			||||||
		});
 | 
							});
 | 
				
			||||||
 | 
						} else {
 | 
				
			||||||
 | 
							error.value = '';
 | 
				
			||||||
 | 
							requestOtp.value = true;
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
</script>
 | 
					</script>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<template>
 | 
					<template>
 | 
				
			||||||
	<div v-if="error !== ''" v-text="error"></div>
 | 
						<div v-if="error !== ''" v-text="error"></div>
 | 
				
			||||||
	<input type="email" placeholder="Email" v-model="username" />
 | 
						<template v-if="!requestOtp">
 | 
				
			||||||
	<input type="password" placeholder="Password" v-model="password" />
 | 
							<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>
 | 
						<button @click="login()">Login</button>
 | 
				
			||||||
	<router-link to="signup">Signup instead?</router-link>
 | 
						<router-link to="signup">Signup instead?</router-link>
 | 
				
			||||||
</template>
 | 
					</template>
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,6 +1,6 @@
 | 
				
			|||||||
<script setup lang="ts">
 | 
					<script setup lang="ts">
 | 
				
			||||||
import { ref } from 'vue';
 | 
					import { ref } from 'vue';
 | 
				
			||||||
import { auth_signup, isErrorResponse } from '@/api';
 | 
					import { Auth, isErrorResponse } from '@/api';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
let username = ref('');
 | 
					let username = ref('');
 | 
				
			||||||
let password = ref('');
 | 
					let password = ref('');
 | 
				
			||||||
@@ -16,7 +16,7 @@ async function signup() {
 | 
				
			|||||||
		error.value = "Passwords don't match";
 | 
							error.value = "Passwords don't match";
 | 
				
			||||||
		return;
 | 
							return;
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	const res = await auth_signup(username.value, password.value);
 | 
						const res = await Auth.auth_signup(username.value, password.value);
 | 
				
			||||||
	error.value = isErrorResponse(res)
 | 
						error.value = isErrorResponse(res)
 | 
				
			||||||
		? 'Signup failed: ' + res.message
 | 
							? 'Signup failed: ' + res.message
 | 
				
			||||||
		: 'Signup successful, please wait till an admin unlocks your account.';
 | 
							: 'Signup successful, please wait till an admin unlocks your account.';
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -41,6 +41,7 @@
 | 
				
			|||||||
		"reflect-metadata": "^0.1.13",
 | 
							"reflect-metadata": "^0.1.13",
 | 
				
			||||||
		"rxjs": "^7.5.6",
 | 
							"rxjs": "^7.5.6",
 | 
				
			||||||
		"sqlite3": "^5.0.11",
 | 
							"sqlite3": "^5.0.11",
 | 
				
			||||||
 | 
							"thirty-two": "^1.0.2",
 | 
				
			||||||
		"typeorm": "^0.3.7"
 | 
							"typeorm": "^0.3.7"
 | 
				
			||||||
	},
 | 
						},
 | 
				
			||||||
	"devDependencies": {
 | 
						"devDependencies": {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -2,20 +2,21 @@ import {
 | 
				
			|||||||
	BadRequestException,
 | 
						BadRequestException,
 | 
				
			||||||
	Body,
 | 
						Body,
 | 
				
			||||||
	Controller,
 | 
						Controller,
 | 
				
			||||||
 | 
						Get,
 | 
				
			||||||
	HttpCode,
 | 
						HttpCode,
 | 
				
			||||||
 | 
						ParseBoolPipe,
 | 
				
			||||||
	Post,
 | 
						Post,
 | 
				
			||||||
	Request,
 | 
						Request,
 | 
				
			||||||
 | 
						UnauthorizedException,
 | 
				
			||||||
	UseGuards
 | 
						UseGuards
 | 
				
			||||||
} from '@nestjs/common';
 | 
					} from '@nestjs/common';
 | 
				
			||||||
import { AuthService } from '../services/auth';
 | 
					import { AuthService } from '../services/auth';
 | 
				
			||||||
import { AuthGuard } from '@nestjs/passport';
 | 
					import { AuthGuard } from '@nestjs/passport';
 | 
				
			||||||
import { Public } from '../authguards';
 | 
					import { Public } from '../authguards';
 | 
				
			||||||
import {
 | 
					import { Responses } from 'dto';
 | 
				
			||||||
	ErrorResponse,
 | 
					import { tfaTypes } from '../entities';
 | 
				
			||||||
	LoginResponse,
 | 
					import { toDataURL } from 'qrcode';
 | 
				
			||||||
	RefreshResponse,
 | 
					import * as base32 from 'thirty-two';
 | 
				
			||||||
	SignupResponse
 | 
					 | 
				
			||||||
} from 'dto';
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
@Controller('api/auth')
 | 
					@Controller('api/auth')
 | 
				
			||||||
export default class AuthController {
 | 
					export default class AuthController {
 | 
				
			||||||
@@ -25,19 +26,90 @@ export default class AuthController {
 | 
				
			|||||||
	@UseGuards(AuthGuard('local'))
 | 
						@UseGuards(AuthGuard('local'))
 | 
				
			||||||
	@Post('login')
 | 
						@Post('login')
 | 
				
			||||||
	@HttpCode(200)
 | 
						@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 {
 | 
							return {
 | 
				
			||||||
			statusCode: 200,
 | 
								statusCode: 200,
 | 
				
			||||||
			jwt: await this.authService.login(req.user)
 | 
								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()
 | 
						@Public()
 | 
				
			||||||
	@Post('signup')
 | 
						@Post('signup')
 | 
				
			||||||
	async signup(
 | 
						async signup(
 | 
				
			||||||
		@Body('username') username,
 | 
							@Body('username') username,
 | 
				
			||||||
		@Body('password') password
 | 
							@Body('password') password
 | 
				
			||||||
	): Promise<SignupResponse | ErrorResponse> {
 | 
						): Promise<Responses.Auth.SignupResponse> {
 | 
				
			||||||
		if ((await this.authService.findUser(username)) != null)
 | 
							if ((await this.authService.findUser(username)) != null)
 | 
				
			||||||
			throw new BadRequestException('Username already taken');
 | 
								throw new BadRequestException('Username already taken');
 | 
				
			||||||
		await this.authService.signup(username, password);
 | 
							await this.authService.signup(username, password);
 | 
				
			||||||
@@ -47,7 +119,7 @@ export default class AuthController {
 | 
				
			|||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	@Post('refresh')
 | 
						@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);
 | 
							const token = await this.authService.login(req.user);
 | 
				
			||||||
		await this.authService.revoke(req.token);
 | 
							await this.authService.revoke(req.token);
 | 
				
			||||||
		return {
 | 
							return {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -8,15 +8,7 @@ import {
 | 
				
			|||||||
	Request,
 | 
						Request,
 | 
				
			||||||
	StreamableFile
 | 
						StreamableFile
 | 
				
			||||||
} from '@nestjs/common';
 | 
					} from '@nestjs/common';
 | 
				
			||||||
import {
 | 
					import { Responses } from 'dto';
 | 
				
			||||||
	CreateFileResponse,
 | 
					 | 
				
			||||||
	CreateFolderResponse,
 | 
					 | 
				
			||||||
	DeleteResponse,
 | 
					 | 
				
			||||||
	GetNodeResponse,
 | 
					 | 
				
			||||||
	GetPathResponse,
 | 
					 | 
				
			||||||
	GetRootResponse,
 | 
					 | 
				
			||||||
	UploadFileResponse
 | 
					 | 
				
			||||||
} from 'dto/index';
 | 
					 | 
				
			||||||
import FileSystemService from '../services/filesystem';
 | 
					import FileSystemService from '../services/filesystem';
 | 
				
			||||||
import { UserRole } from '../entities';
 | 
					import { UserRole } from '../entities';
 | 
				
			||||||
import { Role } from '../authguards';
 | 
					import { Role } from '../authguards';
 | 
				
			||||||
@@ -27,7 +19,7 @@ export default class FileSystemController {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
	@Get('root')
 | 
						@Get('root')
 | 
				
			||||||
	@Role(UserRole.USER)
 | 
						@Role(UserRole.USER)
 | 
				
			||||||
	async getRoot(@Request() req): Promise<GetRootResponse> {
 | 
						async getRoot(@Request() req): Promise<Responses.FS.GetRootResponse> {
 | 
				
			||||||
		return {
 | 
							return {
 | 
				
			||||||
			statusCode: 200,
 | 
								statusCode: 200,
 | 
				
			||||||
			rootId: req.user.rootId
 | 
								rootId: req.user.rootId
 | 
				
			||||||
@@ -39,9 +31,9 @@ export default class FileSystemController {
 | 
				
			|||||||
	async getNode(
 | 
						async getNode(
 | 
				
			||||||
		@Request() req,
 | 
							@Request() req,
 | 
				
			||||||
		@Param('node', ParseIntPipe) nodeId
 | 
							@Param('node', ParseIntPipe) nodeId
 | 
				
			||||||
	): Promise<GetNodeResponse> {
 | 
						): Promise<Responses.FS.GetNodeResponse> {
 | 
				
			||||||
		const node = await this.fsService.getNodeAndValidate(nodeId, req.user);
 | 
							const node = await this.fsService.getNodeAndValidate(nodeId, req.user);
 | 
				
			||||||
		const data: GetNodeResponse = {
 | 
							const data: Responses.FS.GetNodeResponse = {
 | 
				
			||||||
			id: nodeId,
 | 
								id: nodeId,
 | 
				
			||||||
			statusCode: 200,
 | 
								statusCode: 200,
 | 
				
			||||||
			name: node.name,
 | 
								name: node.name,
 | 
				
			||||||
@@ -61,7 +53,7 @@ export default class FileSystemController {
 | 
				
			|||||||
	async getPath(
 | 
						async getPath(
 | 
				
			||||||
		@Request() req,
 | 
							@Request() req,
 | 
				
			||||||
		@Param('node', ParseIntPipe) nodeId
 | 
							@Param('node', ParseIntPipe) nodeId
 | 
				
			||||||
	): Promise<GetPathResponse> {
 | 
						): Promise<Responses.FS.GetPathResponse> {
 | 
				
			||||||
		return {
 | 
							return {
 | 
				
			||||||
			statusCode: 200,
 | 
								statusCode: 200,
 | 
				
			||||||
			path: await this.fsService.generatePath(
 | 
								path: await this.fsService.generatePath(
 | 
				
			||||||
@@ -76,7 +68,7 @@ export default class FileSystemController {
 | 
				
			|||||||
		@Request() req,
 | 
							@Request() req,
 | 
				
			||||||
		@Body('parent', ParseIntPipe) parent,
 | 
							@Body('parent', ParseIntPipe) parent,
 | 
				
			||||||
		@Body('name') name
 | 
							@Body('name') name
 | 
				
			||||||
	): Promise<CreateFolderResponse> {
 | 
						): Promise<Responses.FS.CreateFolderResponse> {
 | 
				
			||||||
		const newChild = await this.fsService.create(
 | 
							const newChild = await this.fsService.create(
 | 
				
			||||||
			await this.fsService.getNodeAndValidate(parent, req.user),
 | 
								await this.fsService.getNodeAndValidate(parent, req.user),
 | 
				
			||||||
			name,
 | 
								name,
 | 
				
			||||||
@@ -95,7 +87,7 @@ export default class FileSystemController {
 | 
				
			|||||||
		@Request() req,
 | 
							@Request() req,
 | 
				
			||||||
		@Body('parent', ParseIntPipe) parent,
 | 
							@Body('parent', ParseIntPipe) parent,
 | 
				
			||||||
		@Body('name') name
 | 
							@Body('name') name
 | 
				
			||||||
	): Promise<CreateFileResponse> {
 | 
						): Promise<Responses.FS.CreateFileResponse> {
 | 
				
			||||||
		const newChild = await this.fsService.create(
 | 
							const newChild = await this.fsService.create(
 | 
				
			||||||
			await this.fsService.getNodeAndValidate(parent, req.user),
 | 
								await this.fsService.getNodeAndValidate(parent, req.user),
 | 
				
			||||||
			name,
 | 
								name,
 | 
				
			||||||
@@ -113,7 +105,7 @@ export default class FileSystemController {
 | 
				
			|||||||
	async delete(
 | 
						async delete(
 | 
				
			||||||
		@Request() req,
 | 
							@Request() req,
 | 
				
			||||||
		@Body('node', ParseIntPipe) node_id
 | 
							@Body('node', ParseIntPipe) node_id
 | 
				
			||||||
	): Promise<DeleteResponse> {
 | 
						): Promise<Responses.FS.DeleteResponse> {
 | 
				
			||||||
		await this.fsService.delete(
 | 
							await this.fsService.delete(
 | 
				
			||||||
			await this.fsService.getNodeAndValidate(node_id, req.user)
 | 
								await this.fsService.getNodeAndValidate(node_id, req.user)
 | 
				
			||||||
		);
 | 
							);
 | 
				
			||||||
@@ -125,7 +117,7 @@ export default class FileSystemController {
 | 
				
			|||||||
	async upload(
 | 
						async upload(
 | 
				
			||||||
		@Request() req,
 | 
							@Request() req,
 | 
				
			||||||
		@Param('node', ParseIntPipe) nodeId
 | 
							@Param('node', ParseIntPipe) nodeId
 | 
				
			||||||
	): Promise<UploadFileResponse> {
 | 
						): Promise<Responses.FS.UploadFileResponse> {
 | 
				
			||||||
		await this.fsService.uploadFile(await req.file(), nodeId, req.user);
 | 
							await this.fsService.uploadFile(await req.file(), nodeId, req.user);
 | 
				
			||||||
		return { statusCode: 200 };
 | 
							return { statusCode: 200 };
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -76,7 +76,6 @@ export class User {
 | 
				
			|||||||
	})
 | 
						})
 | 
				
			||||||
	tfaType: tfaTypes;
 | 
						tfaType: tfaTypes;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// base32 string
 | 
					 | 
				
			||||||
	@Column({ nullable: true })
 | 
						@Column({ nullable: true })
 | 
				
			||||||
	tfaSecret: string;
 | 
						tfaSecret: string;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -4,17 +4,27 @@ import {
 | 
				
			|||||||
	UnauthorizedException
 | 
						UnauthorizedException
 | 
				
			||||||
} from '@nestjs/common';
 | 
					} from '@nestjs/common';
 | 
				
			||||||
import { InjectRepository } from '@nestjs/typeorm';
 | 
					import { InjectRepository } from '@nestjs/typeorm';
 | 
				
			||||||
import { JWTToken, User, UserRole } from '../entities';
 | 
					import { JWTToken, tfaTypes, User, UserRole } from '../entities';
 | 
				
			||||||
import { Repository, LessThanOrEqual } from 'typeorm';
 | 
					import { LessThanOrEqual, Repository } from 'typeorm';
 | 
				
			||||||
import * as argon2 from 'argon2';
 | 
					import * as argon2 from 'argon2';
 | 
				
			||||||
import { PassportStrategy } from '@nestjs/passport';
 | 
					import { PassportStrategy } from '@nestjs/passport';
 | 
				
			||||||
import { Strategy as LocalStrategy } from 'passport-local';
 | 
					import { Strategy as LocalStrategy } from 'passport-local';
 | 
				
			||||||
import { ExtractJwt, Strategy as JWTStrategy } from 'passport-jwt';
 | 
					import { ExtractJwt, Strategy as JWTStrategy } from 'passport-jwt';
 | 
				
			||||||
import FileSystemService from './filesystem';
 | 
					import FileSystemService from './filesystem';
 | 
				
			||||||
import * as jwt from 'jsonwebtoken';
 | 
					import * as jwt from 'jsonwebtoken';
 | 
				
			||||||
 | 
					import { createTransport } from 'nodemailer';
 | 
				
			||||||
 | 
					import * as notp from 'notp';
 | 
				
			||||||
 | 
					import { randomBytes } from 'crypto';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const jwtSecret = 'CUM';
 | 
					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 {
 | 
					interface jwtPayload {
 | 
				
			||||||
	sub: number;
 | 
						sub: number;
 | 
				
			||||||
	jti: number;
 | 
						jti: number;
 | 
				
			||||||
@@ -32,6 +42,16 @@ export class AuthService {
 | 
				
			|||||||
		private fsService: FileSystemService
 | 
							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> {
 | 
						async getUser(userId: number): Promise<User | null> {
 | 
				
			||||||
		return this.userRepo.findOneBy({
 | 
							return this.userRepo.findOneBy({
 | 
				
			||||||
			id: userId
 | 
								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) {
 | 
						async login(user: User) {
 | 
				
			||||||
		const token = new JWTToken();
 | 
							const token = new JWTToken();
 | 
				
			||||||
		token.ownerId = user.id;
 | 
							token.ownerId = user.id;
 | 
				
			||||||
@@ -101,6 +167,12 @@ export class AuthService {
 | 
				
			|||||||
			id: token.id
 | 
								id: token.id
 | 
				
			||||||
		});
 | 
							});
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						async revokeAll(user: User) {
 | 
				
			||||||
 | 
							await this.tokenRepo.delete({
 | 
				
			||||||
 | 
								ownerId: user.id
 | 
				
			||||||
 | 
							});
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@Injectable()
 | 
					@Injectable()
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -5389,6 +5389,11 @@ thenify-all@^1.0.0:
 | 
				
			|||||||
  dependencies:
 | 
					  dependencies:
 | 
				
			||||||
    any-promise "^1.0.0"
 | 
					    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:
 | 
					thread-stream@^2.0.0:
 | 
				
			||||||
  version "2.1.0"
 | 
					  version "2.1.0"
 | 
				
			||||||
  resolved "https://registry.yarnpkg.com/thread-stream/-/thread-stream-2.1.0.tgz#d560dd8b9d09482b0e2e876a96c229c374870836"
 | 
					  resolved "https://registry.yarnpkg.com/thread-stream/-/thread-stream-2.1.0.tgz#d560dd8b9d09482b0e2e876a96c229c374870836"
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user