Revamp onboarding page and mechanics, change skipping to setting a step

master
Thomas Lynch 7 months ago
parent e432907878
commit bd8d28ff2b
  1. 4
      api.js
  2. 5
      components/MapRow.js
  3. 13
      controllers/account.js
  4. 1
      pages/csr.js
  5. 112
      pages/onboarding.js
  6. 2
      router.js

@ -4,8 +4,8 @@ import NProgress from 'nprogress';
export async function getAccount(dispatch, errorCallback, router) {
return ApiCall('/account.json', 'GET', null, dispatch, errorCallback, router);
}
export async function finishOnboarding(dispatch, errorCallback, router) {
return ApiCall('/forms/onboarding', 'POST', null, dispatch, errorCallback, router);
export async function updateOnboarding(body, dispatch, errorCallback, router) {
return ApiCall('/forms/onboarding', 'POST', body, dispatch, errorCallback, router);
}
export async function login(body, dispatch, errorCallback, router) {
return ApiCall('/forms/login', 'POST', body, dispatch, errorCallback, router);

@ -5,7 +5,10 @@ export default function MapRow({ row, onDeleteSubmit, name, csrf, showValues, ma
return (
<tr className='align-middle'>
<td className='text-left'>
<a className='btn btn-sm btn-danger' onClick={() => onDeleteSubmit(csrf, key)}>
<a className='btn btn-sm btn-danger' onClick={() => {
name === 'hosts' && confirm('If you get an error deleting a backend please contact support');
onDeleteSubmit(csrf, key);
}}>
<i className='bi-trash-fill pe-none' width='16' height='16' />
</a>
</td>

@ -3,6 +3,9 @@ import * as db from '../db.js';
import { extractMap, dynamicResponse } from '../util.js';
import { Resolver } from 'node:dns/promises';
import dotenv from 'dotenv';
await dotenv.config({ path: '.env' });
const resolver = new Resolver();
resolver.setServers(process.env.NAMESERVERS.split(','));
@ -171,18 +174,22 @@ export function logout(req, res) {
/**
* POST /forms/onboarding
* finish/skip onboarding
* update onboarding step
*/
export async function finishOnboarding(req, res) {
export async function updateOnboarding(req, res) {
if (!res.locals.user) {
return dynamicResponse(req, res, 400, { error: 'Bad request' });
}
const step = req.body.step;
if (!step || isNaN(step) || parseInt(step) !== +step) {
return dynamicResponse(req, res, 400, { error: 'Bad request' });
}
await db.db().collection('accounts')
.updateOne({
_id: res.locals.user.username
}, {
'$set': {
onboarding: true,
onboarding: parseInt(step),
}
});
return dynamicResponse(req, res, 302, { redirect: '/account' });

@ -120,3 +120,4 @@ export default function Csr(props) {
export async function getServerSideProps({ req, res, query, resolvedUrl, locale, locales, defaultLocale}) {
return { props: { user: res.locals.user || null, ...query } };
}

@ -4,12 +4,14 @@ import Head from 'next/head';
import Link from 'next/link';
import ErrorAlert from '../components/ErrorAlert.js';
import * as API from '../api.js';
import NProgress from 'nprogress';
export default function Onboarding(props) {
const router = useRouter();
const [state, dispatch] = useState(props);
const [error, setError] = useState();
const [csrState, setCsrState] = useState();
useEffect(() => {
if (!state.user) {
@ -17,7 +19,7 @@ export default function Onboarding(props) {
}
}, [state.user, state.maps, router]);
if (state.user == null) {
if (state.user == null || !state.txtRecords || state.txtRecords.length === 0) {
return (
<div className='d-flex flex-column'>
{error && <ErrorAlert error={error} />}
@ -31,13 +33,15 @@ export default function Onboarding(props) {
}
const { user, maps, globalAcl, csrf, aRecords, aaaaRecords, txtRecords } = state;
const domainAdded = user.domains.length > 0;
const backendMap = maps && maps.find(m => m.name === 'hosts');
const backendAdded = backendMap && backendMap.count > 0;
const certAdded = user.numCerts > 0;
const domainAdded = false; //user.domains && user.domains.length > 0;
const backendMap = false; //maps && maps.find(m => m.name === 'hosts');
const backendAdded = false; //backendMap && backendMap.count > 0;
const certAdded = false; //user.numCerts && user.numCerts > 0;
async function finishOnboarding(e) {
await API.finishOnboarding(dispatch, setError, router);
async function updateOnboarding(step) {
await API.updateOnboarding({
step
}, dispatch, setError, router);
await API.getAccount(dispatch, setError, router);
}
@ -67,6 +71,17 @@ export default function Onboarding(props) {
e.target.reset();
}
async function verifyCSR(e) {
e.preventDefault();
setError(null);
await API.verifyCSR({
_csrf: csrf,
csr: e.target.csr.value,
json: true,
}, setCsrState, setError, router);
NProgress.done(true);
}
return (<>
<Head>
@ -75,11 +90,16 @@ export default function Onboarding(props) {
{error && <ErrorAlert error={error} />}
<h5 className='fw-bold'>Onboarding</h5>
{!user.onboarding && <div className='my-2'>
<input onClick={finishOnboarding} className='btn btn-warning' type='submit' value='Skip Onboarding' />
</div>}
<h5 className='fw-bold'>
Onboarding
<div className='my-2'>
<input onClick={() => {
if (confirm('Are you sure you want to skip onboarding?')) {
updateOnboarding(7);
}
}} className='btn btn-sm btn-warning' type='submit' value='Skip Onboarding' />
</div>
</h5>
<div className='list-group'>
<div className='list-group-item d-flex gap-3'>
@ -94,11 +114,11 @@ export default function Onboarding(props) {
<p>Add your first domain (i.e. <code>example.com</code>) that you want to protect with BasedFlare.</p>
<p>You can add other domains and/or subdomains later from the &quot;domains&quot; page.</p>
</span>
<form className='d-flex mb-3' onSubmit={addDomain} action='/forms/domain/add' method='post'>
<form className='mb-3' onSubmit={addDomain} action='/forms/domain/add' method='post'>
<input type='hidden' name='_csrf' value={csrf} />
<input type='hidden' name='onboarding' value='1' />
<input className='btn btn-success' type='submit' value='+' disabled={domainAdded} />
<input className='form-control mx-3' type='text' name='domain' placeholder='domain' disabled={domainAdded} required />
<input className='form-control mb-3' type='text' name='domain' placeholder='domain' disabled={domainAdded} required />
<input className='btn btn-success' type='submit' value='Add domain' disabled={domainAdded} />
</form>
</>}
{domainAdded && (<div><strong>
@ -162,36 +182,34 @@ export default function Onboarding(props) {
<span className='pt-1 form-checked-content'>
<strong style={{ textDecoration: backendAdded ? 'line-through' : '' }}>
<i className='bi-hdd-network-fill pe-none me-2' width='1em' height='1em' />
4. Setup a backend
4. Add a backend
</strong>
{!backendAdded && <>
<span className='d-block text-body-secondary mt-3'>
<p>Enter the backend server IP address and port in ip:port format, e.g. <code>12.34.56.78:443</code>.</p>
<p>This is the &quot;origin&quot; that you want BasedFlare to proxy traffic to.</p>
</span>
<form onSubmit={addToMap} className='d-flex mb-3' action='/forms/map/hosts/add' method='post'>
<form onSubmit={addToMap} className='mb-3' action='/forms/map/hosts/add' method='post'>
<input type='hidden' name='_csrf' value={csrf} />
<input type='hidden' name='onboarding' value='1' />
<input className='btn btn-success' type='submit' value='+'
//disabled={backendAdded}
/>
<select className='form-select mx-3' name='key' defaultValue=''
//disabled={backendAdded}
<select className='form-select mb-3' name='key' defaultValue=''
disabled={backendAdded}
required>
<option value=''>select domain</option>
{user.domains.map((d, i) => (<option key={'option'+i} value={d}>{d}</option>))}
{(user.domains||[]).map((d, i) => (<option key={'option'+i} value={d}>{d}</option>))}
</select>
{
(process.env.NEXT_PUBLIC_CUSTOM_BACKENDS_ENABLED) &&
<input
className='form-control ml-2'
className='form-control mb-3'
type='text'
name='value'
placeholder='backend ip:port'
//disabled={backendAdded}
disabled={backendAdded}
required
/>
}
<input className='btn btn-success' type='submit' value='Add backend' disabled={backendAdded} />
</form>
</>}
{backendAdded && (<div><strong>
@ -214,12 +232,17 @@ export default function Onboarding(props) {
<p>Certificates last 90 days and will automatically renew when they have less than 30 days remaining.</p>
<p>You can manage certificates later from the &quot;HTTPS Certificates&quot; page.</p>
</span>
<form className='d-flex mb-3' onSubmit={addCert} action='/forms/cert/add' method='post'>
<form className='mb-3' onSubmit={addCert} action='/forms/cert/add' method='post'>
<input type='hidden' name='_csrf' value={csrf} />
<input type='hidden' name='onboarding' value='1' />
<input className='btn btn-success' type='submit' value='+' disabled={certAdded} />
<input className='form-control mx-3' type='text' name='subject' placeholder='domain.com' disabled={certAdded} required />
<input className='form-control me-3' type='text' name='altnames' placeholder='www.domain.com,staging.domain.com,etc...' disabled={certAdded} required />
<input className='form-control mb-3' type='text' name='subject' placeholder='domain.com' disabled={certAdded} required />
<textarea
className='form-control mb-3'
name='altnames'
placeholder={'www.domain.com\r\ntest.example.com\r\netc...'}
rows={4}
required />
<input className='btn btn-success' type='submit' value='Generate certificate' disabled={certAdded} />
</form>
</>}
{certAdded && (<div><strong>
@ -232,13 +255,13 @@ export default function Onboarding(props) {
<input className='form-check-input flex-shrink-0' type='checkbox' value='' checked={certAdded} disabled />
<span className='pt-1 form-checked-content'>
<strong>
<i className='bi-file-earmark-lock-fill pe-none me-2' width='1em' height='1em' />
<i className='bi-building-fill-lock pe-none me-2' width='1em' height='1em' />
6. Get your HTTPS CSR signed
</strong>
<span className='d-block text-body-secondary mt-3'>
<p>Finally, generate a certificate signing request for your origin server(s) and have BasedFlare sign it.</p>
<p>This allows BasedFlare servers to verify the connection to your backend and prevents trivial MITM attacks and other weaknesses that are possible with e.g self-signed certificates in CloudFlare&apos;s &quot;flexible&quot; or &quot;full&quot; ssl mode.</p>
<ol>
<ol className='text-break'>
<li>Generate the private key and certificate signing request for your domains on your origin server:
<p>
<code>
@ -252,11 +275,32 @@ export default function Onboarding(props) {
<li>You can then setup <code>origin.key</code> and <code>origin.crt</code>, as the key and certificate respectively, in your origin web server.</li>
</ol>
</span>
<form className='d-flex mb-3' action='/forms/csr/verify' method='post'>
<form onSubmit={verifyCSR} className='mb-3' action='/forms/csr/verify' method='post'>
<input type='hidden' name='_csrf' value={csrf} />
<textarea className='form-control mx-3' name='csr' placeholder='-----BEGIN CERTIFICATE REQUEST----- ...' required />
<input className='btn btn-success' type='submit' value='Verify' />
<textarea
className='form-control mb-3'
name='csr'
placeholder={'-----BEGIN CERTIFICATE REQUEST-----\n...'}
rows={4}
required />
<button className='btn btn-sm btn-success' type='submit'>
<i className='bi-plus-lg pe-1' width='16' height='16' />
Verify CSR
</button>
</form>
{csrState && csrState.csr && <div>
<div className='mb-2'>
<label className='form-label w-100'>Here&apos;s your certificate:
<textarea
className='form-control'
name='csr'
value={csrState.csr}
rows={10}
readOnly
required />
</label>
</div>
</div>}
</span>
</div>
<div className='list-group-item d-flex gap-3 justify-content-center'>

@ -229,7 +229,7 @@ export default function router(server, app) {
useSession,
fetchSession,
checkSession,
accountController.finishOnboarding,
accountController.updateOnboarding,
);
server.post('/forms/logout', useSession, accountController.logout);
server.post(

Loading…
Cancel
Save