initial 2FA support

This commit is contained in:
MathMan05 2024-08-09 18:54:34 -05:00
parent c27a9af9bd
commit 13c896d756
6 changed files with 262 additions and 11 deletions

View file

@ -38,6 +38,7 @@ class Localuser{
wsinterval:NodeJS.Timeout;
connectionSucceed=0;
errorBackoff=0;
mfa_enabled:boolean;
constructor(userinfo:Specialuser){
this.token=userinfo.token;
this.userinfo=userinfo;
@ -53,6 +54,7 @@ class Localuser{
this.guilds=[];
this.guildids=new Map();
this.user=new User(ready.d.user,this);
this.mfa_enabled=ready.d.user.mfa_enabled;
this.userinfo.username=this.user.username;
this.userinfo.pfpsrc=this.user.getpfpsrc();
this.status=this.ready.d.user_settings.status;
@ -743,6 +745,66 @@ class Localuser{
},{initColor:userinfos.accent_color})
}
}
{
const security=settings.addButton("Account Security");
if(this.mfa_enabled){
security.addTextInput("Disable 2FA, totp code:",_=>{
fetch(this.info.api.toString()+"/users/@me/mfa/totp/disable",{
method:"POST",
headers:this.headers,
body:JSON.stringify({
code:_
})
}).then(r=>r.json()).then(json=>{
if(json.message){
alert(json.message);
}else{
this.mfa_enabled=false;
alert("2FA turned off successfully");
}
});
})
}else{
security.addButtonInput("","Enable 2FA",async ()=>{
let secret=""
for(let i=0;i<18;i++){
secret+="ABCDEFGHIJKLMNOPQRSTUVWXYZ234567"[Math.floor(Math.random()*32)];
}
let password="";
let code="";
const addmodel=new Fullscreen(
["vdiv",
["title","2FA set up"],
["text","Copy this secret into your totp(time-based one time password) app"],
["text",`Your secret is: ${secret} and it's 6 digits, with a 30 second token period`],
["textbox","Account password:","",function(){password=this.value}],
["textbox","Code:","",function(){code=this.value}],
["button","","Submit",()=>{
fetch(this.info.api.toString()+"/users/@me/mfa/totp/enable/",{
method:"POST",
headers:this.headers,
body:JSON.stringify({
password,
code,
secret
})
}).then(r=>r.json()).then(json=>{
if(json.message){
alert(json.message);
}else{
alert("2FA set up successfully");
addmodel.hide();
this.mfa_enabled=true;
}
})
}]
]);
console.log("here :3")
addmodel.show();
})
}
}
settings.show();
}
/**

View file

@ -1,3 +1,5 @@
import { Fullscreen } from "/fullscreen.js";
const mobile = /iPhone|iPad|iPod|Android/i.test(navigator.userAgent);
export {mobile, getBulkUsers,getBulkInfo,setTheme,Specialuser}
function setTheme(){
@ -165,8 +167,8 @@ async function login(username:string, password:string, captcha:string){
}}
try{
const info=JSON.parse(localStorage.getItem("instanceinfo"));
const url=new URL(info.login);
return await fetch(url.origin+'/api/auth/login',options).then(response=>response.json())
const api=info.login;
return await fetch(api+'/auth/login',options).then(response=>response.json())
.then((response) => {
console.log(response,response.message)
if("Invalid Form Body"===response.message){
@ -193,9 +195,33 @@ async function login(username:string, password:string, captcha:string){
}
return;
}else{
adduser({serverurls:JSON.parse(localStorage.getItem("instanceinfo")),email:username,token:response.token});
window.location.href = '/channels/@me';
return response.token;
console.log(response);
if(response.ticket){
let onetimecode="";
new Fullscreen(["vdiv",["title","2FA code:"],["textbox","","",function(){onetimecode=this.value}],["button","","Submit",function(){
fetch(api+"/auth/mfa/totp",{
method:"POST",
headers:{
"Content-Type": "application/json"
},
body:JSON.stringify({
code:onetimecode,
ticket:response.ticket,
})
}).then(r=>r.json()).then(response=>{
if(response.message){
alert(response.message)
}else{
adduser({serverurls:JSON.parse(localStorage.getItem("instanceinfo")),email:username,token:response.token});
window.location.href = '/channels/@me';
}
})
}]]).show();
}else{
adduser({serverurls:JSON.parse(localStorage.getItem("instanceinfo")),email:username,token:response.token});
window.location.href = '/channels/@me';
return response.token;
}
}
})
}catch(error){

View file

@ -2,10 +2,13 @@ import { Permissions } from "./permissions.js";
import { Guild } from "./guild.js";
import { SnowFlake } from "./snowflake.js";
import { Role } from "./role.js";
interface OptionsElement {
interface OptionsElement {//OptionsElement<x>
generateHTML():HTMLElement;
submit:()=>void;
//watchForChange:(func:(arg1:x)=>void)=>void
}
//future me stuff
class Buttons implements OptionsElement{
readonly name:string;
readonly buttons:[string,Options|string][];
@ -176,6 +179,36 @@ class TextInput implements OptionsElement{
}
}
class ButtonInput implements OptionsElement{
readonly label:string;
readonly owner:Options;
readonly onClick:()=>void;
textContent:string;
constructor(label:string,textContent:string,onClick:()=>void,owner:Options,{}={}){
this.label=label;
this.owner=owner;
this.onClick=onClick;
this.textContent=textContent;
}
generateHTML():HTMLDivElement{
const div=document.createElement("div");
const span=document.createElement("span");
span.textContent=this.label;
div.append(span);
const button=document.createElement("button");
button.textContent=this.textContent;
button.onclick=this.onClickEvent.bind(this);
div.append(button);
return div;
}
private onClickEvent(ev:Event){
console.log("here :3")
this.onClick();
}
watchForChange(){}
submit(){}
}
class ColorInput implements OptionsElement{
readonly label:string;
readonly owner:Options;
@ -446,6 +479,11 @@ class Options implements OptionsElement{
this.options.push(htmlarea);
return htmlarea;
}
addButtonInput(label:string,textContent:string,onSubmit:()=>void){
const button=new ButtonInput(label,textContent,onSubmit,this);
this.options.push(button);
return button;
}
generateHTML():HTMLElement{
const div=document.createElement("div");
div.classList.add("titlediv");