mirror of https://github.com/Requarks/wiki.git
25 changed files with 668 additions and 62 deletions
Unified View
Diff Options
-
4.editorconfig
-
1Makefile
-
2client/client-app.js
-
12client/components/admin/admin-auth.vue
-
54client/components/admin/admin-contribute.vue
-
6client/components/admin/admin-rendering.vue
-
4client/components/admin/admin-users-edit.vue
-
51client/components/common/loader.vue
-
79client/components/common/password-strength.vue
-
11client/components/editor.vue
-
33client/components/login.vue
-
303client/components/register.vue
-
13client/graph/register/register-mutation-create.gql
-
BINclient/static/img/icon-browse.png
-
BINclient/static/img/icon-people.png
-
BINclient/static/img/icon-unlock.png
-
18client/static/svg/logo-icons8.svg
-
2dev/docker/docker-compose.yml
-
7package.json
-
7server/controllers/auth.js
-
20server/graph/resolvers/authentication.js
-
11server/graph/schemas/authentication.graphql
-
68server/helpers/error.js
-
19server/models/users.js
-
5server/views/register.pug
@ -0,0 +1,51 @@ |
|||||
|
<template lang='pug'> |
||||
|
v-dialog(v-model='value', persistent, max-width='350') |
||||
|
v-card.loader-dialog.radius-7(:color='color', dark) |
||||
|
v-card-text.text-xs-center.py-4 |
||||
|
atom-spinner.is-inline( |
||||
|
:animation-duration='1000' |
||||
|
:size='60' |
||||
|
color='#FFF' |
||||
|
) |
||||
|
.subheading {{ title }} |
||||
|
.caption {{ subtitle }} |
||||
|
</template> |
||||
|
|
||||
|
<script> |
||||
|
import { AtomSpinner } from 'epic-spinners' |
||||
|
|
||||
|
export default { |
||||
|
components: { |
||||
|
AtomSpinner |
||||
|
}, |
||||
|
props: { |
||||
|
value: { |
||||
|
type: Boolean, |
||||
|
default: false |
||||
|
}, |
||||
|
color: { |
||||
|
type: String, |
||||
|
default: 'blue darken-3' |
||||
|
}, |
||||
|
title: { |
||||
|
type: String, |
||||
|
default: 'Working...' |
||||
|
}, |
||||
|
subtitle: { |
||||
|
type: String, |
||||
|
default: 'Please wait' |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
</script> |
||||
|
|
||||
|
<style lang='scss'> |
||||
|
.loader-dialog { |
||||
|
.atom-spinner.is-inline { |
||||
|
display: inline-block; |
||||
|
} |
||||
|
.caption { |
||||
|
color: rgba(255,255,255,.7); |
||||
|
} |
||||
|
} |
||||
|
</style> |
@ -0,0 +1,79 @@ |
|||||
|
<template lang="pug"> |
||||
|
.password-strength |
||||
|
v-progress-linear( |
||||
|
:color='passwordStrengthColor' |
||||
|
v-model='passwordStrength' |
||||
|
height='2' |
||||
|
) |
||||
|
.caption(v-if='!hideText', :class='passwordStrengthColor + "--text"') {{passwordStrengthText}} |
||||
|
</template> |
||||
|
|
||||
|
<script> |
||||
|
import zxcvbn from 'zxcvbn' |
||||
|
import _ from 'lodash' |
||||
|
|
||||
|
export default { |
||||
|
props: { |
||||
|
value: { |
||||
|
type: String, |
||||
|
default: '' |
||||
|
}, |
||||
|
hideText: { |
||||
|
type: Boolean, |
||||
|
default: false |
||||
|
} |
||||
|
}, |
||||
|
data() { |
||||
|
return { |
||||
|
passwordStrength: 0, |
||||
|
passwordStrengthColor: 'grey', |
||||
|
passwordStrengthText: '' |
||||
|
} |
||||
|
}, |
||||
|
watch: { |
||||
|
value(newValue) { |
||||
|
this.checkPasswordStrength(newValue) |
||||
|
} |
||||
|
}, |
||||
|
methods: { |
||||
|
checkPasswordStrength: _.debounce(function (pwd) { |
||||
|
if (!pwd || pwd.length < 1) { |
||||
|
this.passwordStrength = 0 |
||||
|
this.passwordStrengthColor = 'grey' |
||||
|
this.passwordStrengthText = '' |
||||
|
return |
||||
|
} |
||||
|
const strength = zxcvbn(pwd) |
||||
|
this.passwordStrength = _.round((strength.score + 1 ) / 5 * 100) |
||||
|
if (this.passwordStrength <= 20) { |
||||
|
this.passwordStrengthColor = 'red' |
||||
|
this.passwordStrengthText = 'Very Weak' |
||||
|
} else if (this.passwordStrength <= 40) { |
||||
|
this.passwordStrengthColor = 'orange' |
||||
|
this.passwordStrengthText = 'Weak' |
||||
|
} else if (this.passwordStrength <= 60) { |
||||
|
this.passwordStrengthColor = 'teal' |
||||
|
this.passwordStrengthText = 'Average' |
||||
|
} else if (this.passwordStrength <= 80) { |
||||
|
this.passwordStrengthColor = 'green' |
||||
|
this.passwordStrengthText = 'Strong' |
||||
|
} else { |
||||
|
this.passwordStrengthColor = 'green' |
||||
|
this.passwordStrengthText = 'Very Strong' |
||||
|
} |
||||
|
}, 100) |
||||
|
} |
||||
|
} |
||||
|
</script> |
||||
|
|
||||
|
<style lang="scss"> |
||||
|
|
||||
|
.password-strength > .caption { |
||||
|
width: 100%; |
||||
|
left: 0; |
||||
|
margin: 0; |
||||
|
position: absolute; |
||||
|
top: calc(100% + 5px); |
||||
|
} |
||||
|
|
||||
|
</style> |
@ -0,0 +1,303 @@ |
|||||
|
<template lang="pug"> |
||||
|
v-app |
||||
|
.register |
||||
|
v-container(grid-list-lg) |
||||
|
v-layout(row, wrap) |
||||
|
v-flex( |
||||
|
xs12 |
||||
|
offset-sm1, sm10 |
||||
|
offset-md2, md8 |
||||
|
offset-lg3, lg6 |
||||
|
offset-xl4, xl4 |
||||
|
) |
||||
|
transition(name='zoom') |
||||
|
v-card.elevation-5.md2(v-show='isShown') |
||||
|
v-toolbar(color='indigo', flat, dense, dark) |
||||
|
v-spacer |
||||
|
.subheading {{ $t('auth:registerTitle') }} |
||||
|
v-spacer |
||||
|
v-card-text.text-xs-center |
||||
|
h1.display-1.indigo--text.py-2 {{ siteTitle }} |
||||
|
.body-2 {{ $t('auth:registerSubTitle') }} |
||||
|
v-text-field.md2.mt-3( |
||||
|
solo |
||||
|
flat |
||||
|
prepend-icon='email' |
||||
|
background-color='grey lighten-4' |
||||
|
hide-details |
||||
|
ref='iptEmail' |
||||
|
v-model='email' |
||||
|
:placeholder='$t("auth:fields.email")' |
||||
|
color='indigo' |
||||
|
) |
||||
|
v-text-field.md2.mt-2( |
||||
|
solo |
||||
|
flat |
||||
|
prepend-icon='vpn_key' |
||||
|
background-color='grey lighten-4' |
||||
|
ref='iptPassword' |
||||
|
v-model='password' |
||||
|
:append-icon='hidePassword ? "visibility" : "visibility_off"' |
||||
|
@click:append='() => (hidePassword = !hidePassword)' |
||||
|
:type='hidePassword ? "password" : "text"' |
||||
|
:placeholder='$t("auth:fields.password")' |
||||
|
color='indigo' |
||||
|
loading |
||||
|
) |
||||
|
password-strength(slot='progress', v-model='password') |
||||
|
v-text-field.md2.mt-2( |
||||
|
solo |
||||
|
flat |
||||
|
prepend-icon='vpn_key' |
||||
|
background-color='grey lighten-4' |
||||
|
hide-details |
||||
|
ref='iptVerifyPassword' |
||||
|
v-model='verifyPassword' |
||||
|
@click:append='() => (hidePassword = !hidePassword)' |
||||
|
type='password' |
||||
|
:placeholder='$t("auth:fields.verifyPassword")' |
||||
|
color='indigo' |
||||
|
) |
||||
|
v-text-field.md2.mt-2( |
||||
|
solo |
||||
|
flat |
||||
|
prepend-icon='person' |
||||
|
background-color='grey lighten-4' |
||||
|
hide-details |
||||
|
ref='iptName' |
||||
|
v-model='name' |
||||
|
:placeholder='$t("auth:fields.name")' |
||||
|
@keyup.enter='register' |
||||
|
color='indigo' |
||||
|
) |
||||
|
v-card-actions.pb-4 |
||||
|
v-spacer |
||||
|
v-btn.md2( |
||||
|
block |
||||
|
large |
||||
|
dark |
||||
|
color='indigo' |
||||
|
@click='register' |
||||
|
round |
||||
|
:loading='isLoading' |
||||
|
) {{ $t('auth:actions.register') }} |
||||
|
v-spacer |
||||
|
v-divider |
||||
|
v-card-actions.py-3.grey.lighten-4 |
||||
|
v-spacer |
||||
|
i18next.caption(path='auth:switchToLogin.text', tag='div') |
||||
|
a.caption(href='/login', place='link') {{ $t('auth:switchToLogin.link') }} |
||||
|
v-spacer |
||||
|
|
||||
|
loader(v-model='isLoading', :color='loaderColor', :title='loaderTitle', :subtitle='$t(`auth:pleaseWait`)') |
||||
|
nav-footer(color='grey darken-4', dark-color='grey darken-4') |
||||
|
</template> |
||||
|
|
||||
|
<script> |
||||
|
/* global siteConfig */ |
||||
|
|
||||
|
import _ from 'lodash' |
||||
|
import Cookies from 'js-cookie' |
||||
|
import validate from 'validate.js' |
||||
|
import PasswordStrength from './common/password-strength.vue' |
||||
|
|
||||
|
import registerMutation from 'gql/register/register-mutation-create.gql' |
||||
|
|
||||
|
export default { |
||||
|
i18nOptions: { namespaces: 'auth' }, |
||||
|
components: { |
||||
|
PasswordStrength |
||||
|
}, |
||||
|
data () { |
||||
|
return { |
||||
|
email: '', |
||||
|
password: '', |
||||
|
verifyPassword: '', |
||||
|
name: '', |
||||
|
hidePassword: true, |
||||
|
isLoading: false, |
||||
|
isShown: false |
||||
|
} |
||||
|
}, |
||||
|
computed: { |
||||
|
siteTitle () { |
||||
|
return siteConfig.title |
||||
|
} |
||||
|
}, |
||||
|
mounted () { |
||||
|
this.isShown = true |
||||
|
this.$nextTick(() => { |
||||
|
this.$refs.iptEmail.focus() |
||||
|
}) |
||||
|
}, |
||||
|
methods: { |
||||
|
/** |
||||
|
* REGISTER |
||||
|
*/ |
||||
|
async register () { |
||||
|
const validation = validate({ |
||||
|
email: this.email, |
||||
|
password: this.password, |
||||
|
verifyPassword: this.verifyPassword, |
||||
|
name: this.name |
||||
|
}, { |
||||
|
email: { |
||||
|
presence: { |
||||
|
message: this.$t('auth:missingEmail'), |
||||
|
allowEmpty: false |
||||
|
}, |
||||
|
email: { |
||||
|
message: this.$t('auth:invalidEmail') |
||||
|
} |
||||
|
}, |
||||
|
password: { |
||||
|
presence: { |
||||
|
message: this.$t('auth:missingPassword'), |
||||
|
allowEmpty: false |
||||
|
}, |
||||
|
length: { |
||||
|
minimum: 6, |
||||
|
tooShort: this.$t('auth:passwordTooShort') |
||||
|
} |
||||
|
}, |
||||
|
verifyPassword: { |
||||
|
equality: { |
||||
|
attribute: 'password', |
||||
|
message: this.$t('auth:passwordNotMatch') |
||||
|
} |
||||
|
}, |
||||
|
name: { |
||||
|
presence: { |
||||
|
message: this.$t('auth:missingName'), |
||||
|
allowEmpty: false |
||||
|
}, |
||||
|
length: { |
||||
|
minimum: 2, |
||||
|
maximum: 255, |
||||
|
tooShort: this.$t('auth:nameTooShort'), |
||||
|
tooLong: this.$t('auth:nameTooLong') |
||||
|
} |
||||
|
}, |
||||
|
}, { fullMessages: false }) |
||||
|
|
||||
|
if (validation) { |
||||
|
if(validation.email) { |
||||
|
this.$store.commit('showNotification', { |
||||
|
style: 'red', |
||||
|
message: validation.email[0], |
||||
|
icon: 'warning' |
||||
|
}) |
||||
|
this.$refs.iptEmail.focus() |
||||
|
} else if (validation.password) { |
||||
|
this.$store.commit('showNotification', { |
||||
|
style: 'red', |
||||
|
message: validation.password[0], |
||||
|
icon: 'warning' |
||||
|
}) |
||||
|
this.$refs.iptPassword.focus() |
||||
|
} else if (validation.verifyPassword) { |
||||
|
this.$store.commit('showNotification', { |
||||
|
style: 'red', |
||||
|
message: validation.verifyPassword[0], |
||||
|
icon: 'warning' |
||||
|
}) |
||||
|
this.$refs.iptVerifyPassword.focus() |
||||
|
} else { |
||||
|
this.$store.commit('showNotification', { |
||||
|
style: 'red', |
||||
|
message: validation.name[0], |
||||
|
icon: 'warning' |
||||
|
}) |
||||
|
this.$refs.iptName.focus() |
||||
|
} |
||||
|
} else { |
||||
|
this.isLoading = true |
||||
|
try { |
||||
|
let resp = await this.$apollo.mutate({ |
||||
|
mutation: registerMutation, |
||||
|
variables: { |
||||
|
email: this.email, |
||||
|
password: this.password, |
||||
|
name: this.name |
||||
|
} |
||||
|
}) |
||||
|
if (_.has(resp, 'data.authentication.register')) { |
||||
|
let respObj = _.get(resp, 'data.authentication.register', {}) |
||||
|
if (respObj.responseResult.succeeded === true) { |
||||
|
this.$store.commit('showNotification', { |
||||
|
message: 'Account created successfully! Redirecting...', |
||||
|
style: 'success', |
||||
|
icon: 'check' |
||||
|
}) |
||||
|
Cookies.set('jwt', respObj.jwt, { expires: 365 }) |
||||
|
_.delay(() => { |
||||
|
window.location.replace('/') |
||||
|
}, 1000) |
||||
|
} else { |
||||
|
throw new Error(respObj.responseResult.message) |
||||
|
} |
||||
|
} else { |
||||
|
throw new Error('Registration is unavailable at this time.') |
||||
|
} |
||||
|
} catch (err) { |
||||
|
console.error(err) |
||||
|
this.$store.commit('showNotification', { |
||||
|
style: 'red', |
||||
|
message: err.message, |
||||
|
icon: 'warning' |
||||
|
}) |
||||
|
this.isLoading = false |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
</script> |
||||
|
|
||||
|
<style lang="scss"> |
||||
|
.register { |
||||
|
background-color: mc('indigo', '900'); |
||||
|
background-image: url('../static/svg/motif-blocks.svg'); |
||||
|
background-repeat: repeat; |
||||
|
background-size: 200px; |
||||
|
width: 100%; |
||||
|
height: 100%; |
||||
|
animation: loginBgReveal 20s linear infinite; |
||||
|
|
||||
|
@include keyframes(loginBgReveal) { |
||||
|
0% { |
||||
|
background-position-x: 0; |
||||
|
} |
||||
|
100% { |
||||
|
background-position-x: 800px; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
&::before { |
||||
|
content: ''; |
||||
|
position: absolute; |
||||
|
background-image: url('../static/svg/motif-overlay.svg'); |
||||
|
background-attachment: fixed; |
||||
|
background-size: cover; |
||||
|
opacity: .5; |
||||
|
top: 0; |
||||
|
left: 0; |
||||
|
width: 100vw; |
||||
|
height: 100vh; |
||||
|
} |
||||
|
|
||||
|
> .container { |
||||
|
height: 100%; |
||||
|
align-items: center; |
||||
|
display: flex; |
||||
|
} |
||||
|
|
||||
|
h1 { |
||||
|
font-family: 'Varela Round' !important; |
||||
|
} |
||||
|
|
||||
|
.v-text-field.centered input { |
||||
|
text-align: center; |
||||
|
} |
||||
|
} |
||||
|
</style> |
@ -0,0 +1,13 @@ |
|||||
|
mutation($email: String!, $password: String!, $name: String!) { |
||||
|
authentication { |
||||
|
register(email: $email, password: $password, name: $name) { |
||||
|
responseResult { |
||||
|
succeeded |
||||
|
errorCode |
||||
|
slug |
||||
|
message |
||||
|
} |
||||
|
jwt |
||||
|
} |
||||
|
} |
||||
|
} |
@ -0,0 +1,18 @@ |
|||||
|
<?xml version="1.0" encoding="UTF-8"?> |
||||
|
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="80pt" height="80pt" viewBox="0 0 80 80" version="1.1"> |
||||
|
<g id="surface1"> |
||||
|
<path style=" stroke:none;fill-rule:nonzero;fill:#78909C;fill-opacity:1;" d="M 16.667969 73.332031 C 15.667969 73.332031 15 72.667969 15 71.667969 C 15 70.667969 15.667969 70 16.667969 70 C 17.5 70 18.332031 69.832031 18.332031 68.167969 L 18.332031 61.667969 C 18.332031 60.667969 19 60 20 60 C 21 60 21.667969 60.667969 21.667969 61.667969 L 21.667969 68.167969 C 21.667969 71.332031 19.667969 73.332031 16.667969 73.332031 Z "/> |
||||
|
<path style=" stroke:none;fill-rule:nonzero;fill:#CFD8DC;fill-opacity:1;" d="M 67.832031 25 C 68.167969 24 68.332031 22.832031 68.332031 21.667969 C 68.332031 16.167969 63.832031 11.667969 58.332031 11.667969 C 55.5 11.667969 53 12.832031 51.167969 14.832031 C 49.667969 10 45.167969 6.667969 40 6.667969 C 34.667969 6.667969 30.332031 10.167969 28.832031 15 C 27.332031 14 25.332031 13.332031 23.332031 13.332031 C 17.832031 13.332031 13.332031 17.832031 13.332031 23.332031 C 13.332031 23.832031 13.332031 24.5 13.5 25 Z "/> |
||||
|
<path style=" stroke:none;fill-rule:nonzero;fill:#32C24D;fill-opacity:1;" d="M 10 21.667969 L 70 21.667969 L 70 58.332031 L 10 58.332031 Z "/> |
||||
|
<path style=" stroke:none;fill-rule:nonzero;fill:#FFFFFF;fill-opacity:1;" d="M 43.332031 40 C 43.332031 41.832031 41.832031 43.332031 40 43.332031 C 38.167969 43.332031 36.667969 41.832031 36.667969 40 C 36.667969 38.167969 38.167969 36.667969 40 36.667969 C 41.832031 36.667969 43.332031 38.167969 43.332031 40 Z "/> |
||||
|
<path style=" stroke:none;fill-rule:nonzero;fill:#FFFFFF;fill-opacity:1;" d="M 56.667969 40 C 56.667969 41.832031 55.167969 43.332031 53.332031 43.332031 C 51.5 43.332031 50 41.832031 50 40 C 50 38.167969 51.5 36.667969 53.332031 36.667969 C 55.167969 36.667969 56.667969 38.167969 56.667969 40 Z "/> |
||||
|
<path style=" stroke:none;fill-rule:nonzero;fill:#FFFFFF;fill-opacity:1;" d="M 30 40 C 30 41.832031 28.5 43.332031 26.667969 43.332031 C 24.832031 43.332031 23.332031 41.832031 23.332031 40 C 23.332031 38.167969 24.832031 36.667969 26.667969 36.667969 C 28.5 36.667969 30 38.167969 30 40 Z "/> |
||||
|
<path style=" stroke:none;fill-rule:nonzero;fill:#546E7A;fill-opacity:1;" d="M 18.332031 58.332031 L 21.667969 58.332031 L 21.667969 61.667969 L 18.332031 61.667969 Z "/> |
||||
|
<path style=" stroke:none;fill-rule:nonzero;fill:#78909C;fill-opacity:1;" d="M 26.667969 73.332031 C 25.667969 73.332031 25 72.667969 25 71.667969 C 25 70.667969 25.667969 70 26.667969 70 C 27.5 70 28.332031 69.832031 28.332031 68.167969 L 28.332031 61.667969 C 28.332031 60.667969 29 60 30 60 C 31 60 31.667969 60.667969 31.667969 61.667969 L 31.667969 68.167969 C 31.667969 71.332031 29.667969 73.332031 26.667969 73.332031 Z "/> |
||||
|
<path style=" stroke:none;fill-rule:nonzero;fill:#546E7A;fill-opacity:1;" d="M 28.332031 58.332031 L 31.667969 58.332031 L 31.667969 61.667969 L 28.332031 61.667969 Z "/> |
||||
|
<path style=" stroke:none;fill-rule:nonzero;fill:#78909C;fill-opacity:1;" d="M 48.332031 73.332031 C 47.332031 73.332031 46.667969 72.667969 46.667969 71.667969 C 46.667969 70.667969 47.332031 70 48.332031 70 C 49.167969 70 50 69.832031 50 68.167969 L 50 61.667969 C 50 60.667969 50.667969 60 51.667969 60 C 52.667969 60 53.332031 60.667969 53.332031 61.667969 L 53.332031 68.167969 C 53.332031 71.332031 51.332031 73.332031 48.332031 73.332031 Z "/> |
||||
|
<path style=" stroke:none;fill-rule:nonzero;fill:#546E7A;fill-opacity:1;" d="M 50 58.332031 L 53.332031 58.332031 L 53.332031 61.667969 L 50 61.667969 Z "/> |
||||
|
<path style=" stroke:none;fill-rule:nonzero;fill:#78909C;fill-opacity:1;" d="M 58.332031 73.332031 C 57.332031 73.332031 56.667969 72.667969 56.667969 71.667969 C 56.667969 70.667969 57.332031 70 58.332031 70 C 59.167969 70 60 69.832031 60 68.167969 L 60 61.667969 C 60 60.667969 60.667969 60 61.667969 60 C 62.667969 60 63.332031 60.667969 63.332031 61.667969 L 63.332031 68.167969 C 63.332031 71.332031 61.332031 73.332031 58.332031 73.332031 Z "/> |
||||
|
<path style=" stroke:none;fill-rule:nonzero;fill:#546E7A;fill-opacity:1;" d="M 60 58.332031 L 63.332031 58.332031 L 63.332031 61.667969 L 60 61.667969 Z "/> |
||||
|
</g> |
||||
|
</svg> |
@ -1,30 +1,44 @@ |
|||||
class BaseError extends Error { |
|
||||
constructor (message) { |
|
||||
super(message) |
|
||||
this.name = this.constructor.name |
|
||||
Error.captureStackTrace(this, this.constructor) |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
class AuthGenericError extends BaseError { constructor (message = 'An unexpected error occured during login.') { super(message) } } |
|
||||
class AuthLoginFailed extends BaseError { constructor (message = 'Invalid email / username or password.') { super(message) } } |
|
||||
class AuthProviderInvalid extends BaseError { constructor (message = 'Invalid authentication provider.') { super(message) } } |
|
||||
class AuthTFAFailed extends BaseError { constructor (message = 'Incorrect TFA Security Code.') { super(message) } } |
|
||||
class AuthTFAInvalid extends BaseError { constructor (message = 'Invalid TFA Security Code or Login Token.') { super(message) } } |
|
||||
class BruteInstanceIsInvalid extends BaseError { constructor (message = 'Invalid Brute Force Instance.') { super(message) } } |
|
||||
class BruteTooManyAttempts extends BaseError { constructor (message = 'Too many attempts! Try again later.') { super(message) } } |
|
||||
class LocaleInvalidNamespace extends BaseError { constructor (message = 'Invalid locale or namespace.') { super(message) } } |
|
||||
class UserCreationFailed extends BaseError { constructor (message = 'An unexpected error occured during user creation.') { super(message) } } |
|
||||
|
const CustomError = require('custom-error-instance') |
||||
|
|
||||
module.exports = { |
module.exports = { |
||||
BaseError, |
|
||||
AuthGenericError, |
|
||||
AuthLoginFailed, |
|
||||
AuthProviderInvalid, |
|
||||
AuthTFAFailed, |
|
||||
AuthTFAInvalid, |
|
||||
BruteInstanceIsInvalid, |
|
||||
BruteTooManyAttempts, |
|
||||
LocaleInvalidNamespace, |
|
||||
UserCreationFailed |
|
||||
|
AuthGenericError: CustomError('AuthGenericError', { |
||||
|
message: 'An unexpected error occured during login.', |
||||
|
code: 1001 |
||||
|
}), |
||||
|
AuthLoginFailed: CustomError('AuthLoginFailed', { |
||||
|
message: 'Invalid email / username or password.', |
||||
|
code: 1002 |
||||
|
}), |
||||
|
AuthProviderInvalid: CustomError('AuthProviderInvalid', { |
||||
|
message: 'Invalid authentication provider.', |
||||
|
code: 1003 |
||||
|
}), |
||||
|
AuthAccountAlreadyExists: CustomError('AuthAccountAlreadyExists', { |
||||
|
message: 'An account already exists using this email address.', |
||||
|
code: 1004 |
||||
|
}), |
||||
|
AuthTFAFailed: CustomError('AuthTFAFailed', { |
||||
|
message: 'Incorrect TFA Security Code.', |
||||
|
code: 1005 |
||||
|
}), |
||||
|
AuthTFAInvalid: CustomError('AuthTFAInvalid', { |
||||
|
message: 'Invalid TFA Security Code or Login Token.', |
||||
|
code: 1006 |
||||
|
}), |
||||
|
BruteInstanceIsInvalid: CustomError('BruteInstanceIsInvalid', { |
||||
|
message: 'Invalid Brute Force Instance.', |
||||
|
code: 1007 |
||||
|
}), |
||||
|
BruteTooManyAttempts: CustomError('BruteTooManyAttempts', { |
||||
|
message: 'Too many attempts! Try again later.', |
||||
|
code: 1008 |
||||
|
}), |
||||
|
LocaleInvalidNamespace: CustomError('LocaleInvalidNamespace', { |
||||
|
message: 'Invalid locale or namespace.', |
||||
|
code: 1009 |
||||
|
}), |
||||
|
UserCreationFailed: CustomError('UserCreationFailed', { |
||||
|
message: 'An unexpected error occured during user creation.', |
||||
|
code: 1010 |
||||
|
}) |
||||
} |
} |
@ -0,0 +1,5 @@ |
|||||
|
extends master.pug |
||||
|
|
||||
|
block body |
||||
|
#root.is-fullscreen |
||||
|
register |
Write
Preview
Loading…
Cancel
Save