initial 2FA support
This commit is contained in:
parent
c27a9af9bd
commit
13c896d756
6 changed files with 262 additions and 11 deletions
|
@ -34,6 +34,7 @@ class Localuser {
|
||||||
wsinterval;
|
wsinterval;
|
||||||
connectionSucceed = 0;
|
connectionSucceed = 0;
|
||||||
errorBackoff = 0;
|
errorBackoff = 0;
|
||||||
|
mfa_enabled;
|
||||||
constructor(userinfo) {
|
constructor(userinfo) {
|
||||||
this.token = userinfo.token;
|
this.token = userinfo.token;
|
||||||
this.userinfo = userinfo;
|
this.userinfo = userinfo;
|
||||||
|
@ -49,6 +50,7 @@ class Localuser {
|
||||||
this.guilds = [];
|
this.guilds = [];
|
||||||
this.guildids = new Map();
|
this.guildids = new Map();
|
||||||
this.user = new User(ready.d.user, this);
|
this.user = new User(ready.d.user, this);
|
||||||
|
this.mfa_enabled = ready.d.user.mfa_enabled;
|
||||||
this.userinfo.username = this.user.username;
|
this.userinfo.username = this.user.username;
|
||||||
this.userinfo.pfpsrc = this.user.getpfpsrc();
|
this.userinfo.pfpsrc = this.user.getpfpsrc();
|
||||||
this.status = this.ready.d.user_settings.status;
|
this.status = this.ready.d.user_settings.status;
|
||||||
|
@ -718,6 +720,67 @@ class Localuser {
|
||||||
}, { initColor: userinfos.accent_color });
|
}, { 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();
|
settings.show();
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
import { Fullscreen } from "/fullscreen.js";
|
||||||
const mobile = /iPhone|iPad|iPod|Android/i.test(navigator.userAgent);
|
const mobile = /iPhone|iPad|iPod|Android/i.test(navigator.userAgent);
|
||||||
export { mobile, getBulkUsers, getBulkInfo, setTheme, Specialuser };
|
export { mobile, getBulkUsers, getBulkInfo, setTheme, Specialuser };
|
||||||
function setTheme() {
|
function setTheme() {
|
||||||
|
@ -167,8 +168,8 @@ async function login(username, password, captcha) {
|
||||||
};
|
};
|
||||||
try {
|
try {
|
||||||
const info = JSON.parse(localStorage.getItem("instanceinfo"));
|
const info = JSON.parse(localStorage.getItem("instanceinfo"));
|
||||||
const url = new URL(info.login);
|
const api = info.login;
|
||||||
return await fetch(url.origin + '/api/auth/login', options).then(response => response.json())
|
return await fetch(api + '/auth/login', options).then(response => response.json())
|
||||||
.then((response) => {
|
.then((response) => {
|
||||||
console.log(response, response.message);
|
console.log(response, response.message);
|
||||||
if ("Invalid Form Body" === response.message) {
|
if ("Invalid Form Body" === response.message) {
|
||||||
|
@ -194,9 +195,35 @@ async function login(username, password, captcha) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
adduser({ serverurls: JSON.parse(localStorage.getItem("instanceinfo")), email: username, token: response.token });
|
console.log(response);
|
||||||
window.location.href = '/channels/@me';
|
if (response.ticket) {
|
||||||
return response.token;
|
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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import { Permissions } from "./permissions.js";
|
import { Permissions } from "./permissions.js";
|
||||||
import { SnowFlake } from "./snowflake.js";
|
import { SnowFlake } from "./snowflake.js";
|
||||||
import { Role } from "./role.js";
|
import { Role } from "./role.js";
|
||||||
|
//future me stuff
|
||||||
class Buttons {
|
class Buttons {
|
||||||
name;
|
name;
|
||||||
buttons;
|
buttons;
|
||||||
|
@ -175,6 +176,35 @@ class TextInput {
|
||||||
this.onSubmit(this.textContent);
|
this.onSubmit(this.textContent);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
class ButtonInput {
|
||||||
|
label;
|
||||||
|
owner;
|
||||||
|
onClick;
|
||||||
|
textContent;
|
||||||
|
constructor(label, textContent, onClick, owner, {} = {}) {
|
||||||
|
this.label = label;
|
||||||
|
this.owner = owner;
|
||||||
|
this.onClick = onClick;
|
||||||
|
this.textContent = textContent;
|
||||||
|
}
|
||||||
|
generateHTML() {
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
onClickEvent(ev) {
|
||||||
|
console.log("here :3");
|
||||||
|
this.onClick();
|
||||||
|
}
|
||||||
|
watchForChange() { }
|
||||||
|
submit() { }
|
||||||
|
}
|
||||||
class ColorInput {
|
class ColorInput {
|
||||||
label;
|
label;
|
||||||
owner;
|
owner;
|
||||||
|
@ -443,6 +473,11 @@ class Options {
|
||||||
this.options.push(htmlarea);
|
this.options.push(htmlarea);
|
||||||
return htmlarea;
|
return htmlarea;
|
||||||
}
|
}
|
||||||
|
addButtonInput(label, textContent, onSubmit) {
|
||||||
|
const button = new ButtonInput(label, textContent, onSubmit, this);
|
||||||
|
this.options.push(button);
|
||||||
|
return button;
|
||||||
|
}
|
||||||
generateHTML() {
|
generateHTML() {
|
||||||
const div = document.createElement("div");
|
const div = document.createElement("div");
|
||||||
div.classList.add("titlediv");
|
div.classList.add("titlediv");
|
||||||
|
|
|
@ -38,6 +38,7 @@ class Localuser{
|
||||||
wsinterval:NodeJS.Timeout;
|
wsinterval:NodeJS.Timeout;
|
||||||
connectionSucceed=0;
|
connectionSucceed=0;
|
||||||
errorBackoff=0;
|
errorBackoff=0;
|
||||||
|
mfa_enabled:boolean;
|
||||||
constructor(userinfo:Specialuser){
|
constructor(userinfo:Specialuser){
|
||||||
this.token=userinfo.token;
|
this.token=userinfo.token;
|
||||||
this.userinfo=userinfo;
|
this.userinfo=userinfo;
|
||||||
|
@ -53,6 +54,7 @@ class Localuser{
|
||||||
this.guilds=[];
|
this.guilds=[];
|
||||||
this.guildids=new Map();
|
this.guildids=new Map();
|
||||||
this.user=new User(ready.d.user,this);
|
this.user=new User(ready.d.user,this);
|
||||||
|
this.mfa_enabled=ready.d.user.mfa_enabled;
|
||||||
this.userinfo.username=this.user.username;
|
this.userinfo.username=this.user.username;
|
||||||
this.userinfo.pfpsrc=this.user.getpfpsrc();
|
this.userinfo.pfpsrc=this.user.getpfpsrc();
|
||||||
this.status=this.ready.d.user_settings.status;
|
this.status=this.ready.d.user_settings.status;
|
||||||
|
@ -743,6 +745,66 @@ class Localuser{
|
||||||
},{initColor:userinfos.accent_color})
|
},{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();
|
settings.show();
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
import { Fullscreen } from "/fullscreen.js";
|
||||||
|
|
||||||
const mobile = /iPhone|iPad|iPod|Android/i.test(navigator.userAgent);
|
const mobile = /iPhone|iPad|iPod|Android/i.test(navigator.userAgent);
|
||||||
export {mobile, getBulkUsers,getBulkInfo,setTheme,Specialuser}
|
export {mobile, getBulkUsers,getBulkInfo,setTheme,Specialuser}
|
||||||
function setTheme(){
|
function setTheme(){
|
||||||
|
@ -165,8 +167,8 @@ async function login(username:string, password:string, captcha:string){
|
||||||
}}
|
}}
|
||||||
try{
|
try{
|
||||||
const info=JSON.parse(localStorage.getItem("instanceinfo"));
|
const info=JSON.parse(localStorage.getItem("instanceinfo"));
|
||||||
const url=new URL(info.login);
|
const api=info.login;
|
||||||
return await fetch(url.origin+'/api/auth/login',options).then(response=>response.json())
|
return await fetch(api+'/auth/login',options).then(response=>response.json())
|
||||||
.then((response) => {
|
.then((response) => {
|
||||||
console.log(response,response.message)
|
console.log(response,response.message)
|
||||||
if("Invalid Form Body"===response.message){
|
if("Invalid Form Body"===response.message){
|
||||||
|
@ -193,9 +195,33 @@ async function login(username:string, password:string, captcha:string){
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}else{
|
}else{
|
||||||
adduser({serverurls:JSON.parse(localStorage.getItem("instanceinfo")),email:username,token:response.token});
|
console.log(response);
|
||||||
window.location.href = '/channels/@me';
|
if(response.ticket){
|
||||||
return response.token;
|
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){
|
}catch(error){
|
||||||
|
|
|
@ -2,10 +2,13 @@ import { Permissions } from "./permissions.js";
|
||||||
import { Guild } from "./guild.js";
|
import { Guild } from "./guild.js";
|
||||||
import { SnowFlake } from "./snowflake.js";
|
import { SnowFlake } from "./snowflake.js";
|
||||||
import { Role } from "./role.js";
|
import { Role } from "./role.js";
|
||||||
interface OptionsElement {
|
interface OptionsElement {//OptionsElement<x>
|
||||||
generateHTML():HTMLElement;
|
generateHTML():HTMLElement;
|
||||||
submit:()=>void;
|
submit:()=>void;
|
||||||
|
//watchForChange:(func:(arg1:x)=>void)=>void
|
||||||
}
|
}
|
||||||
|
//future me stuff
|
||||||
|
|
||||||
class Buttons implements OptionsElement{
|
class Buttons implements OptionsElement{
|
||||||
readonly name:string;
|
readonly name:string;
|
||||||
readonly buttons:[string,Options|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{
|
class ColorInput implements OptionsElement{
|
||||||
readonly label:string;
|
readonly label:string;
|
||||||
readonly owner:Options;
|
readonly owner:Options;
|
||||||
|
@ -446,6 +479,11 @@ class Options implements OptionsElement{
|
||||||
this.options.push(htmlarea);
|
this.options.push(htmlarea);
|
||||||
return 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{
|
generateHTML():HTMLElement{
|
||||||
const div=document.createElement("div");
|
const div=document.createElement("div");
|
||||||
div.classList.add("titlediv");
|
div.classList.add("titlediv");
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue