Add required username to magic link authentication and fix many errors
This commit is contained in:
parent
acc5233185
commit
b75b227ca1
@ -46,5 +46,8 @@
|
||||
view: {
|
||||
cache: false
|
||||
},
|
||||
magic_link: {
|
||||
validity_period: 20,
|
||||
},
|
||||
approval_mode: false,
|
||||
}
|
||||
|
@ -14,5 +14,8 @@
|
||||
mail: {
|
||||
secure: true,
|
||||
allow_invalid_tls: false
|
||||
}
|
||||
},
|
||||
magic_link: {
|
||||
validity_period: 900,
|
||||
},
|
||||
}
|
||||
|
@ -4,6 +4,6 @@
|
||||
user: "root",
|
||||
password: "",
|
||||
database: "swaf_test",
|
||||
create_database_automatically: true
|
||||
}
|
||||
create_database_automatically: true,
|
||||
},
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
import config from "config";
|
||||
import {MailTemplate} from "./Mail";
|
||||
import {MailTemplate} from "./mail/Mail";
|
||||
|
||||
export const MAGIC_LINK_MAIL = new MailTemplate(
|
||||
'magic_link',
|
||||
|
@ -23,22 +23,23 @@ import MagicLinkWebSocketListener from "./auth/magic_link/MagicLinkWebSocketList
|
||||
import MagicLinkController from "./auth/magic_link/MagicLinkController";
|
||||
import AddPasswordToUsersMigration from "./auth/password/AddPasswordToUsersMigration";
|
||||
import AddNameToUsersMigration from "./auth/migrations/AddNameToUsersMigration";
|
||||
import packageJson = require('../package.json');
|
||||
import CsrfProtectionComponent from "./components/CsrfProtectionComponent";
|
||||
import MailController from "./mail/MailController";
|
||||
import WebSocketServerComponent from "./components/WebSocketServerComponent";
|
||||
import Controller from "./Controller";
|
||||
import packageJson = require('../package.json');
|
||||
|
||||
export const MIGRATIONS = [
|
||||
CreateMigrationsTable,
|
||||
CreateUsersAndUserEmailsTableMigration,
|
||||
AddNameToUsersMigration,
|
||||
AddPasswordToUsersMigration,
|
||||
CreateMagicLinksTableMigration,
|
||||
AddNameToUsersMigration,
|
||||
];
|
||||
|
||||
export default class TestApp extends Application {
|
||||
private readonly addr: string;
|
||||
private readonly port: number;
|
||||
private expressAppComponent?: ExpressAppComponent;
|
||||
private magicLinkWebSocketListener?: MagicLinkWebSocketListener<this>;
|
||||
|
||||
public constructor(addr: string, port: number) {
|
||||
super(packageJson.version, true);
|
||||
@ -57,12 +58,8 @@ export default class TestApp extends Application {
|
||||
}
|
||||
|
||||
protected registerComponents(): void {
|
||||
this.expressAppComponent = new ExpressAppComponent(this.addr, this.port);
|
||||
const redisComponent = new RedisComponent();
|
||||
const mysqlComponent = new MysqlComponent();
|
||||
|
||||
// Base
|
||||
this.use(this.expressAppComponent);
|
||||
this.use(new ExpressAppComponent(this.addr, this.port));
|
||||
this.use(new LogRequestsComponent());
|
||||
|
||||
// Static files
|
||||
@ -73,12 +70,12 @@ export default class TestApp extends Application {
|
||||
this.use(new RedirectBackComponent());
|
||||
|
||||
// Services
|
||||
this.use(mysqlComponent);
|
||||
this.use(new MysqlComponent());
|
||||
this.use(new MailComponent());
|
||||
|
||||
// Session
|
||||
this.use(redisComponent);
|
||||
this.use(new SessionComponent(redisComponent));
|
||||
this.use(new RedisComponent());
|
||||
this.use(new SessionComponent(this.as(RedisComponent)));
|
||||
|
||||
// Utils
|
||||
this.use(new FormHelperComponent());
|
||||
@ -88,18 +85,29 @@ export default class TestApp extends Application {
|
||||
|
||||
// Auth
|
||||
this.use(new AuthComponent(this, new MagicLinkAuthMethod(this, MAGIC_LINK_MAIL), new PasswordAuthMethod(this)));
|
||||
|
||||
// WebSocket server
|
||||
this.use(new WebSocketServerComponent(this, this.as(ExpressAppComponent), this.as(RedisComponent)));
|
||||
}
|
||||
|
||||
protected registerWebSocketListeners(): void {
|
||||
this.magicLinkWebSocketListener = new MagicLinkWebSocketListener();
|
||||
this.use(this.magicLinkWebSocketListener);
|
||||
this.use(new MagicLinkWebSocketListener());
|
||||
}
|
||||
|
||||
protected registerControllers(): void {
|
||||
this.use(new MailController());
|
||||
this.use(new AuthController());
|
||||
|
||||
if (!this.magicLinkWebSocketListener) throw new Error('Magic link websocket listener not initialized.');
|
||||
this.use(new MagicLinkController(this.magicLinkWebSocketListener));
|
||||
this.use(new MagicLinkController(this.as<MagicLinkWebSocketListener<this>>(MagicLinkWebSocketListener)));
|
||||
|
||||
// Special home controller
|
||||
this.use(new class extends Controller {
|
||||
public routes(): void {
|
||||
this.get('/', (req, res) => {
|
||||
res.render('home');
|
||||
}, 'home');
|
||||
}
|
||||
}());
|
||||
}
|
||||
|
||||
public getExpressApp(): Express {
|
||||
|
@ -2,6 +2,11 @@ import Controller from "../Controller";
|
||||
import {NextFunction, Request, Response} from "express";
|
||||
import AuthComponent, {AuthMiddleware, RequireAuthMiddleware, RequireGuestMiddleware} from "./AuthComponent";
|
||||
import {BadRequestError} from "../HttpError";
|
||||
import ModelFactory from "../db/ModelFactory";
|
||||
import User from "./models/User";
|
||||
import UserPasswordComponent from "./password/UserPasswordComponent";
|
||||
import UserNameComponent from "./models/UserNameComponent";
|
||||
import {log} from "../Logger";
|
||||
|
||||
export default class AuthController extends Controller {
|
||||
public getRoutesPrefix(): string {
|
||||
@ -9,6 +14,8 @@ export default class AuthController extends Controller {
|
||||
}
|
||||
|
||||
public routes(): void {
|
||||
this.post('/logout', this.postLogout, 'logout', RequireAuthMiddleware);
|
||||
|
||||
this.use(async (req, res, next) => {
|
||||
const authGuard = this.getApp().as(AuthComponent).getAuthGuard();
|
||||
if (await authGuard.interruptAuth(req, res)) return;
|
||||
@ -18,14 +25,17 @@ export default class AuthController extends Controller {
|
||||
this.get('/', this.getAuth, 'auth', RequireGuestMiddleware);
|
||||
this.post('/login', this.postLogin, 'login', RequireGuestMiddleware);
|
||||
this.post('/register', this.postRegister, 'register', RequireGuestMiddleware);
|
||||
this.post('/logout', this.postLogout, 'logout', RequireAuthMiddleware);
|
||||
}
|
||||
|
||||
protected async getAuth(req: Request, res: Response, _next: NextFunction): Promise<void> {
|
||||
const authGuard = this.getApp().as(AuthComponent).getAuthGuard();
|
||||
|
||||
const userModelFactory = ModelFactory.get(User);
|
||||
const hasUsername = userModelFactory.hasComponent(UserNameComponent);
|
||||
res.render('auth/auth', {
|
||||
auth_methods: authGuard.getAuthMethodNames(),
|
||||
has_username: hasUsername,
|
||||
register_with_password: hasUsername && userModelFactory.hasComponent(UserPasswordComponent),
|
||||
});
|
||||
}
|
||||
|
||||
@ -53,9 +63,10 @@ export default class AuthController extends Controller {
|
||||
|
||||
const user = await method.findUserByIdentifier(identifier);
|
||||
if (!user) { // Register
|
||||
return isRegistration ?
|
||||
await method.attemptRegister(req, res, identifier) :
|
||||
await this.redirectToRegistration(req, res, identifier);
|
||||
if (!isRegistration) return await this.redirectToRegistration(req, res, identifier);
|
||||
|
||||
await method.attemptRegister(req, res, identifier);
|
||||
return;
|
||||
}
|
||||
|
||||
// Login
|
||||
@ -66,9 +77,10 @@ export default class AuthController extends Controller {
|
||||
const methods = await authGuard.getAuthMethodsByIdentifier(identifier);
|
||||
|
||||
if (methods.length === 0) { // Register
|
||||
return isRegistration ?
|
||||
await authGuard.getRegistrationMethod().attemptRegister(req, res, identifier) :
|
||||
await this.redirectToRegistration(req, res, identifier);
|
||||
if (!isRegistration) return await this.redirectToRegistration(req, res, identifier);
|
||||
|
||||
await authGuard.getRegistrationMethod().attemptRegister(req, res, identifier);
|
||||
return;
|
||||
}
|
||||
|
||||
const {user, method} = methods[0];
|
||||
|
@ -4,7 +4,7 @@ import User from "./models/User";
|
||||
import {Connection} from "mysql";
|
||||
import {Request, Response} from "express";
|
||||
import {PENDING_ACCOUNT_REVIEW_MAIL_TEMPLATE} from "../Mails";
|
||||
import Mail from "../Mail";
|
||||
import Mail from "../mail/Mail";
|
||||
import Controller from "../Controller";
|
||||
import config from "config";
|
||||
import Application from "../Application";
|
||||
|
@ -1,4 +1,4 @@
|
||||
export default {
|
||||
LOGIN: 'Login',
|
||||
REGISTER: 'Register',
|
||||
LOGIN: 'login',
|
||||
REGISTER: 'register',
|
||||
};
|
||||
|
@ -9,7 +9,7 @@ import geoip from "geoip-lite";
|
||||
import MagicLinkController from "./MagicLinkController";
|
||||
import RedirectBackComponent from "../../components/RedirectBackComponent";
|
||||
import Application from "../../Application";
|
||||
import {MailTemplate} from "../../Mail";
|
||||
import {MailTemplate} from "../../mail/Mail";
|
||||
import AuthMagicLinkActionType from "./AuthMagicLinkActionType";
|
||||
|
||||
export default class MagicLinkAuthMethod implements AuthMethod<MagicLink> {
|
||||
@ -25,7 +25,7 @@ export default class MagicLinkAuthMethod implements AuthMethod<MagicLink> {
|
||||
|
||||
public async findUserByIdentifier(identifier: string): Promise<User | null> {
|
||||
return (await UserEmail.select()
|
||||
.with('user')
|
||||
.with('user.mainEmail')
|
||||
.where('email', identifier)
|
||||
.first())?.user.getOrFail() || null;
|
||||
}
|
||||
@ -40,15 +40,17 @@ export default class MagicLinkAuthMethod implements AuthMethod<MagicLink> {
|
||||
public async interruptAuth(req: Request, res: Response): Promise<boolean> {
|
||||
const pendingLink = await MagicLink.select()
|
||||
.where('session_id', req.getSession().id)
|
||||
.where('action_type', [AuthMagicLinkActionType.LOGIN, AuthMagicLinkActionType.REGISTER], WhereTest.IN)
|
||||
.where('authorized', false)
|
||||
.first();
|
||||
|
||||
if (pendingLink && await pendingLink.isValid()) {
|
||||
res.redirect(Controller.route('magic_link_lobby', undefined, {
|
||||
redirect_uri: req.query.redirect_uri?.toString() || pendingLink.original_url || undefined,
|
||||
}));
|
||||
return true;
|
||||
if (pendingLink) {
|
||||
if (await pendingLink.isValid()) {
|
||||
res.redirect(Controller.route('magic_link_lobby', undefined, {
|
||||
redirect_uri: req.query.redirect_uri?.toString() || pendingLink.original_url || undefined,
|
||||
}));
|
||||
return true;
|
||||
} else {
|
||||
await pendingLink.delete();
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
@ -70,34 +72,30 @@ export default class MagicLinkAuthMethod implements AuthMethod<MagicLink> {
|
||||
}
|
||||
|
||||
private async auth(req: Request, res: Response, isRegistration: boolean, email: string): Promise<void> {
|
||||
if (!isRegistration || req.body.confirm_register === 'confirm') {
|
||||
const geo = geoip.lookup(req.ip);
|
||||
const actionType = isRegistration ? AuthMagicLinkActionType.REGISTER : AuthMagicLinkActionType.LOGIN;
|
||||
const geo = geoip.lookup(req.ip);
|
||||
const actionType = isRegistration ? AuthMagicLinkActionType.REGISTER : AuthMagicLinkActionType.LOGIN;
|
||||
|
||||
await MagicLinkController.sendMagicLink(
|
||||
this.app,
|
||||
req.getSession().id,
|
||||
actionType,
|
||||
Controller.route('auth', undefined, {
|
||||
redirect_uri: req.query.redirect_uri?.toString() || undefined,
|
||||
}),
|
||||
email,
|
||||
this.magicLinkMailTemplate,
|
||||
{
|
||||
type: actionType,
|
||||
ip: req.ip,
|
||||
geo: geo ? `${geo.city}, ${geo.country}` : 'Unknown location',
|
||||
},
|
||||
);
|
||||
|
||||
res.redirect(Controller.route('magic_link_lobby', undefined, {
|
||||
redirect_uri: req.query.redirect_uri?.toString() || RedirectBackComponent.getPreviousURL(req),
|
||||
}));
|
||||
} else {
|
||||
req.flash('register_identifier', email);
|
||||
res.redirect(Controller.route('auth', undefined, {
|
||||
await MagicLinkController.sendMagicLink(
|
||||
this.app,
|
||||
req.getSession().id,
|
||||
actionType,
|
||||
Controller.route('auth', undefined, {
|
||||
redirect_uri: req.query.redirect_uri?.toString() || undefined,
|
||||
}));
|
||||
}
|
||||
}),
|
||||
email,
|
||||
this.magicLinkMailTemplate,
|
||||
{
|
||||
type: actionType,
|
||||
ip: req.ip,
|
||||
geo: geo ? `${geo.city}, ${geo.country}` : 'Unknown location',
|
||||
},
|
||||
{
|
||||
username: req.body.name,
|
||||
},
|
||||
);
|
||||
|
||||
res.redirect(Controller.route('magic_link_lobby', undefined, {
|
||||
redirect_uri: req.query.redirect_uri?.toString() || RedirectBackComponent.getPreviousURL(req),
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
@ -3,7 +3,7 @@ import {Request, Response} from "express";
|
||||
import MagicLinkWebSocketListener from "./MagicLinkWebSocketListener";
|
||||
import {BadRequestError, NotFoundHttpError} from "../../HttpError";
|
||||
import Throttler from "../../Throttler";
|
||||
import Mail, {MailTemplate} from "../../Mail";
|
||||
import Mail, {MailTemplate} from "../../mail/Mail";
|
||||
import MagicLink from "../models/MagicLink";
|
||||
import config from "config";
|
||||
import Application from "../../Application";
|
||||
@ -14,6 +14,9 @@ import AuthComponent, {AuthMiddleware} from "../AuthComponent";
|
||||
import {AuthError, PendingApprovalAuthError, RegisterCallback} from "../AuthGuard";
|
||||
import UserEmail from "../models/UserEmail";
|
||||
import AuthMagicLinkActionType from "./AuthMagicLinkActionType";
|
||||
import {QueryVariable} from "../../db/MysqlConnectionManager";
|
||||
import UserNameComponent from "../models/UserNameComponent";
|
||||
import MagicLinkUserNameComponent from "../models/MagicLinkUserNameComponent";
|
||||
|
||||
export default class MagicLinkController<A extends Application> extends Controller {
|
||||
public static async sendMagicLink(
|
||||
@ -24,15 +27,16 @@ export default class MagicLinkController<A extends Application> extends Controll
|
||||
email: string,
|
||||
mailTemplate: MailTemplate,
|
||||
data: ParsedUrlQueryInput,
|
||||
magicLinkData: Record<string, QueryVariable> = {},
|
||||
): Promise<void> {
|
||||
Throttler.throttle('magic_link', 2, MagicLink.validityPeriod(), sessionId, 0, 0);
|
||||
Throttler.throttle('magic_link', 1, MagicLink.validityPeriod(), email, 0, 0);
|
||||
|
||||
const link = MagicLink.create({
|
||||
const link = MagicLink.create(Object.assign(magicLinkData, {
|
||||
session_id: sessionId,
|
||||
action_type: actionType,
|
||||
original_url: original_url,
|
||||
});
|
||||
}));
|
||||
|
||||
const token = await link.generateToken(email);
|
||||
await link.save();
|
||||
@ -55,7 +59,11 @@ export default class MagicLinkController<A extends Application> extends Controll
|
||||
// Auth
|
||||
try {
|
||||
return await req.as(AuthMiddleware).getAuthGuard().authenticateOrRegister(
|
||||
session, magicLink, undefined, undefined, async (connection, user) => {
|
||||
session, magicLink, undefined, async (connection, user) => {
|
||||
const userNameComponent = user.asOptional(UserNameComponent);
|
||||
if (userNameComponent) userNameComponent.name = magicLink.as(MagicLinkUserNameComponent).username;
|
||||
return [];
|
||||
}, async (connection, user) => {
|
||||
const callbacks: RegisterCallback[] = [];
|
||||
|
||||
const userEmail = UserEmail.create({
|
||||
|
@ -1,19 +1,25 @@
|
||||
import Migration from "../../db/Migration";
|
||||
import ModelFactory from "../../db/ModelFactory";
|
||||
import User from "../models/User";
|
||||
import UserNameComponent from "../UserNameComponent";
|
||||
import UserNameComponent from "../models/UserNameComponent";
|
||||
import MagicLink from "../models/MagicLink";
|
||||
import MagicLinkUserNameComponent from "../models/MagicLinkUserNameComponent";
|
||||
|
||||
export default class AddNameToUsersMigration extends Migration {
|
||||
public async install(): Promise<void> {
|
||||
await this.query(`ALTER TABLE users
|
||||
ADD COLUMN name VARCHAR(64) UNIQUE NOT NULL`);
|
||||
await this.query(`ALTER TABLE magic_links
|
||||
ADD COLUMN username VARCHAR(64) DEFAULT NULL`);
|
||||
}
|
||||
|
||||
public async rollback(): Promise<void> {
|
||||
await this.query('ALTER TABLE users DROP COLUMN name');
|
||||
await this.query('ALTER TABLE magic_links DROP COLUMN username');
|
||||
}
|
||||
|
||||
public registerModels(): void {
|
||||
ModelFactory.get(User).addComponent(UserNameComponent);
|
||||
ModelFactory.get(MagicLink).addComponent(MagicLinkUserNameComponent);
|
||||
}
|
||||
}
|
||||
|
12
src/auth/models/MagicLinkUserNameComponent.ts
Normal file
12
src/auth/models/MagicLinkUserNameComponent.ts
Normal file
@ -0,0 +1,12 @@
|
||||
import ModelComponent from "../../db/ModelComponent";
|
||||
import MagicLink from "./MagicLink";
|
||||
import {USERNAME_REGEXP} from "./UserNameComponent";
|
||||
import User from "./User";
|
||||
|
||||
export default class MagicLinkUserNameComponent extends ModelComponent<MagicLink> {
|
||||
public readonly username?: string = undefined;
|
||||
|
||||
protected init(): void {
|
||||
this.setValidation('name').defined().between(3, 64).regexp(USERNAME_REGEXP).unique(User, 'name');
|
||||
}
|
||||
}
|
@ -1,5 +1,5 @@
|
||||
import ModelComponent from "../db/ModelComponent";
|
||||
import User from "./models/User";
|
||||
import ModelComponent from "../../db/ModelComponent";
|
||||
import User from "../models/User";
|
||||
|
||||
export const USERNAME_REGEXP = /^[0-9a-z_-]+$/;
|
||||
|
@ -6,7 +6,7 @@ import UserPasswordComponent from "./UserPasswordComponent";
|
||||
export default class AddPasswordToUsersMigration extends Migration {
|
||||
public async install(): Promise<void> {
|
||||
await this.query(`ALTER TABLE users
|
||||
ADD COLUMN password VARCHAR(128) NOT NULL`);
|
||||
ADD COLUMN password VARCHAR(128) DEFAULT NULL`);
|
||||
}
|
||||
|
||||
public async rollback(): Promise<void> {
|
||||
|
@ -10,7 +10,7 @@ import {AuthError, PendingApprovalAuthError, RegisterCallback} from "../AuthGuar
|
||||
import Validator, {InvalidFormatValidationError, ValidationBag} from "../../db/Validator";
|
||||
import Controller from "../../Controller";
|
||||
import UserPasswordComponent from "./UserPasswordComponent";
|
||||
import UserNameComponent, {USERNAME_REGEXP} from "../UserNameComponent";
|
||||
import UserNameComponent, {USERNAME_REGEXP} from "../models/UserNameComponent";
|
||||
import ModelFactory from "../../db/ModelFactory";
|
||||
import {WhereOperator, WhereTest} from "../../db/ModelQuery";
|
||||
import {ServerError} from "../../HttpError";
|
||||
|
@ -1,6 +1,6 @@
|
||||
import ApplicationComponent from "../ApplicationComponent";
|
||||
import {Express} from "express";
|
||||
import Mail from "../Mail";
|
||||
import Mail from "../mail/Mail";
|
||||
import config from "config";
|
||||
import SecurityError from "../SecurityError";
|
||||
|
||||
|
@ -3,7 +3,7 @@ import Controller from "../Controller";
|
||||
import User from "../auth/models/User";
|
||||
import {Request, Response} from "express";
|
||||
import {BadRequestError, NotFoundHttpError} from "../HttpError";
|
||||
import Mail from "../Mail";
|
||||
import Mail from "../mail/Mail";
|
||||
import {ACCOUNT_REVIEW_NOTICE_MAIL_TEMPLATE} from "../Mails";
|
||||
import UserEmail from "../auth/models/UserEmail";
|
||||
import UserApprovedComponent from "../auth/models/UserApprovedComponent";
|
||||
|
@ -3,10 +3,10 @@ import config from "config";
|
||||
import {Options} from "nodemailer/lib/mailer";
|
||||
import {Environment} from 'nunjucks';
|
||||
import * as util from "util";
|
||||
import {WrappingError} from "./Utils";
|
||||
import {WrappingError} from "../Utils";
|
||||
import mjml2html from "mjml";
|
||||
import {log} from "./Logger";
|
||||
import Controller from "./Controller";
|
||||
import {log} from "../Logger";
|
||||
import Controller from "../Controller";
|
||||
import {ParsedUrlQueryInput} from "querystring";
|
||||
|
||||
export default class Mail {
|
@ -1,6 +1,6 @@
|
||||
import {Request, Response} from "express";
|
||||
import Controller from "../Controller";
|
||||
import Mail from "../Mail";
|
||||
import Mail from "./Mail";
|
||||
import NunjucksComponent from "../components/NunjucksComponent";
|
||||
|
||||
export default class MailController extends Controller {
|
7
test/views/home.njk
Normal file
7
test/views/home.njk
Normal file
@ -0,0 +1,7 @@
|
||||
{% extends 'layouts/base.njk' %}
|
||||
|
||||
{% set title = app.name + ' - Home' %}
|
||||
|
||||
{% block body %}
|
||||
<h1>{{ title }}</h1>
|
||||
{% endblock %}
|
@ -1,4 +1,5 @@
|
||||
{% extends 'layouts/barebone.njk' %}
|
||||
{% import 'macros.njk' as macros %}
|
||||
|
||||
{% block _stylesheets %}
|
||||
{{ super() }}
|
||||
@ -16,14 +17,18 @@
|
||||
<button id="menu-button"><i data-feather="menu"></i></button>
|
||||
<ul id="main-menu">
|
||||
{% if user %}
|
||||
<li class="dropdown"><a href="{{ route('widgets') }}"><i data-feather="zap"></i> <span class="tip">Widgets</span></a></li>
|
||||
<div class="separator"></div>
|
||||
<li><a href="{{ route('account') }}"><i data-feather="user"></i> <span class="tip">{{ user.name }} - Account</span></a></li>
|
||||
|
||||
{% if user.is_admin %}
|
||||
<li><a href="{{ route('early-access-backend') }}"><i data-feather="sliders"></i> <span class="tip">Backend</span></a></li>
|
||||
<li><a href="{{ route('backend') }}"><i data-feather="settings"></i> <span class="tip">Backend</span></a></li>
|
||||
{% endif %}
|
||||
<li><a href="{{ route('logout') }}"><i data-feather="log-out"></i> <span class="tip">Log out</span></a></li>
|
||||
|
||||
{# <li><a href="{{ route('account') }}"><i data-feather="user"></i>#}
|
||||
{# <span class="tip">{{ user.name }}</span></a></li>#}
|
||||
<li>
|
||||
<form action="{{ route('logout') }}" method="POST">
|
||||
<button><i data-feather="log-out"></i> <span class="tip">Logout</span></button>
|
||||
{{ macros.csrf(getCsrfToken) }}
|
||||
</form>
|
||||
</li>
|
||||
{% else %}
|
||||
<li><a href="{{ route('auth') }}"><i data-feather="log-in"></i> Log in / Register</a></li>
|
||||
{% endif %}
|
||||
|
@ -7,42 +7,68 @@
|
||||
|
||||
{% block body %}
|
||||
<div class="container">
|
||||
<div class="panel">
|
||||
{% set queryStr = '' %}
|
||||
{% if query.redirect_uri | length %}
|
||||
{% set queryStr = '?' + querystring.stringify({redirect_uri: query.redirect_uri}) %}
|
||||
{% endif %}
|
||||
{% set action = route('auth') + queryStr %}
|
||||
{% set queryStr = '' %}
|
||||
{% if query.redirect_uri | length %}
|
||||
{% set queryStr = '?' + querystring.stringify({redirect_uri: query.redirect_uri}) %}
|
||||
{% endif %}
|
||||
|
||||
{% if register_confirm_email %}
|
||||
<form action="{{ action }}" method="POST" id="register-form">
|
||||
<h2>Register</h2>
|
||||
{{ macros.message('question', 'Do you wish to create a new account with ' + register_confirm_email + '?', false, false) }}
|
||||
{{ macros.message('warning', 'If you already have an account, please log in with your existing email first and then add your new email in the Account page.', false, true) }}
|
||||
<input type="hidden" name="email" value="{{ register_confirm_email }}">
|
||||
<input type="hidden" name="confirm_register" value="confirm">
|
||||
<section class="panel">
|
||||
<h2>Log in</h2>
|
||||
|
||||
<div class="form-field">
|
||||
<div class="form-display">Email: {{ register_confirm_email }}</div>
|
||||
</div>
|
||||
<form action="{{ route('login') + queryStr }}" method="POST" id="login-form">
|
||||
{{ macros.field(_locals, 'text', 'identifier', query.identifier or '', 'Your email address or username', null, 'required') }}
|
||||
|
||||
{{ macros.field(_locals, 'password', 'password', null, 'Your password', 'Do not fill to log in via magic link.') }}
|
||||
|
||||
<button type="submit">Authenticate</button>
|
||||
|
||||
{{ macros.csrf(getCsrfToken) }}
|
||||
</form>
|
||||
</section>
|
||||
|
||||
<section class="panel">
|
||||
<h2>Register with email</h2>
|
||||
|
||||
<form action="{{ route('register') + queryStr }}" method="POST" id="register-form">
|
||||
<input type="hidden" name="auth_method" value="magic_link">
|
||||
{{ macros.csrf(getCsrfToken) }}
|
||||
|
||||
{% if has_username %}
|
||||
{{ macros.field(_locals, 'text', 'name', null, 'Choose your username', 'This cannot be changed later.', 'pattern="[0-9a-z_-]+" required') }}
|
||||
{% endif %}
|
||||
|
||||
{{ macros.field(_locals, 'email', 'identifier', null, 'Your email address', null, 'required') }}
|
||||
|
||||
{{ macros.field(_locals, 'checkbox', 'terms', null, 'I accept the <a href="/terms-of-services" target="_blank">Terms Of Services</a>.' | safe, null, 'required') }}
|
||||
|
||||
<button type="submit" class="primary">Register</button>
|
||||
</form>
|
||||
</section>
|
||||
|
||||
{% if register_with_password %}
|
||||
<section class="panel">
|
||||
<h2>Register with password</h2>
|
||||
|
||||
<form action="{{ route('register') + queryStr }}" method="POST" id="register-form">
|
||||
<input type="hidden" name="auth_method" value="password">
|
||||
{{ macros.csrf(getCsrfToken) }}
|
||||
|
||||
<section class="sub-panel">
|
||||
<h3>Username</h3>
|
||||
{{ macros.field(_locals, 'text', 'identifier', null, 'Choose your username', 'This cannot be changed later.', 'pattern="[0-9a-z_-]+" required') }}
|
||||
</section>
|
||||
|
||||
<section class="sub-panel">
|
||||
<h3>Password</h3>
|
||||
{{ macros.field(_locals, 'password', 'password', null, 'Choose a password', null, 'required') }}
|
||||
{{ macros.field(_locals, 'password', 'password_confirmation', null, 'Confirm your password', null, 'required') }}
|
||||
</section>
|
||||
|
||||
{{ macros.field(_locals, 'checkbox', 'terms', null, 'I accept the <a href="/terms-of-services" target="_blank">Terms Of Services</a>.' | safe, null, 'required') }}
|
||||
|
||||
<a href="/auth" class="button transparent">Go back</a>
|
||||
<button type="submit" class="primary">Register</button>
|
||||
|
||||
{{ macros.csrf(getCsrfToken) }}
|
||||
</form>
|
||||
{% else %}
|
||||
<form action="{{ action }}" method="POST" id="login-form">
|
||||
<h2>Log in or register</h2>
|
||||
{# {{ macros.message('info', 'If we don\'t find your email address in our database, you will be able to register.', false, true) }} #}
|
||||
<div class="input-field">
|
||||
{{ macros.field(_locals, 'email', 'email', query.email or '', 'Your email address', "If we don't find your email address in our database, you will be able to register.", 'required') }}
|
||||
</div>
|
||||
<button type="submit">Authenticate</button>
|
||||
|
||||
{{ macros.csrf(getCsrfToken) }}
|
||||
</form>
|
||||
{% endif %}
|
||||
</div>
|
||||
</section>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
@ -1,5 +1,4 @@
|
||||
{% extends 'layouts/base.njk' %}
|
||||
{% import 'macros.njk' as macros %}
|
||||
|
||||
{% set actionType = magicLink.action_type %}
|
||||
{% set title = app.name + 'Magic Link' + (' - ' + actionType if actionType) %}
|
||||
|
@ -1,5 +1,4 @@
|
||||
{% extends 'layouts/base.njk' %}
|
||||
{% import 'macros.njk' as macros %}
|
||||
|
||||
{% set title = 'Authentication lobby' %}
|
||||
{% set h1 = 'Authentication lobby' %}
|
||||
@ -55,4 +54,4 @@
|
||||
</script>
|
||||
|
||||
{{ macros.websocket(websocketUrl, 'websocketListen', 1, 'isValid') }}
|
||||
{% endblock %}
|
||||
{% endblock %}
|
||||
|
Loading…
Reference in New Issue
Block a user