parent
b2d67f6ed4
commit
643a5755c0
@ -2,7 +2,7 @@
|
|||||||
<configuration default="false" name="clippy" type="CargoCommandRunConfiguration" factoryName="Cargo Command">
|
<configuration default="false" name="clippy" type="CargoCommandRunConfiguration" factoryName="Cargo Command">
|
||||||
<option name="command" value="clippy" />
|
<option name="command" value="clippy" />
|
||||||
<option name="workingDirectory" value="file://$PROJECT_DIR$" />
|
<option name="workingDirectory" value="file://$PROJECT_DIR$" />
|
||||||
<option name="channel" value="DEFAULT" />
|
<option name="channel" value="STABLE" />
|
||||||
<option name="requiredFeatures" value="true" />
|
<option name="requiredFeatures" value="true" />
|
||||||
<option name="allFeatures" value="false" />
|
<option name="allFeatures" value="false" />
|
||||||
<option name="emulateTerminal" value="false" />
|
<option name="emulateTerminal" value="false" />
|
||||||
|
@ -152,6 +152,17 @@ pub mod requests {
|
|||||||
pub otp: Option<String>
|
pub otp: Option<String>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize, Serialize, Clone)]
|
||||||
|
pub struct SendRecoveryKey {
|
||||||
|
pub username: String
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize, Serialize, Clone)]
|
||||||
|
pub struct ResetPassword {
|
||||||
|
pub key: String,
|
||||||
|
pub password: String
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Deserialize, Serialize, Clone)]
|
#[derive(Debug, Deserialize, Serialize, Clone)]
|
||||||
pub struct TfaSetup {
|
pub struct TfaSetup {
|
||||||
pub mail: bool
|
pub mail: bool
|
||||||
|
@ -15,7 +15,7 @@ use tiny_http::{Method, Request, Response, ResponseBox, Server, StatusCode};
|
|||||||
use crate::{
|
use crate::{
|
||||||
db::DBConnection,
|
db::DBConnection,
|
||||||
metrics::TRACER,
|
metrics::TRACER,
|
||||||
routes::{get_reply, header, AppError}
|
routes::{header, AppError}
|
||||||
};
|
};
|
||||||
|
|
||||||
static THREAD_COUNT: usize = 10;
|
static THREAD_COUNT: usize = 10;
|
||||||
@ -117,6 +117,8 @@ fn handle_api_request(span: &mut Span, req: &mut Request, pool: db::DBPool) -> R
|
|||||||
("/api/metrics", Method::Get) => metrics::get_metrics(),
|
("/api/metrics", Method::Get) => metrics::get_metrics(),
|
||||||
("/api/auth/login", Method::Post) => parse_body(span, req).and_then(|v|routes::auth::basic::login(span, req, db, v)),
|
("/api/auth/login", Method::Post) => parse_body(span, req).and_then(|v|routes::auth::basic::login(span, req, db, v)),
|
||||||
("/api/auth/signup", Method::Post) => parse_body(span, req).and_then(|v| routes::auth::basic::signup(span, req, db, v)),
|
("/api/auth/signup", Method::Post) => parse_body(span, req).and_then(|v| routes::auth::basic::signup(span, req, db, v)),
|
||||||
|
("/api/auth/send_key", Method::Post) => parse_body(span, req).and_then(|v| routes::auth::basic::send_key(span, req, db, v)),
|
||||||
|
("/api/auth/reset", Method::Post) => parse_body(span, req).and_then(|v| routes::auth::basic::reset(span, req, db, v)),
|
||||||
("/api/auth/gitlab", Method::Get) => routes::auth::gitlab::gitlab(span, req, db),
|
("/api/auth/gitlab", Method::Get) => routes::auth::gitlab::gitlab(span, req, db),
|
||||||
("/api/auth/gitlab_callback", Method::Get) => routes::auth::gitlab::gitlab_callback(span, req, db, &query),
|
("/api/auth/gitlab_callback", Method::Get) => routes::auth::gitlab::gitlab_callback(span, req, db, &query),
|
||||||
("/api/fs/download", Method::Post) => routes::fs::routes::download(span, req, db),
|
("/api/fs/download", Method::Post) => routes::fs::routes::download(span, req, db),
|
||||||
@ -143,7 +145,7 @@ fn handle_api_request(span: &mut Span, req: &mut Request, pool: db::DBPool) -> R
|
|||||||
("/api/admin/logout", Method::Post, None) => parse_body(span, req).and_then(|v| routes::admin::logout(span, req, db, info, v)),
|
("/api/admin/logout", Method::Post, None) => parse_body(span, req).and_then(|v| routes::admin::logout(span, req, db, info, v)),
|
||||||
("/api/admin/delete", Method::Post, None) => parse_body(span, req).and_then(|v| routes::admin::delete_user(span, req, db, info, v)),
|
("/api/admin/delete", Method::Post, None) => parse_body(span, req).and_then(|v| routes::admin::delete_user(span, req, db, info, v)),
|
||||||
("/api/admin/disable_2fa", Method::Post, None) => parse_body(span, req).and_then(|v| routes::admin::disable_2fa(span, req, db, info, v)),
|
("/api/admin/disable_2fa", Method::Post, None) => parse_body(span, req).and_then(|v| routes::admin::disable_2fa(span, req, db, info, v)),
|
||||||
("/api/admin/is_admin", Method::Post, None) => get_reply(&dto::responses::Success { statusCode: 200 }),
|
("/api/admin/is_admin", Method::Get, None) => routes::admin::is_admin(span, req, db, info),
|
||||||
("/api/admin/get_token", Method::Get, Some(v)) => routes::admin::get_token(span, req, db, info, v),
|
("/api/admin/get_token", Method::Get, Some(v)) => routes::admin::get_token(span, req, db, info, v),
|
||||||
("/api/auth/refresh", Method::Post, None) => routes::auth::basic::refresh(span, req, db, info),
|
("/api/auth/refresh", Method::Post, None) => routes::auth::basic::refresh(span, req, db, info),
|
||||||
("/api/auth/logout_all", Method::Post, None) => routes::auth::basic::logout_all(span, req, db, info),
|
("/api/auth/logout_all", Method::Post, None) => routes::auth::basic::logout_all(span, req, db, info),
|
||||||
|
@ -110,6 +110,14 @@ pub fn disable_2fa(
|
|||||||
get_reply(&dto::responses::Success { statusCode: 200 })
|
get_reply(&dto::responses::Success { statusCode: 200 })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn is_admin(_: &Span, _: &mut Request, _: &mut DBConnection, info: UserInfo) -> Result<ResponseBox, AppError> {
|
||||||
|
if info.0.role != UserRole::Admin {
|
||||||
|
return AppError::Forbidden("Forbidden").err();
|
||||||
|
}
|
||||||
|
|
||||||
|
get_reply(&dto::responses::Success { statusCode: 200 })
|
||||||
|
}
|
||||||
|
|
||||||
pub fn get_token(
|
pub fn get_token(
|
||||||
span: &Span,
|
span: &Span,
|
||||||
_: &mut Request,
|
_: &mut Request,
|
||||||
|
@ -1,12 +1,23 @@
|
|||||||
|
use std::{collections::HashMap, ops::Add};
|
||||||
|
|
||||||
|
use lettre::Transport;
|
||||||
|
use once_cell::sync::Lazy;
|
||||||
|
use parking_lot::Mutex;
|
||||||
|
use ring::rand::SecureRandom;
|
||||||
use rustracing_jaeger::Span;
|
use rustracing_jaeger::Span;
|
||||||
use tiny_http::{Request, ResponseBox};
|
use tiny_http::{Request, ResponseBox};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
db::{DBConnection, TfaTypes, UserRole},
|
db::{DBConnection, TfaTypes, UserRole},
|
||||||
dto,
|
dto,
|
||||||
routes::{filters::UserInfo, get_reply, AppError}
|
metrics,
|
||||||
|
routes::{auth::tfa::MAIL_SENDER, filters::UserInfo, get_reply, AppError}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
type RecoveryKeyMap = HashMap<String, (i32, chrono::DateTime<chrono::Utc>)>;
|
||||||
|
static RECOVERY_KEYS: Lazy<Mutex<RecoveryKeyMap>> = Lazy::new(|| Mutex::new(RecoveryKeyMap::new()));
|
||||||
|
const KEY_CHARSET: &[u8] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
|
||||||
|
|
||||||
pub fn login(
|
pub fn login(
|
||||||
span: &Span,
|
span: &Span,
|
||||||
_: &mut Request,
|
_: &mut Request,
|
||||||
@ -60,6 +71,70 @@ pub fn signup(
|
|||||||
get_reply(&dto::responses::Success { statusCode: 200 })
|
get_reply(&dto::responses::Success { statusCode: 200 })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn send_key(
|
||||||
|
span: &Span,
|
||||||
|
_: &mut Request,
|
||||||
|
db: &mut DBConnection,
|
||||||
|
data: dto::requests::SendRecoveryKey
|
||||||
|
) -> Result<ResponseBox, AppError> {
|
||||||
|
// Silently fail to prevent information leak about usernames
|
||||||
|
if let Some(user) = db.find_user(span, &data.username, false) {
|
||||||
|
let _span = metrics::span("send_recovery_key", span);
|
||||||
|
|
||||||
|
let mut bin = [0_u8; 10];
|
||||||
|
crate::routes::auth::SEC_RANDOM.fill(&mut bin).expect("Failed to generate recovery key");
|
||||||
|
let code_arr = bin.map(|v| KEY_CHARSET[v as usize % KEY_CHARSET.len()]);
|
||||||
|
let code = String::from_utf8_lossy(&code_arr);
|
||||||
|
|
||||||
|
{
|
||||||
|
RECOVERY_KEYS.lock().insert(
|
||||||
|
code.to_string(),
|
||||||
|
(
|
||||||
|
user.id,
|
||||||
|
chrono::Utc::now().add(chrono::Duration::minutes(5))
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
let mail = lettre::Message::builder()
|
||||||
|
.from("fileserver@mattv.de".parse().unwrap())
|
||||||
|
.to(user.name.parse().unwrap())
|
||||||
|
.subject("MFileserver - Password reset")
|
||||||
|
.body(format!(
|
||||||
|
"Your recovery key is: {}\r\nIt is valid for 5 minutes",
|
||||||
|
code
|
||||||
|
))
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
MAIL_SENDER.send(&mail).expect("Failed to send mail");
|
||||||
|
}
|
||||||
|
|
||||||
|
get_reply(&dto::responses::Success { statusCode: 200 })
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn reset(
|
||||||
|
span: &Span,
|
||||||
|
_: &mut Request,
|
||||||
|
db: &mut DBConnection,
|
||||||
|
data: dto::requests::ResetPassword
|
||||||
|
) -> Result<ResponseBox, AppError> {
|
||||||
|
if let Some(entry) = { RECOVERY_KEYS.lock().get(&data.key) } {
|
||||||
|
if entry.1 < chrono::Utc::now() {
|
||||||
|
return AppError::BadRequest("Invalid key").err();
|
||||||
|
}
|
||||||
|
if let Some(mut user) = db.get_user(span, entry.0) {
|
||||||
|
user.password = super::hash_password(&data.password);
|
||||||
|
db.save_user(span, &user);
|
||||||
|
db.delete_all_tokens(span, user.id);
|
||||||
|
get_reply(&dto::responses::Success { statusCode: 200 })
|
||||||
|
} else {
|
||||||
|
AppError::BadRequest("Invalid user").err()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
AppError::BadRequest("Invalid key").err()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn refresh(span: &Span, _: &mut Request, db: &mut DBConnection, info: UserInfo) -> Result<ResponseBox, AppError> {
|
pub fn refresh(span: &Span, _: &mut Request, db: &mut DBConnection, info: UserInfo) -> Result<ResponseBox, AppError> {
|
||||||
db.delete_token(span, info.1.id);
|
db.delete_token(span, info.1.id);
|
||||||
|
|
||||||
|
@ -26,7 +26,7 @@ fn build_mail_sender() -> lettre::SmtpTransport {
|
|||||||
.build()
|
.build()
|
||||||
}
|
}
|
||||||
|
|
||||||
static MAIL_SENDER: Lazy<lettre::SmtpTransport> = Lazy::new(build_mail_sender);
|
pub static MAIL_SENDER: Lazy<lettre::SmtpTransport> = Lazy::new(build_mail_sender);
|
||||||
|
|
||||||
fn get_totp(user: &db::User) -> totp_rs::TOTP {
|
fn get_totp(user: &db::User) -> totp_rs::TOTP {
|
||||||
totp_rs::TOTP::from_rfc6238(
|
totp_rs::TOTP::from_rfc6238(
|
||||||
|
@ -6,10 +6,9 @@ use std::{
|
|||||||
ops::DerefMut,
|
ops::DerefMut,
|
||||||
sync::atomic::Ordering
|
sync::atomic::Ordering
|
||||||
};
|
};
|
||||||
use image::DynamicImage;
|
|
||||||
|
|
||||||
use fast_image_resize as fir;
|
use fast_image_resize as fir;
|
||||||
use fast_image_resize::PixelType;
|
use image::DynamicImage;
|
||||||
use rustracing_jaeger::Span;
|
use rustracing_jaeger::Span;
|
||||||
use tiny_http::{Request, Response, ResponseBox, StatusCode};
|
use tiny_http::{Request, Response, ResponseBox, StatusCode};
|
||||||
|
|
||||||
@ -212,18 +211,54 @@ pub fn upload(
|
|||||||
let width = NonZeroU32::try_from(img.width()).unwrap();
|
let width = NonZeroU32::try_from(img.width()).unwrap();
|
||||||
let height = NonZeroU32::try_from(img.height()).unwrap();
|
let height = NonZeroU32::try_from(img.height()).unwrap();
|
||||||
match img {
|
match img {
|
||||||
DynamicImage::ImageLuma8(v) => fir::Image::from_vec_u8(width, height, v.into_raw(), fir::PixelType::U8),
|
DynamicImage::ImageLuma8(v) =>
|
||||||
DynamicImage::ImageLumaA8(v) => fir::Image::from_vec_u8(width, height, v.into_raw(), fir::PixelType::U8x2),
|
fir::Image::from_vec_u8(width, height, v.into_raw(), fir::PixelType::U8),
|
||||||
DynamicImage::ImageRgb8(v) => fir::Image::from_vec_u8(width, height, v.into_raw(), fir::PixelType::U8x3),
|
DynamicImage::ImageLumaA8(v) =>
|
||||||
DynamicImage::ImageRgba8(v) => fir::Image::from_vec_u8(width, height, v.into_raw(), fir::PixelType::U8x4),
|
fir::Image::from_vec_u8(width, height, v.into_raw(), fir::PixelType::U8x2),
|
||||||
DynamicImage::ImageLuma16(_) => fir::Image::from_vec_u8(width, height, img.to_luma8().into_raw(), fir::PixelType::U8),
|
DynamicImage::ImageRgb8(v) =>
|
||||||
DynamicImage::ImageLumaA16(_) => fir::Image::from_vec_u8(width, height, img.to_luma_alpha8().into_raw(), fir::PixelType::U8x2),
|
fir::Image::from_vec_u8(width, height, v.into_raw(), fir::PixelType::U8x3),
|
||||||
DynamicImage::ImageRgb16(_) => fir::Image::from_vec_u8(width, height, img.to_rgb8().into_raw(), fir::PixelType::U8x3),
|
DynamicImage::ImageRgba8(v) =>
|
||||||
DynamicImage::ImageRgba16(_) => fir::Image::from_vec_u8(width, height, img.to_rgba8().into_raw(), fir::PixelType::U8x4),
|
fir::Image::from_vec_u8(width, height, v.into_raw(), fir::PixelType::U8x4),
|
||||||
DynamicImage::ImageRgb32F(_) => fir::Image::from_vec_u8(width, height, img.to_rgb8().into_raw(), fir::PixelType::U8x3),
|
DynamicImage::ImageLuma16(_) =>
|
||||||
DynamicImage::ImageRgba32F(_) => fir::Image::from_vec_u8(width, height, img.to_rgba8().into_raw(), fir::PixelType::U8x4),
|
fir::Image::from_vec_u8(width, height, img.to_luma8().into_raw(), fir::PixelType::U8),
|
||||||
_ => fir::Image::from_vec_u8(width, height, img.to_rgba8().into_raw(), fir::PixelType::U8x4)
|
DynamicImage::ImageLumaA16(_) => fir::Image::from_vec_u8(
|
||||||
}.expect("Failed to convert preview image")
|
width,
|
||||||
|
height,
|
||||||
|
img.to_luma_alpha8().into_raw(),
|
||||||
|
fir::PixelType::U8x2
|
||||||
|
),
|
||||||
|
DynamicImage::ImageRgb16(_) => fir::Image::from_vec_u8(
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
img.to_rgb8().into_raw(),
|
||||||
|
fir::PixelType::U8x3
|
||||||
|
),
|
||||||
|
DynamicImage::ImageRgba16(_) => fir::Image::from_vec_u8(
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
img.to_rgba8().into_raw(),
|
||||||
|
fir::PixelType::U8x4
|
||||||
|
),
|
||||||
|
DynamicImage::ImageRgb32F(_) => fir::Image::from_vec_u8(
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
img.to_rgb8().into_raw(),
|
||||||
|
fir::PixelType::U8x3
|
||||||
|
),
|
||||||
|
DynamicImage::ImageRgba32F(_) => fir::Image::from_vec_u8(
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
img.to_rgba8().into_raw(),
|
||||||
|
fir::PixelType::U8x4
|
||||||
|
),
|
||||||
|
_ => fir::Image::from_vec_u8(
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
img.to_rgba8().into_raw(),
|
||||||
|
fir::PixelType::U8x4
|
||||||
|
)
|
||||||
|
}
|
||||||
|
.expect("Failed to convert preview image")
|
||||||
};
|
};
|
||||||
let img = {
|
let img = {
|
||||||
let _span = metrics::span("generate_preview_resize", &prev_span);
|
let _span = metrics::span("generate_preview_resize", &prev_span);
|
||||||
@ -233,10 +268,7 @@ pub fn upload(
|
|||||||
NonZeroU32::try_from(new_dim.1).unwrap(),
|
NonZeroU32::try_from(new_dim.1).unwrap(),
|
||||||
img.pixel_type()
|
img.pixel_type()
|
||||||
);
|
);
|
||||||
fir::Resizer::new(fir::ResizeAlg::SuperSampling(
|
fir::Resizer::new(fir::ResizeAlg::SuperSampling(fir::FilterType::Hamming, 2))
|
||||||
fir::FilterType::Hamming,
|
|
||||||
2
|
|
||||||
))
|
|
||||||
.resize(&img.view(), &mut dst.view_mut())
|
.resize(&img.view(), &mut dst.view_mut())
|
||||||
.expect("Failed to resize preview image");
|
.expect("Failed to resize preview image");
|
||||||
dst
|
dst
|
||||||
@ -252,10 +284,10 @@ pub fn upload(
|
|||||||
img.width().get(),
|
img.width().get(),
|
||||||
img.height().get(),
|
img.height().get(),
|
||||||
match img.pixel_type() {
|
match img.pixel_type() {
|
||||||
PixelType::U8 => image::ColorType::L8,
|
fir::PixelType::U8 => image::ColorType::L8,
|
||||||
PixelType::U8x2 => image::ColorType::La8,
|
fir::PixelType::U8x2 => image::ColorType::La8,
|
||||||
PixelType::U8x3 => image::ColorType::Rgb8,
|
fir::PixelType::U8x3 => image::ColorType::Rgb8,
|
||||||
PixelType::U8x4 => image::ColorType::Rgba8,
|
fir::PixelType::U8x4 => image::ColorType::Rgba8,
|
||||||
_ => unreachable!()
|
_ => unreachable!()
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
@ -21,6 +21,22 @@ export const auth_signup = (
|
|||||||
password: password
|
password: password
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export const send_recovery_key = (
|
||||||
|
username: string
|
||||||
|
): Promise<Responses.Success | Responses.Error> =>
|
||||||
|
post<Requests.SendRecoveryKey>('/api/auth/send_key', {
|
||||||
|
username: username
|
||||||
|
});
|
||||||
|
|
||||||
|
export const reset_password = (
|
||||||
|
key: string,
|
||||||
|
password: string
|
||||||
|
): Promise<Responses.Success | Responses.Error> =>
|
||||||
|
post<Requests.ResetPassword>('/api/auth/reset', {
|
||||||
|
key: key,
|
||||||
|
password: password
|
||||||
|
});
|
||||||
|
|
||||||
export const refresh_token = (
|
export const refresh_token = (
|
||||||
token: string
|
token: string
|
||||||
): Promise<Responses.Login | Responses.Error> =>
|
): Promise<Responses.Login | Responses.Error> =>
|
||||||
|
@ -1,10 +1,5 @@
|
|||||||
import type { Requests, Responses, UploadFile } from '@/dto';
|
import type { Requests, Responses, UploadFile } from '@/dto';
|
||||||
import {
|
import { get_token, post_token, isErrorResponse } from './base';
|
||||||
get_token,
|
|
||||||
post_token,
|
|
||||||
post_token_form,
|
|
||||||
isErrorResponse
|
|
||||||
} from './base';
|
|
||||||
import axios from 'axios';
|
import axios from 'axios';
|
||||||
|
|
||||||
export const get_root = (
|
export const get_root = (
|
||||||
|
@ -32,6 +32,15 @@ export namespace Requests {
|
|||||||
otp?: string;
|
otp?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface SendRecoveryKey extends Base {
|
||||||
|
username: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ResetPassword extends Base {
|
||||||
|
key: string;
|
||||||
|
password: string;
|
||||||
|
}
|
||||||
|
|
||||||
export interface TfaSetup extends Base {
|
export interface TfaSetup extends Base {
|
||||||
mail: boolean;
|
mail: boolean;
|
||||||
}
|
}
|
||||||
|
119
frontend/src/views/ForgotPasswordView.vue
Normal file
119
frontend/src/views/ForgotPasswordView.vue
Normal file
@ -0,0 +1,119 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { ref } from 'vue';
|
||||||
|
import { Auth, isErrorResponse } from '@/api';
|
||||||
|
import { useMessage, NInput, NGrid, NGi, NButton, NCard } from 'naive-ui';
|
||||||
|
import { useRouter } from 'vue-router';
|
||||||
|
import { loadingMsgWrapper } from '@/utils';
|
||||||
|
|
||||||
|
const router = useRouter();
|
||||||
|
const message = useMessage();
|
||||||
|
|
||||||
|
const email = ref('');
|
||||||
|
const code = ref('');
|
||||||
|
const password = ref('');
|
||||||
|
const password2 = ref('');
|
||||||
|
const enterCode = ref(false);
|
||||||
|
|
||||||
|
const reset = loadingMsgWrapper(message, async () => {
|
||||||
|
if (password.value !== password2.value) {
|
||||||
|
message.error("Passwords don't match");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const res = await Auth.reset_password(code.value, password.value);
|
||||||
|
if (isErrorResponse(res)) {
|
||||||
|
message.error(`Failed to reset password: ${res.message}`);
|
||||||
|
} else {
|
||||||
|
message.success('Password reset', {
|
||||||
|
duration: 2000
|
||||||
|
});
|
||||||
|
login();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const sendKey = loadingMsgWrapper(message, async () => {
|
||||||
|
const res = await Auth.send_recovery_key(email.value);
|
||||||
|
if (isErrorResponse(res)) {
|
||||||
|
message.error(`Failed to send recovery key: ${res.message}`);
|
||||||
|
} else {
|
||||||
|
message.success('If the email is registered a mail has been sent', {
|
||||||
|
duration: 10000
|
||||||
|
});
|
||||||
|
enterCode.value = true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
function login() {
|
||||||
|
router.replace('login');
|
||||||
|
}
|
||||||
|
|
||||||
|
function onKey(event: KeyboardEvent) {
|
||||||
|
if (event.key == 'Enter') enterCode.value ? reset() : sendKey();
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<n-card>
|
||||||
|
<n-grid cols="2" x-gap="16" y-gap="16">
|
||||||
|
<template v-if="enterCode">
|
||||||
|
<n-gi span="2">
|
||||||
|
<n-input
|
||||||
|
:key="1"
|
||||||
|
type="text"
|
||||||
|
placeholder="Recovery key"
|
||||||
|
v-model:value="code"
|
||||||
|
maxlength="10"
|
||||||
|
autofocus
|
||||||
|
@keyup="onKey"
|
||||||
|
/>
|
||||||
|
</n-gi>
|
||||||
|
<n-gi span="2">
|
||||||
|
<n-input
|
||||||
|
type="password"
|
||||||
|
placeholder="Password"
|
||||||
|
v-model:value="password"
|
||||||
|
@keyup="onKey"
|
||||||
|
/>
|
||||||
|
</n-gi>
|
||||||
|
<n-gi span="2">
|
||||||
|
<n-input
|
||||||
|
type="password"
|
||||||
|
placeholder="Repeat password"
|
||||||
|
v-model:value="password2"
|
||||||
|
@keyup="onKey"
|
||||||
|
/>
|
||||||
|
</n-gi>
|
||||||
|
<n-gi>
|
||||||
|
<n-button type="info" @click="reset">
|
||||||
|
Reset password
|
||||||
|
</n-button>
|
||||||
|
</n-gi>
|
||||||
|
<n-gi style="text-align: right">
|
||||||
|
<n-button ghost @click="login">Go to login</n-button>
|
||||||
|
</n-gi>
|
||||||
|
</template>
|
||||||
|
<template v-else>
|
||||||
|
<n-gi span="2">
|
||||||
|
<n-input
|
||||||
|
:key="2"
|
||||||
|
type="text"
|
||||||
|
placeholder="Email"
|
||||||
|
v-model:value="email"
|
||||||
|
autofocus
|
||||||
|
:input-props="{ type: 'email' }"
|
||||||
|
@keyup="onKey"
|
||||||
|
/>
|
||||||
|
</n-gi>
|
||||||
|
<n-gi>
|
||||||
|
<n-button type="info" @click="sendKey">
|
||||||
|
Send recovery key
|
||||||
|
</n-button>
|
||||||
|
</n-gi>
|
||||||
|
<n-gi style="text-align: right">
|
||||||
|
<n-button ghost @click="login">Go to login</n-button>
|
||||||
|
</n-gi>
|
||||||
|
</template>
|
||||||
|
</n-grid>
|
||||||
|
</n-card>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped></style>
|
Loading…
Reference in New Issue
Block a user