login
parent
98a1906860
commit
f3c8a2e482
|
|
@ -8,7 +8,6 @@ export function connectApi(url: string) {
|
||||||
const connect = async () => {
|
const connect = async () => {
|
||||||
try {
|
try {
|
||||||
connectionAttempts ++;
|
connectionAttempts ++;
|
||||||
// console.log('connecting to', url);
|
|
||||||
socket = new WebSocket(url);
|
socket = new WebSocket(url);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
if(destroy) return;
|
if(destroy) return;
|
||||||
|
|
@ -25,6 +24,7 @@ export function connectApi(url: string) {
|
||||||
socket.addEventListener('open', () => {
|
socket.addEventListener('open', () => {
|
||||||
if(socket === null) return;
|
if(socket === null) return;
|
||||||
connectionAttempts = 0;
|
connectionAttempts = 0;
|
||||||
|
console.log('connected to', url);
|
||||||
// socket.send('Hello Server!');
|
// socket.send('Hello Server!');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -5,10 +5,12 @@ import { FaUserPlus } from 'react-icons/fa';
|
||||||
import ServerConnection from "../components/ServerConnection";
|
import ServerConnection from "../components/ServerConnection";
|
||||||
import useHomeServer from "../contexts/PersistentState/useHomeServerNative";
|
import useHomeServer from "../contexts/PersistentState/useHomeServerNative";
|
||||||
import { BigButton } from "./BigButton";
|
import { BigButton } from "./BigButton";
|
||||||
import { SignUp } from "./SignUp";
|
|
||||||
import { MdOutlineNavigateNext } from 'react-icons/md';
|
import { MdOutlineNavigateNext } from 'react-icons/md';
|
||||||
import useHover from "../hooks/useHover";
|
import useHover from "../hooks/useHover";
|
||||||
import { AiOutlineEdit } from "react-icons/ai";
|
import { AiOutlineEdit } from "react-icons/ai";
|
||||||
|
import { useApi } from "../lib/useApi";
|
||||||
|
import useSessionToken from "../hooks/useSessionToken";
|
||||||
|
import useClientId from "../hooks/useClientId";
|
||||||
|
|
||||||
export default function NewAccount() {
|
export default function NewAccount() {
|
||||||
|
|
||||||
|
|
@ -70,11 +72,8 @@ export default function NewAccount() {
|
||||||
// const homeServerInputRef = useRef<HTMLInputElement>(null);
|
// const homeServerInputRef = useRef<HTMLInputElement>(null);
|
||||||
const { setHomeServer, homeServer } = useHomeServer();
|
const { setHomeServer, homeServer } = useHomeServer();
|
||||||
const [homeServerInput, setHomeServerInput] = useState<string>(homeServer ?? '');
|
const [homeServerInput, setHomeServerInput] = useState<string>(homeServer ?? '');
|
||||||
const [usernameInput, setUsernameInput] = useState('');
|
|
||||||
const [authCodeInput, setAuthCodeInput] = useState('');
|
|
||||||
const [returning, setReturning] = useState(true);
|
const [returning, setReturning] = useState(true);
|
||||||
const [connection, setConnection] = useState<WebSocket | null>(null);
|
// const [connection, setConnection] = useState<WebSocket | null>(null);
|
||||||
const [connecting, setConnecting] = useState(false);
|
|
||||||
const [connectionError, setConnectionError] = useState('');
|
const [connectionError, setConnectionError] = useState('');
|
||||||
const [edittingHomeServer, setEdittingHomeServer] = useState(false);
|
const [edittingHomeServer, setEdittingHomeServer] = useState(false);
|
||||||
const [homeServerInputRef, homeServerHovered] = useHover<HTMLInputElement>();
|
const [homeServerInputRef, homeServerHovered] = useHover<HTMLInputElement>();
|
||||||
|
|
@ -88,29 +87,43 @@ export default function NewAccount() {
|
||||||
}
|
}
|
||||||
}, [homeServer]);
|
}, [homeServer]);
|
||||||
|
|
||||||
const connect = useCallback((url: string) => {
|
const [connecting, setConnecting] = useState(false);
|
||||||
|
const [connectionSucceeded, setConnectionSucceeded] = useState(false);
|
||||||
|
|
||||||
|
const connect = useCallback(() => {
|
||||||
if(connecting) return;
|
if(connecting) return;
|
||||||
setHomeServer(url);
|
const url = homeServerInput;
|
||||||
setConnecting(true);
|
setConnecting(true);
|
||||||
|
try {
|
||||||
const ws = new WebSocket(url);
|
const ws = new WebSocket(url);
|
||||||
|
|
||||||
|
ws.addEventListener('open', () => {
|
||||||
|
setConnecting(false);
|
||||||
|
setConnectionSucceeded(true);
|
||||||
|
setHomeServer(homeServerInput);
|
||||||
|
setEdittingHomeServer(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
ws.addEventListener('error', (e) => {
|
||||||
|
setConnecting(false);
|
||||||
|
setConnectionSucceeded(false);
|
||||||
|
setConnectionError('Connection failed')
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
setConnecting(false)
|
||||||
|
setConnectionSucceeded(false);
|
||||||
|
setConnectionError('Connection failed in catch');
|
||||||
|
}
|
||||||
|
}, [connecting, homeServerInput]);
|
||||||
|
|
||||||
ws.addEventListener('open', () => {
|
const next = useCallback(() => {
|
||||||
setConnecting(false);
|
// debugger;
|
||||||
setConnection(ws);
|
if(edittingHomeServer) {
|
||||||
setConnectionError('');
|
connect()
|
||||||
});
|
} else {
|
||||||
|
console.log('do login');
|
||||||
ws.addEventListener('close', (e) => {
|
}
|
||||||
setConnecting(false);
|
}, [homeServer, homeServerInput, edittingHomeServer, connect])
|
||||||
setConnection(null);
|
|
||||||
console.log(e)
|
|
||||||
});
|
|
||||||
|
|
||||||
ws.addEventListener('error', (e) => {
|
|
||||||
setConnectionError('Connection failed')
|
|
||||||
});
|
|
||||||
}, [connecting]);
|
|
||||||
|
|
||||||
// return (
|
// return (
|
||||||
// <div style={{
|
// <div style={{
|
||||||
|
|
@ -181,7 +194,8 @@ export default function NewAccount() {
|
||||||
background: 'var(--neutral-3)',
|
background: 'var(--neutral-3)',
|
||||||
}}>
|
}}>
|
||||||
<div style={{
|
<div style={{
|
||||||
width: '450px',
|
width: 'calc(100% - 40px)',
|
||||||
|
maxWidth: '450px',
|
||||||
background: 'var(--neutral-4)',
|
background: 'var(--neutral-4)',
|
||||||
boxShadow: '0px 4px 20px 0px var(--neutral-1)',
|
boxShadow: '0px 4px 20px 0px var(--neutral-1)',
|
||||||
borderRadius: '8px',
|
borderRadius: '8px',
|
||||||
|
|
@ -254,57 +268,173 @@ export default function NewAccount() {
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
setEdittingHomeServer(true);
|
setEdittingHomeServer(true);
|
||||||
}}
|
}}
|
||||||
onKeyPress={(e: any) => {
|
onKeyPress={(e: any) => e.code === 'Enter' && next()}
|
||||||
if(e.code === 'Enter') {
|
|
||||||
if(homeServer === homeServerInput)
|
|
||||||
return setEdittingHomeServer(false);
|
|
||||||
setHomeServer(homeServerInput)
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
></Input>
|
></Input>
|
||||||
|
<div style={{
|
||||||
|
paddingLeft: '16px'
|
||||||
|
}}>
|
||||||
|
{(connecting) ? (
|
||||||
|
<div style={{ color: 'var(--neutral-7)'}}>
|
||||||
|
Connecting...
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
(!connectionSucceeded) && (
|
||||||
|
<div style={{ color: 'var(--red)'}}>
|
||||||
|
{connectionError}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<Label>Username</Label>
|
<ServerConnection url={homeServer ?? ''}>
|
||||||
<div style={{
|
{(returning) ? (
|
||||||
transform: 'skew(6deg, 0deg)',
|
<Login disabled={edittingHomeServer}></Login>
|
||||||
margin: '8px',
|
) : (
|
||||||
}}>
|
<SignUp disabled={edittingHomeServer}></SignUp>
|
||||||
<Input
|
)}
|
||||||
disabled={edittingHomeServer}
|
</ServerConnection>
|
||||||
value={usernameInput}
|
{edittingHomeServer && <Next onClick={next}></Next>}
|
||||||
setValue={setUsernameInput}
|
|
||||||
></Input>
|
|
||||||
</div>
|
|
||||||
<Label>Auth Code</Label>
|
|
||||||
<div style={{
|
|
||||||
transform: 'skew(6deg, 0deg)',
|
|
||||||
margin: '8px',
|
|
||||||
}}>
|
|
||||||
<Input
|
|
||||||
disabled={edittingHomeServer}
|
|
||||||
value={authCodeInput}
|
|
||||||
setValue={setAuthCodeInput}
|
|
||||||
></Input>
|
|
||||||
</div>
|
|
||||||
<div style={{
|
|
||||||
transform: 'skew(6deg, 0deg)',
|
|
||||||
margin: '8px',
|
|
||||||
textAlign: 'right'
|
|
||||||
}}>
|
|
||||||
<BigButton
|
|
||||||
icon={MdOutlineNavigateNext}
|
|
||||||
text="Next"
|
|
||||||
selected={false}
|
|
||||||
angle={6}
|
|
||||||
width="auto"
|
|
||||||
inline={true}
|
|
||||||
onClick={() => {}}
|
|
||||||
></BigButton>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function Next(props: {
|
||||||
|
onClick?: (e: any) => void
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
<div style={{
|
||||||
|
transform: 'skew(6deg, 0deg)',
|
||||||
|
margin: '8px',
|
||||||
|
textAlign: 'right'
|
||||||
|
}}>
|
||||||
|
<BigButton
|
||||||
|
icon={MdOutlineNavigateNext}
|
||||||
|
text="Next"
|
||||||
|
selected={false}
|
||||||
|
angle={6}
|
||||||
|
width="auto"
|
||||||
|
inline={true}
|
||||||
|
onClick={props.onClick}
|
||||||
|
></BigButton>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
interface LoginProps {
|
||||||
|
disabled?: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
function Login(props: LoginProps) {
|
||||||
|
const [usernameInput, setUsernameInput] = useState('');
|
||||||
|
const [authCodeInput, setAuthCodeInput] = useState('');
|
||||||
|
const [error, setError] = useState<string | null>(null);
|
||||||
|
const [success, setSuccess] = useState<string | null>(null);
|
||||||
|
|
||||||
|
const { setSessionToken } = useSessionToken();
|
||||||
|
const { setClientId } = useClientId();
|
||||||
|
|
||||||
|
const { send } = useApi({
|
||||||
|
'session:login'({ err, sessionToken, clientId }) {
|
||||||
|
if(err) {
|
||||||
|
setSuccess(null);
|
||||||
|
setError(err);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setError(null);
|
||||||
|
setSuccess('Success!');
|
||||||
|
setTimeout(() => {
|
||||||
|
setClientId(clientId);
|
||||||
|
setSessionToken(sessionToken);
|
||||||
|
}, 1000)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const next = () => {
|
||||||
|
send('session:login', {
|
||||||
|
username: usernameInput,
|
||||||
|
totp: authCodeInput
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Label>Username</Label>
|
||||||
|
<div style={{
|
||||||
|
transform: 'skew(6deg, 0deg)',
|
||||||
|
margin: '8px',
|
||||||
|
}}>
|
||||||
|
<Input
|
||||||
|
disabled={props.disabled}
|
||||||
|
value={usernameInput}
|
||||||
|
setValue={setUsernameInput}
|
||||||
|
focusOnEenable={true}
|
||||||
|
></Input>
|
||||||
|
</div>
|
||||||
|
<Label>Auth Code</Label>
|
||||||
|
<div style={{
|
||||||
|
transform: 'skew(6deg, 0deg)',
|
||||||
|
margin: '8px',
|
||||||
|
}}>
|
||||||
|
<Input
|
||||||
|
disabled={props.disabled}
|
||||||
|
value={authCodeInput}
|
||||||
|
setValue={setAuthCodeInput}
|
||||||
|
></Input>
|
||||||
|
</div>
|
||||||
|
{error && <div style={{ color: 'var(--red)', textAlign: 'center' }}>
|
||||||
|
{error}
|
||||||
|
</div>}
|
||||||
|
{success && <div style={{ color: 'var(--green)', textAlign: 'center' }}>
|
||||||
|
{success}
|
||||||
|
</div>}
|
||||||
|
{!props.disabled && <Next onClick={next}></Next>}
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
interface SignUpProps {
|
||||||
|
disabled?: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
function SignUp(props: SignUpProps) {
|
||||||
|
const [usernameInput, setUsernameInput] = useState('');
|
||||||
|
const [authCodeInput, setAuthCodeInput] = useState('');
|
||||||
|
|
||||||
|
const next = () => {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Label>Username</Label>
|
||||||
|
<div style={{
|
||||||
|
transform: 'skew(6deg, 0deg)',
|
||||||
|
margin: '8px',
|
||||||
|
}}>
|
||||||
|
<Input
|
||||||
|
disabled={props.disabled}
|
||||||
|
value={usernameInput}
|
||||||
|
setValue={setUsernameInput}
|
||||||
|
focusOnEenable={true}
|
||||||
|
></Input>
|
||||||
|
</div>
|
||||||
|
<Label>Auth Code</Label>
|
||||||
|
<div style={{
|
||||||
|
transform: 'skew(6deg, 0deg)',
|
||||||
|
margin: '8px',
|
||||||
|
}}>
|
||||||
|
<Input
|
||||||
|
disabled={props.disabled}
|
||||||
|
value={authCodeInput}
|
||||||
|
setValue={setAuthCodeInput}
|
||||||
|
></Input>
|
||||||
|
</div>
|
||||||
|
{!props.disabled && <Next onClick={next}></Next>}
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
function Label(props: any) {
|
function Label(props: any) {
|
||||||
return <label style={{
|
return <label style={{
|
||||||
paddingLeft: '24px',
|
paddingLeft: '24px',
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
SELECT name, username, uid FROM clients WHERE username=?
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
SELECT totp FROM clients WHERE uid=?;
|
||||||
|
|
@ -19,7 +19,7 @@ export function expose(router: Function, port: number) {
|
||||||
}
|
}
|
||||||
const {action, data} = message;
|
const {action, data} = message;
|
||||||
try {
|
try {
|
||||||
if(typeof data === 'object' && 'sessionToken' in data) {
|
if(typeof data === 'object' && 'sessionToken' in data && data.sessionToken !== null) {
|
||||||
const auth = await validateSessionToken(data.sessionToken);
|
const auth = await validateSessionToken(data.sessionToken);
|
||||||
data.$sessionToken = data.sessionToken;
|
data.$sessionToken = data.sessionToken;
|
||||||
delete data['sessionToken'];
|
delete data['sessionToken'];
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,16 @@
|
||||||
|
import query from "../db/query";
|
||||||
|
import addSessionToken from '../db/snippets/session/new.sql';
|
||||||
|
import { rb32 } from "../lib/rb32";
|
||||||
|
|
||||||
|
export const generateSessionToken = async (clientId: string) => {
|
||||||
|
let token = '';
|
||||||
|
for (let i = 0; i < 64; i++) {
|
||||||
|
token += rb32();
|
||||||
|
}
|
||||||
|
console.log('created session token', clientId, token);
|
||||||
|
// scnd min hr day year
|
||||||
|
const year = 1000 * 60 * 60 * 24 * 365;
|
||||||
|
const expiration = Date.now() + year;
|
||||||
|
await query(addSessionToken, clientId, expiration, token);
|
||||||
|
return token;
|
||||||
|
};
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
import { rb32 } from "../lib/rb32";
|
||||||
|
|
||||||
|
export const generateTotpKey = () => rb32() + rb32();
|
||||||
|
|
@ -0,0 +1,8 @@
|
||||||
|
import query from '../db/query'
|
||||||
|
import getByUsername from '../db/snippets/client/getByUsername.sql'
|
||||||
|
|
||||||
|
export async function getClientIdByUsername(username: string) {
|
||||||
|
const res = await query(getByUsername, username);
|
||||||
|
if(res === null || res.length !== 1) return null;
|
||||||
|
return res[0].uid;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,25 @@
|
||||||
|
import { randomBytes } from 'crypto';
|
||||||
|
|
||||||
|
// 0 1 2 3 4
|
||||||
|
// | | | | | |
|
||||||
|
// 0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7 0
|
||||||
|
// 0 1 2 3 4 0 1 2 3 4 0 1 2 3 4 0 1 2 3 4 0 1 2 3 4 0 1 2 3 4 0 1 2 3 4 0 1 2 3 4 0
|
||||||
|
// | | | | | | | | |
|
||||||
|
// a b c d e f g h
|
||||||
|
const mask = (len: number) => Math.pow(2, len) - 1;
|
||||||
|
const manipulate = (b: number, start: number, len: number, end: number) => (((b >> start) & mask(len)) << end) & (mask(len) << end);
|
||||||
|
const dict = (n: number): string => 'ABCDEFGHIJKLMNOPQRSTUVWXYZ234567'[n] as string;
|
||||||
|
export function rb32() {
|
||||||
|
const bytes = randomBytes(5);
|
||||||
|
|
||||||
|
const a = manipulate(bytes[0], 3, 5, 0);
|
||||||
|
const b = manipulate(bytes[0], 0, 3, 2) | manipulate(bytes[1], 6, 2, 0);
|
||||||
|
const c = manipulate(bytes[1], 1, 5, 0);
|
||||||
|
const d = manipulate(bytes[1], 0, 1, 4) | manipulate(bytes[2], 4, 4, 0);
|
||||||
|
const e = manipulate(bytes[2], 0, 4, 1) | manipulate(bytes[3], 7, 1, 0);
|
||||||
|
const f = manipulate(bytes[3], 2, 5, 0);
|
||||||
|
const g = manipulate(bytes[3], 0, 2, 3) | manipulate(bytes[4], 5, 3, 0);
|
||||||
|
const h = manipulate(bytes[4], 0, 5, 0);
|
||||||
|
|
||||||
|
return dict(a) + dict(b) + dict(c) + dict(d) + dict(e) + dict(f) + dict(g) + dict(h);
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,9 @@
|
||||||
|
import query from '../db/query'
|
||||||
|
import getTotpKey from '../db/snippets/client/getTotpKeyByClientId.sql'
|
||||||
|
import { validateTotp } from './validateTotp';
|
||||||
|
|
||||||
|
export async function validateClientTotp(clientId: string, code: string) {
|
||||||
|
const res = await query(getTotpKey, clientId);
|
||||||
|
if(res === null || res.length !== 1) return false;
|
||||||
|
return validateTotp(res[0].totp, code);
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,9 @@
|
||||||
|
import getToken from 'totp-generator';
|
||||||
|
|
||||||
|
export const validateTotp = (key: string, code: string) => {
|
||||||
|
return [
|
||||||
|
getToken(key, { timestamp: Date.now() }),
|
||||||
|
getToken(key, { timestamp: Date.now() - 30 * 1000 }),
|
||||||
|
getToken(key, { timestamp: Date.now() - 2 * 30 * 1000 })
|
||||||
|
].includes(code);
|
||||||
|
};
|
||||||
|
|
@ -4,6 +4,12 @@ import { reply } from "../lib/WebSocketServer"
|
||||||
import invalidate from '../db/snippets/session/invalidate.sql'
|
import invalidate from '../db/snippets/session/invalidate.sql'
|
||||||
import _get from '../db/snippets/session/get.sql'
|
import _get from '../db/snippets/session/get.sql'
|
||||||
import query from "../db/query";
|
import query from "../db/query";
|
||||||
|
import { getClientIdByUsername } from "../lib/getClientIdByUsername";
|
||||||
|
import { generateSessionToken } from "../lib/generateSessionToken";
|
||||||
|
import { validateTotp } from "../lib/validateTotp";
|
||||||
|
import { validateClientTotp } from "../lib/validateClientTotp";
|
||||||
|
|
||||||
|
const randomWait = async () => await new Promise(res => setTimeout(res, Math.random() * 300));
|
||||||
|
|
||||||
export default router({
|
export default router({
|
||||||
async 'invalidate'(data: any) {
|
async 'invalidate'(data: any) {
|
||||||
|
|
@ -11,6 +17,25 @@ export default router({
|
||||||
return reply({
|
return reply({
|
||||||
err: null
|
err: null
|
||||||
})
|
})
|
||||||
|
},
|
||||||
|
async 'login'(data: any) {
|
||||||
|
await randomWait();
|
||||||
|
const { username, totp } = data;
|
||||||
|
const clientId = await getClientIdByUsername(username);
|
||||||
|
if(clientId === null) return reply({
|
||||||
|
err: 'Incorrect username or auth code'
|
||||||
|
});
|
||||||
|
const validTotp = await validateClientTotp(clientId, totp);
|
||||||
|
console.log(username, clientId, validTotp);
|
||||||
|
if(!validTotp) return reply({
|
||||||
|
err: 'Incorrect username or auth code'
|
||||||
|
});
|
||||||
|
const sessionToken = await generateSessionToken(clientId);
|
||||||
|
return reply({
|
||||||
|
err: null,
|
||||||
|
sessionToken,
|
||||||
|
clientId
|
||||||
|
})
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,68 +1,17 @@
|
||||||
import router from "../lib/router"
|
import router from "../lib/router"
|
||||||
import { reply } from "../lib/WebSocketServer"
|
import { reply } from "../lib/WebSocketServer"
|
||||||
import { randomBytes } from 'crypto';
|
|
||||||
import getToken from 'totp-generator';
|
|
||||||
import query from "../db/query";
|
import query from "../db/query";
|
||||||
import confirm from '../db/snippets/totp/confirm.sql';
|
import confirm from '../db/snippets/totp/confirm.sql';
|
||||||
import addSessionToken from '../db/snippets/session/new.sql';
|
import { validateTotp } from "../lib/validateTotp";
|
||||||
|
import { generateSessionToken } from "../lib/generateSessionToken";
|
||||||
const validateTotp = (key: string, code: string) => {
|
import { generateTotpKey } from "../lib/generateTotpKey";
|
||||||
return [
|
|
||||||
getToken(key, { timestamp: Date.now() }),
|
|
||||||
getToken(key, { timestamp: Date.now() - 30 * 1000 }),
|
|
||||||
getToken(key, { timestamp: Date.now() - 2 * 30 * 1000})
|
|
||||||
].includes(code);
|
|
||||||
}
|
|
||||||
|
|
||||||
const generateSessionToken = async (clientId: string) => {
|
|
||||||
let token = '';
|
|
||||||
for(let i = 0; i < 64; i ++) {
|
|
||||||
token += rb32()
|
|
||||||
}
|
|
||||||
console.log('created session token', clientId, token);
|
|
||||||
// scnd min hr day year
|
|
||||||
const year = 1000 * 60 * 60 * 24 * 365;
|
|
||||||
const expiration = Date.now() + year;
|
|
||||||
await query(addSessionToken, clientId, expiration, token);
|
|
||||||
return token;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 0 1 2 3 4
|
|
||||||
// | | | | | |
|
|
||||||
// 0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7 0
|
|
||||||
// 0 1 2 3 4 0 1 2 3 4 0 1 2 3 4 0 1 2 3 4 0 1 2 3 4 0 1 2 3 4 0 1 2 3 4 0 1 2 3 4 0
|
|
||||||
// | | | | | | | | |
|
|
||||||
// a b c d e f g h
|
|
||||||
const mask = (len: number) => Math.pow(2, len) - 1;
|
|
||||||
|
|
||||||
const manipulate = (b: number, start: number, len: number, end: number) =>
|
|
||||||
(((b >> start) & mask(len)) << end) & (mask(len) << end)
|
|
||||||
|
|
||||||
const dict = (n: number): string => 'ABCDEFGHIJKLMNOPQRSTUVWXYZ234567'[n] as string;
|
|
||||||
|
|
||||||
function rb32() {
|
|
||||||
const bytes = randomBytes(5);
|
|
||||||
|
|
||||||
const a = manipulate(bytes[0], 3, 5, 0);
|
|
||||||
const b = manipulate(bytes[0], 0, 3, 2) | manipulate(bytes[1], 6, 2, 0);
|
|
||||||
const c = manipulate(bytes[1], 1, 5, 0);
|
|
||||||
const d = manipulate(bytes[1], 0, 1, 4) | manipulate(bytes[2], 4, 4, 0);
|
|
||||||
const e = manipulate(bytes[2], 0, 4, 1) | manipulate(bytes[3], 7, 1, 0);
|
|
||||||
const f = manipulate(bytes[3], 2, 5, 0);
|
|
||||||
const g = manipulate(bytes[3], 0, 2, 3) | manipulate(bytes[4], 5, 3, 0);
|
|
||||||
const h = manipulate(bytes[4], 0, 5, 0);
|
|
||||||
|
|
||||||
return dict(a) + dict(b) + dict(c) + dict(d) + dict(e) + dict(f) + dict(g) + dict(h);
|
|
||||||
}
|
|
||||||
|
|
||||||
const totpKey = () => rb32() + rb32();
|
|
||||||
|
|
||||||
const proposals: any = {}
|
const proposals: any = {}
|
||||||
|
|
||||||
export default router({
|
export default router({
|
||||||
'propose'(clientId: string) {
|
'propose'(clientId: string) {
|
||||||
if(clientId in proposals) return reply(proposals[clientId]);
|
if(clientId in proposals) return reply(proposals[clientId]);
|
||||||
const key = totpKey();
|
const key = generateTotpKey();
|
||||||
proposals[clientId] = key;
|
proposals[clientId] = key;
|
||||||
console.log(clientId, proposals)
|
console.log(clientId, proposals)
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
|
|
|
||||||
Reference in New Issue