feat: login page + auth panel + various improvements

parent 82ebac2d
......@@ -430,8 +430,7 @@ router.get('/*', async (req, res, next) => {
const page = await WIKI.models.pages.getPage({
path: pageArgs.path,
locale: pageArgs.locale,
userId: req.user.id,
isPrivate: false
userId: req.user.id
})
pageArgs.tags = _.get(page, 'tags', [])
......@@ -440,12 +439,12 @@ router.get('/*', async (req, res, next) => {
// -> Check User Access
if (!effectivePermissions.pages.read) {
if (req.user.id === 2) {
if (req.user.id === WIKI.auth.guest.id) {
res.cookie('loginRedirect', req.path, {
maxAge: 15 * 60 * 1000
})
}
if (pageArgs.path === 'home' && req.user.id === 2) {
if (pageArgs.path === 'home' && req.user.id === WIKI.auth.guest.id) {
return res.redirect('/login')
}
return res.redirect(`/_error/unauthorized?from=${req.path}`)
......
......@@ -75,9 +75,8 @@ module.exports = {
}))
// Load enabled strategies
const enabledStrategies = await WIKI.models.authentication.getStrategies()
for (const idx in enabledStrategies) {
const stg = enabledStrategies[idx]
const enabledStrategies = await WIKI.models.authentication.getStrategies({ enabledOnly: true })
for (const stg of enabledStrategies) {
try {
const strategy = require(`../modules/authentication/${stg.module}/authentication.js`)
......@@ -146,7 +145,7 @@ module.exports = {
try {
const newToken = await WIKI.models.users.refreshToken(jwtPayload.id)
user = newToken.user
user.permissions = user.getGlobalPermissions()
user.permissions = user.getPermissions()
user.groups = user.getGroups()
req.user = user
......@@ -186,7 +185,7 @@ module.exports = {
localeCode: 'en',
permissions: _.get(WIKI.auth.groups, `${user.grp}.permissions`, []),
groups: [user.grp],
getGlobalPermissions () {
getPermissions () {
return req.user.permissions
},
getGroups () {
......@@ -215,7 +214,7 @@ module.exports = {
* @param {String|Boolean} path
*/
checkAccess(user, permissions = [], page = false) {
const userPermissions = user.permissions ? user.permissions : user.getGlobalPermissions()
const userPermissions = user.permissions ? user.permissions : user.getPermissions()
// System Admin
if (_.includes(userPermissions, 'manage:system')) {
......@@ -298,7 +297,7 @@ module.exports = {
* @param {Array<String>} excludePermissions
*/
checkExclusiveAccess(user, includePermissions = [], excludePermissions = []) {
const userPermissions = user.permissions ? user.permissions : user.getGlobalPermissions()
const userPermissions = user.permissions ? user.permissions : user.getPermissions()
// Check Inclusion Permissions
if (_.intersection(userPermissions, includePermissions).length < 1) {
......
......@@ -17,6 +17,13 @@ exports.up = async knex => {
// =====================================
// MODEL TABLES
// =====================================
// ACTIVITY LOGS -----------------------
.createTable('activityLogs', table => {
table.uuid('id').notNullable().primary().defaultTo(knex.raw('gen_random_uuid()'))
table.timestamp('ts').notNullable().defaultTo(knex.fn.now())
table.string('action').notNullable()
table.jsonb('meta').notNullable()
})
// ANALYTICS ---------------------------
.createTable('analytics', table => {
table.uuid('id').notNullable().primary().defaultTo(knex.raw('gen_random_uuid()'))
......@@ -236,6 +243,7 @@ exports.up = async knex => {
table.uuid('id').notNullable().primary().defaultTo(knex.raw('gen_random_uuid()'))
table.string('kind').notNullable()
table.string('token').notNullable()
table.jsonb('meta').notNullable().defaultTo('{}')
table.timestamp('validUntil').notNullable()
table.timestamp('createdAt').notNullable().defaultTo(knex.fn.now())
})
......@@ -244,10 +252,9 @@ exports.up = async knex => {
table.uuid('id').notNullable().primary().defaultTo(knex.raw('gen_random_uuid()'))
table.string('email').notNullable()
table.string('name').notNullable()
table.jsonb('auth')
table.jsonb('tfa')
table.jsonb('meta')
table.jsonb('prefs')
table.jsonb('auth').notNullable().defaultTo('{}')
table.jsonb('meta').notNullable().defaultTo('{}')
table.jsonb('prefs').notNullable().defaultTo('{}')
table.string('pictureUrl')
table.boolean('isSystem').notNullable().defaultTo(false)
table.boolean('isActive').notNullable().defaultTo(false)
......@@ -274,6 +281,9 @@ exports.up = async knex => {
// =====================================
// REFERENCES
// =====================================
.table('activityLogs', table => {
table.uuid('userId').notNullable().references('id').inTable('users')
})
.table('analytics', table => {
table.uuid('siteId').notNullable().references('id').inTable('sites')
})
......@@ -471,6 +481,7 @@ exports.up = async knex => {
index: true,
follow: true
},
authStrategies: [{ id: authModuleId, order: 0, isVisible: true }],
locale: 'en',
localeNamespacing: false,
localeNamespaces: [],
......
const _ = require('lodash')
const fs = require('fs-extra')
const path = require('path')
const graphHelper = require('../../helpers/graph')
/* global WIKI */
......@@ -28,6 +26,9 @@ module.exports = {
apiState () {
return WIKI.config.api.isEnabled
},
/**
* Fetch authentication strategies
*/
async authStrategies () {
return WIKI.data.authentication.map(stg => ({
...stg,
......@@ -38,33 +39,23 @@ module.exports = {
* Fetch active authentication strategies
*/
async authActiveStrategies (obj, args, context) {
return WIKI.models.authentication.getStrategies()
return WIKI.models.authentication.getStrategies({ enabledOnly: args.enabledOnly })
},
/**
* Fetch site authentication strategies
*/
async authSiteStrategies (obj, args, context, info) {
let strategies = await WIKI.models.authentication.getStrategies()
strategies = strategies.map(stg => {
const strategyInfo = _.find(WIKI.data.authentication, ['key', stg.strategyKey]) || {}
const site = await WIKI.models.sites.query().findById(args.siteId)
const activeStrategies = await WIKI.models.authentication.getStrategies({ enabledOnly: true })
return activeStrategies.map(str => {
const siteAuth = _.find(site.config.authStrategies, ['id', str.id]) || {}
return {
...stg,
strategy: strategyInfo,
config: _.sortBy(_.transform(stg.config, (res, value, key) => {
const configData = _.get(strategyInfo.props, key, false)
if (configData) {
res.push({
key,
value: JSON.stringify({
...configData,
value
})
})
}
}, []), 'key')
id: str.id,
activeStrategy: str,
order: siteAuth.order ?? 0,
isVisible: siteAuth.isVisible ?? false
}
})
return args.enabledOnly ? _.filter(strategies, 'isEnabled') : strategies
}
},
Mutation: {
......@@ -93,13 +84,14 @@ module.exports = {
const authResult = await WIKI.models.users.login(args, context)
return {
...authResult,
responseResult: graphHelper.generateSuccess('Login success')
operation: graphHelper.generateSuccess('Login success')
}
} catch (err) {
// LDAP Debug Flag
if (args.strategy === 'ldap' && WIKI.config.flags.ldapdebug) {
WIKI.logger.warn('LDAP LOGIN ERROR (c1): ', err)
}
console.error(err)
return graphHelper.generateError(err)
}
......@@ -119,9 +111,9 @@ module.exports = {
}
},
/**
* Perform Mandatory Password Change after Login
* Perform Password Change
*/
async loginChangePassword (obj, args, context) {
async changePassword (obj, args, context) {
try {
const authResult = await WIKI.models.users.loginChangePassword(args, context)
return {
......@@ -133,7 +125,7 @@ module.exports = {
}
},
/**
* Perform Mandatory Password Change after Login
* Perform Forget Password
*/
async forgotPassword (obj, args, context) {
try {
......
......@@ -9,7 +9,9 @@ extend type Query {
authStrategies: [AuthenticationStrategy]
authActiveStrategies: [AuthenticationActiveStrategy]
authActiveStrategies(
enabledOnly: Boolean
): [AuthenticationActiveStrategy]
authSiteStrategies(
siteId: UUID!
......@@ -27,7 +29,8 @@ extend type Mutation {
login(
username: String!
password: String!
strategy: String!
strategyId: UUID!
siteId: UUID
): AuthenticationLoginResponse @rateLimit(limit: 5, duration: 60)
loginTFA(
......@@ -36,9 +39,13 @@ extend type Mutation {
setup: Boolean
): AuthenticationLoginResponse @rateLimit(limit: 5, duration: 60)
loginChangePassword(
continuationToken: String!
changePassword(
userId: UUID
continuationToken: String
currentPassword: String
newPassword: String!
strategyId: UUID!
siteId: UUID
): AuthenticationLoginResponse @rateLimit(limit: 5, duration: 60)
forgotPassword(
......
......@@ -73,11 +73,6 @@ extend type Mutation {
dateFormat: String!
appearance: String!
): UserTokenResponse
changePassword(
current: String!
new: String!
): UserTokenResponse
}
# -----------------------------------------------
......
......@@ -16,6 +16,6 @@ module.exports = {
slug: err.name,
message: err.message || 'An unexpected error occured.'
}
return (complete) ? { responseResult: error } : error
return (complete) ? { operation: error } : error
}
}
......@@ -21,6 +21,7 @@ module.exports = class Authentication extends Model {
properties: {
id: { type: 'string' },
module: { type: 'string' },
isEnabled: { type: 'boolean' },
selfRegistration: {type: 'boolean'}
}
}
......@@ -34,8 +35,8 @@ module.exports = class Authentication extends Model {
return WIKI.models.authentication.query().findOne({ key })
}
static async getStrategies() {
const strategies = await WIKI.models.authentication.query()
static async getStrategies({ enabledOnly = false } = {}) {
const strategies = await WIKI.models.authentication.query().where(enabledOnly ? { isEnabled: true } : {})
return strategies.map(str => ({
...str,
domainWhitelist: _.get(str.domainWhitelist, 'v', []),
......
......@@ -42,7 +42,6 @@ module.exports = class Page extends Model {
title: {type: 'string'},
description: {type: 'string'},
publishState: {type: 'string'},
privateNS: {type: 'string'},
publishStartDate: {type: 'string'},
publishEndDate: {type: 'string'},
content: {type: 'string'},
......@@ -773,7 +772,7 @@ module.exports = class Page extends Model {
* @returns {Promise} Promise with no value
*/
static async deletePage(opts) {
const page = await WIKI.models.pages.getPageFromDb(_.has(opts, 'id') ? opts.id : opts);
const page = await WIKI.models.pages.getPageFromDb(_.has(opts, 'id') ? opts.id : opts)
if (!page) {
throw new WIKI.Error.PageNotFound()
}
......@@ -1011,14 +1010,6 @@ module.exports = class Page extends Model {
// 'pages.authorId': opts.userId
// })
// })
// .andWhere(builder => {
// if (queryModeID) return
// if (opts.isPrivate) {
// builder.where({ 'pages.isPrivate': true, 'pages.privateNS': opts.privateNS })
// } else {
// builder.where({ 'pages.isPrivate': false })
// }
// })
.first()
} catch (err) {
WIKI.logger.warn(err)
......@@ -1074,8 +1065,7 @@ module.exports = class Page extends Model {
return {
...page,
path: opts.path,
localeCode: opts.locale,
isPrivate: opts.isPrivate
localeCode: opts.locale
}
} catch (err) {
if (err.code === 'ENOENT') {
......
......@@ -16,7 +16,7 @@ module.exports = class UserKey extends Model {
required: ['kind', 'token', 'validUntil'],
properties: {
id: {type: 'integer'},
id: {type: 'string'},
kind: {type: 'string'},
token: {type: 'string'},
createdAt: {type: 'string'},
......@@ -44,11 +44,12 @@ module.exports = class UserKey extends Model {
this.createdAt = DateTime.utc().toISO()
}
static async generateToken ({ userId, kind }, context) {
static async generateToken ({ userId, kind, meta }, context) {
const token = await nanoid()
await WIKI.models.userKeys.query().insert({
kind,
token,
meta,
validUntil: DateTime.utc().plus({ days: 1 }).toISO(),
userId
})
......@@ -64,7 +65,10 @@ module.exports = class UserKey extends Model {
if (DateTime.utc() > DateTime.fromISO(res.validUntil)) {
throw new WIKI.Error.AuthValidationTokenInvalid()
}
return res.user
return {
...res.meta,
user: res.user
}
} else {
throw new WIKI.Error.AuthValidationTokenInvalid()
}
......
/* global WIKI */
const bcrypt = require('bcryptjs-then')
const _ = require('lodash')
const tfa = require('node-2fa')
const jwt = require('jsonwebtoken')
......@@ -22,15 +21,9 @@ module.exports = class User extends Model {
required: ['email'],
properties: {
id: {type: 'integer'},
id: {type: 'string'},
email: {type: 'string', format: 'email'},
name: {type: 'string', minLength: 1, maxLength: 255},
providerId: {type: 'string'},
password: {type: 'string'},
tfaIsActive: {type: 'boolean', default: false},
tfaSecret: {type: ['string', null]},
jobTitle: {type: 'string'},
location: {type: 'string'},
pictureUrl: {type: 'string'},
isSystem: {type: 'boolean'},
isActive: {type: 'boolean'},
......@@ -41,6 +34,10 @@ module.exports = class User extends Model {
}
}
static get jsonAttributes() {
return ['auth', 'meta', 'prefs']
}
static get relationMappings() {
return {
groups: {
......@@ -55,22 +52,6 @@ module.exports = class User extends Model {
to: 'groups.id'
}
},
provider: {
relation: Model.BelongsToOneRelation,
modelClass: require('./authentication'),
join: {
from: 'users.providerKey',
to: 'authentication.key'
}
},
defaultEditor: {
relation: Model.BelongsToOneRelation,
modelClass: require('./editors'),
join: {
from: 'users.editorKey',
to: 'editors.key'
}
},
locale: {
relation: Model.BelongsToOneRelation,
modelClass: require('./locales'),
......@@ -104,21 +85,6 @@ module.exports = class User extends Model {
// Instance Methods
// ------------------------------------------------
async generateHash() {
if (this.password) {
if (bcryptRegexp.test(this.password)) { return }
this.password = await bcrypt.hash(this.password, 12)
}
}
async verifyPassword(pwd) {
if (await bcrypt.compare(pwd, this.password) === true) {
return true
} else {
throw new WIKI.Error.AuthLoginFailed()
}
}
async generateTFA() {
let tfaInfo = tfa.generateSecret({
name: WIKI.config.title,
......@@ -150,7 +116,7 @@ module.exports = class User extends Model {
return (result && _.has(result, 'delta') && result.delta === 0)
}
getGlobalPermissions() {
getPermissions() {
return _.uniq(_.flatten(_.map(this.groups, 'permissions')))
}
......@@ -297,7 +263,7 @@ module.exports = class User extends Model {
throw new WIKI.Error.AuthProviderInvalid()
}
const strInfo = _.find(WIKI.data.authentication, ['key', selStrategy.strategyKey])
const strInfo = _.find(WIKI.data.authentication, ['key', selStrategy.module])
// Inject form user/pass
if (strInfo.useForm) {
......@@ -308,7 +274,7 @@ module.exports = class User extends Model {
// Authenticate
return new Promise((resolve, reject) => {
WIKI.auth.passport.authenticate(selStrategy.key, {
WIKI.auth.passport.authenticate(selStrategy.id, {
session: !strInfo.useForm,
scope: strInfo.scopes ? strInfo.scopes : null
}, async (err, user, info) => {
......@@ -316,7 +282,7 @@ module.exports = class User extends Model {
if (!user) { return reject(new WIKI.Error.AuthLoginFailed()) }
try {
const resp = await WIKI.models.users.afterLoginChecks(user, context, {
const resp = await WIKI.models.users.afterLoginChecks(user, selStrategy.id, context, {
skipTFA: !strInfo.useForm,
skipChangePwd: !strInfo.useForm
})
......@@ -334,7 +300,7 @@ module.exports = class User extends Model {
/**
* Perform post-login checks
*/
static async afterLoginChecks (user, context, { skipTFA, skipChangePwd } = { skipTFA: false, skipChangePwd: false }) {
static async afterLoginChecks (user, strategyId, context, { skipTFA, skipChangePwd } = { skipTFA: false, skipChangePwd: false }) {
// Get redirect target
user.groups = await user.$relatedQuery('groups').select('groups.id', 'permissions', 'redirectOnLogin')
let redirect = '/'
......@@ -347,9 +313,12 @@ module.exports = class User extends Model {
}
}
// Get auth strategy flags
const authStr = user.auth[strategyId] || {}
// Is 2FA required?
if (!skipTFA) {
if (user.tfaIsActive && user.tfaSecret) {
if (authStr.tfaRequired && authStr.tfaSecret) {
try {
const tfaToken = await WIKI.models.userKeys.generateToken({
kind: 'tfa',
......@@ -364,7 +333,7 @@ module.exports = class User extends Model {
WIKI.logger.warn(errc)
throw new WIKI.Error.AuthGenericError()
}
} else if (WIKI.config.auth.enforce2FA || (user.tfaIsActive && !user.tfaSecret)) {
} else if (WIKI.config.auth.enforce2FA || (authStr.tfaIsActive && !authStr.tfaSecret)) {
try {
const tfaQRImage = await user.generateTFA()
const tfaToken = await WIKI.models.userKeys.generateToken({
......@@ -385,7 +354,7 @@ module.exports = class User extends Model {
}
// Must Change Password?
if (!skipChangePwd && user.mustChangePwd) {
if (!skipChangePwd && authStr.mustChangePwd) {
try {
const pwdChangeToken = await WIKI.models.userKeys.generateToken({
kind: 'changePwd',
......@@ -440,18 +409,10 @@ module.exports = class User extends Model {
token: jwt.sign({
id: user.id,
email: user.email,
name: user.name,
av: user.pictureUrl,
tz: user.timezone,
lc: user.localeCode,
df: user.dateFormat,
ap: user.appearance,
// defaultEditor: user.defaultEditor,
permissions: user.getGlobalPermissions(),
groups: user.getGroups()
}, {
key: WIKI.config.certs.private,
passphrase: WIKI.config.sessionSecret
key: WIKI.config.auth.certs.private,
passphrase: WIKI.config.auth.secret
}, {
algorithm: 'RS256',
expiresIn: WIKI.config.auth.tokenExpiration,
......@@ -877,7 +838,7 @@ module.exports = class User extends Model {
WIKI.logger.error('CRITICAL ERROR: Guest user is missing!')
process.exit(1)
}
user.permissions = user.getGlobalPermissions()
user.permissions = user.getPermissions()
return user
}
......
/* global WIKI */
const bcrypt = require('bcryptjs-then')
// ------------------------------------
// Local Account
......@@ -8,27 +9,30 @@ const LocalStrategy = require('passport-local').Strategy
module.exports = {
init (passport, conf) {
passport.use('local',
passport.use(conf.key,
new LocalStrategy({
usernameField: 'email',
passwordField: 'password'
}, async (uEmail, uPassword, done) => {
try {
const user = await WIKI.models.users.query().findOne({
email: uEmail.toLowerCase(),
providerKey: 'local'
email: uEmail.toLowerCase()
})
if (user) {
await user.verifyPassword(uPassword)
if (!user.isActive) {
done(new WIKI.Error.AuthAccountBanned(), null)
const authStrategyData = user.auth[conf.key]
if (!authStrategyData) {
throw new WIKI.Error.AuthLoginFailed()
} else if (await bcrypt.compare(uPassword, authStrategyData.password) !== true) {
throw new WIKI.Error.AuthLoginFailed()
} else if (!user.isActive) {
throw new WIKI.Error.AuthAccountBanned()
} else if (!user.isVerified) {
done(new WIKI.Error.AuthAccountNotVerified(), null)
throw new WIKI.Error.AuthAccountNotVerified()
} else {
done(null, user)
}
} else {
done(new WIKI.Error.AuthLoginFailed(), null)
throw new WIKI.Error.AuthLoginFailed()
}
} catch (err) {
done(err, null)
......
......@@ -6,7 +6,7 @@ html(lang=siteConfig.lang)
meta(name='viewport', content='user-scalable=yes, width=device-width, initial-scale=1, maximum-scale=5')
meta(name='theme-color', content='#1976d2')
meta(name='msapplication-TileColor', content='#1976d2')
meta(name='msapplication-TileImage', content='/_assets/favicons/mstile-150x150.png')
meta(name='msapplication-TileImage', content='/_assets-legacy/favicons/mstile-150x150.png')
title= pageMeta.title + ' | ' + config.title
......@@ -20,12 +20,12 @@ html(lang=siteConfig.lang)
meta(property='og:site_name', content=config.title)
//- Favicon
link(rel='apple-touch-icon', sizes='180x180', href='/_assets/favicons/apple-touch-icon.png')
link(rel='icon', type='image/png', sizes='192x192', href='/_assets/favicons/android-chrome-192x192.png')
link(rel='icon', type='image/png', sizes='32x32', href='/_assets/favicons/favicon-32x32.png')
link(rel='icon', type='image/png', sizes='16x16', href='/_assets/favicons/favicon-16x16.png')
link(rel='mask-icon', href='/_assets/favicons/safari-pinned-tab.svg', color='#1976d2')
link(rel='manifest', href='/_assets/manifest.json')
link(rel='apple-touch-icon', sizes='180x180', href='/_assets-legacy/favicons/apple-touch-icon.png')
link(rel='icon', type='image/png', sizes='192x192', href='/_assets-legacy/favicons/android-chrome-192x192.png')
link(rel='icon', type='image/png', sizes='32x32', href='/_assets-legacy/favicons/favicon-32x32.png')
link(rel='icon', type='image/png', sizes='16x16', href='/_assets-legacy/favicons/favicon-16x16.png')
link(rel='mask-icon', href='/_assets-legacy/favicons/safari-pinned-tab.svg', color='#1976d2')
link(rel='manifest', href='/_assets-legacy/manifest.json')
//- Site Properties
script.
......
extends base.pug
block body
#root.is-fullscreen
.app-error
a(href='/')
img(src='/_assets/svg/logo-wikijs.svg')
strong Oops, something went wrong...
span= message
if error.stack
pre: code #{error.stack}
doctype html
html
head
meta(charset="UTF-8")
link(rel="icon" href="/favicon.ico")
meta(name="viewport" content="user-scalable=no, initial-scale=1, maximum-scale=1, minimum-scale=1, width=device-width")
title Wiki.js
link(href="/_assets/fonts/roboto/roboto.css" rel="stylesheet")
style(lang='text/scss').
body {
margin: 0;
font-family: "Roboto", "-apple-system", "Helvetica Neue", Helvetica, Arial, sans-serif;
font-size: 14px;
}
.errorpage {
background:#070a0d radial-gradient(ellipse,#161b22,#070a0d);
color:#fff;
height:100vh;
}
.errorpage-bg {
position:absolute;
top:50%;
left:50%;
width:320px;
height:320px;
background:linear-gradient(0,transparent 50%,#c62828 50%);
border-radius:50%;
filter:blur(80px);
transform:translate(-50%,-50%);
visibility:hidden;
}
.errorpage-content {
position:absolute;
top:50%;
left:50%;
transform:translate(-50%,-50%);
display:flex;
flex-direction:column;
justify-content:center;
align-items:center;
}
.errorpage-code {
font-size:12rem;
line-height:12rem;
font-weight:700;
background:linear-gradient(45deg,#c62828,#ef9a9a);
-webkit-background-clip:text;
background-clip:text;
-webkit-text-fill-color:transparent;
-webkit-user-select:none;
user-select:none;
}
.errorpage-title {
font-size:80px;
font-weight:500;
line-height:80px;
}
.errorpage-hint {
font-size:1.2rem;
font-weight:500;
color:#ef9a9a;
line-height:1.2rem;
margin-top:1rem;
}
.errorpage-pre {
margin-top: 28px;
color: rgba(255,255,255,.5);
}
body
.errorpage
.errorpage-bg
.errorpage-content
.errorpage-code 500
.errorpage-title Server Error
.errorpage-hint= message
if error.stack
pre.errorpage-pre: code #{error.stack}
......@@ -32,7 +32,7 @@
"@codemirror/tooltip": "0.19.16",
"@codemirror/view": "6.0.2",
"@lezer/common": "1.0.0",
"@quasar/extras": "1.15.0",
"@quasar/extras": "1.15.1",
"@tiptap/core": "2.0.0-beta.176",
"@tiptap/extension-code-block": "2.0.0-beta.37",
"@tiptap/extension-code-block-lowlight": "2.0.0-beta.68",
......@@ -63,31 +63,32 @@
"codemirror": "6.0.1",
"filesize": "9.0.11",
"filesize-parser": "1.5.0",
"graphql": "16.5.0",
"graphql": "16.6.0",
"graphql-tag": "2.12.6",
"js-cookie": "3.0.1",
"jwt-decode": "3.1.2",
"lodash-es": "4.17.21",
"luxon": "3.0.1",
"pinia": "2.0.17",
"pinia": "2.0.20",
"pug": "3.0.2",
"quasar": "2.7.5",
"quasar": "2.7.7",
"tippy.js": "6.3.7",
"uuid": "8.3.2",
"v-network-graph": "0.6.5",
"v-network-graph": "0.6.6",
"vue": "3.2.37",
"vue-codemirror": "6.0.2",
"vue-i18n": "9.1.10",
"vue-i18n": "9.2.2",
"vue-router": "4.1.3",
"vue3-otp-input": "0.3.6",
"vuedraggable": "4.1.0",
"zxcvbn": "4.4.2"
},
"devDependencies": {
"@intlify/vite-plugin-vue-i18n": "5.0.1",
"@quasar/app-vite": "1.0.5",
"@types/lodash": "4.14.182",
"@intlify/vite-plugin-vue-i18n": "6.0.1",
"@quasar/app-vite": "1.0.6",
"@types/lodash": "4.14.184",
"browserlist": "latest",
"eslint": "8.20.0",
"eslint": "8.22.0",
"eslint-config-standard": "17.0.0",
"eslint-plugin-import": "2.26.0",
"eslint-plugin-n": "15.2.4",
......
......@@ -112,7 +112,8 @@ module.exports = configure(function (/* ctx */) {
delay: 500,
spinner: 'QSpinnerGrid',
spinnerSize: 32,
spinnerColor: 'white'
spinnerColor: 'white',
customClass: 'loading-darker'
},
loadingBar: {
color: 'primary',
......
......@@ -117,7 +117,7 @@ body::-webkit-scrollbar-thumb {
}
&:hover .q-focus-helper {
opacity: .3;
opacity: .3 !important;
}
}
......@@ -210,6 +210,12 @@ body::-webkit-scrollbar-thumb {
}
}
.loading-darker {
.q-loading__backdrop {
opacity: .75;
}
}
// ------------------------------------------------------------------
// IMPORTS
// ------------------------------------------------------------------
......
......@@ -919,8 +919,9 @@
"auth.actions.register": "Register",
"auth.changePwd.instructions": "You must choose a new password:",
"auth.changePwd.loading": "Changing password...",
"auth.changePwd.newPasswordPlaceholder": "New Password",
"auth.changePwd.newPasswordVerifyPlaceholder": "Verify New Password",
"auth.changePwd.currentPassword": "Current Password",
"auth.changePwd.newPassword": "New Password",
"auth.changePwd.newPasswordVerify": "Verify New Password",
"auth.changePwd.proceed": "Change Password",
"auth.changePwd.subtitle": "Choose a new password",
"auth.enterCredentials": "Enter your credentials",
......@@ -932,6 +933,20 @@
"auth.errors.tooManyAttempts": "Too many attempts!",
"auth.errors.tooManyAttemptsMsg": "You've made too many failed attempts in a short period of time, please try again {time}.",
"auth.errors.userNotFound": "User not found",
"auth.errors.missingName": "Name is missing.",
"auth.errors.invalidName": "Name is invalid.",
"auth.errors.missingEmail": "Email is missing.",
"auth.errors.invalidEmail": "Email is invalid.",
"auth.errors.missingPassword": "Password is missing.",
"auth.errors.passwordTooShort": "Password is too short.",
"auth.errors.missingVerifyPassword": "Password Verification is missing.",
"auth.errors.passwordsNotMatch": "Passwords do not match.",
"auth.errors.missingUsername": "Username is missing.",
"auth.errors.register": "One or more fields are invalid.",
"auth.errors.login": "Missing or invalid login fields.",
"auth.errors.forgotPassword": "Missing or invalid email address.",
"auth.errors.tfaMissing": "Missing or incomplete security code.",
"auth.login.title": "Login",
"auth.fields.email": "Email Address",
"auth.fields.emailUser": "Email / Username",
"auth.fields.name": "Name",
......@@ -939,7 +954,7 @@
"auth.fields.username": "Username",
"auth.fields.verifyPassword": "Verify Password",
"auth.forgotPasswordCancel": "Cancel",
"auth.forgotPasswordLink": "Forgot your password?",
"auth.forgotPasswordLink": "Forgot Password",
"auth.forgotPasswordLoading": "Requesting password reset...",
"auth.forgotPasswordSubtitle": "Enter your email address to receive the instructions to reset your password:",
"auth.forgotPasswordSuccess": "Check your emails for password reset instructions!",
......@@ -960,29 +975,17 @@
"auth.passwordNotMatch": "Both passwords do not match.",
"auth.passwordTooShort": "Password is too short.",
"auth.pleaseWait": "Please wait",
"auth.providers.azure": "Azure Active Directory",
"auth.providers.facebook": "Facebook",
"auth.providers.github": "GitHub",
"auth.providers.google": "Google ID",
"auth.providers.ldap": "LDAP / Active Directory",
"auth.providers.local": "Local",
"auth.providers.slack": "Slack",
"auth.providers.windowslive": "Microsoft Account",
"auth.registerCheckEmail": "Check your emails to activate your account.",
"auth.registerSubTitle": "Fill-in the form below to create your account.",
"auth.registerSubTitle": "Fill-in the form below to create an account.",
"auth.registerSuccess": "Account created successfully!",
"auth.registerTitle": "Create an account",
"auth.registering": "Creating account...",
"auth.selectAuthProvider": "Select Authentication Provider",
"auth.selectAuthProvider": "Sign in with",
"auth.sendResetPassword": "Reset Password",
"auth.signingIn": "Signing In...",
"auth.switchToLogin.link": "Login instead",
"auth.switchToLogin.text": "Already have an account? {link}",
"auth.switchToRegister.link": "Create an account",
"auth.switchToRegister.text": "Don't have an account yet? {link}",
"auth.tfa.placeholder": "XXXXXX",
"auth.switchToLogin.link": "Back to Login",
"auth.switchToRegister.link": "Create an Account",
"auth.tfa.subtitle": "Security code required:",
"auth.tfa.title": "Two Factor Authentication",
"auth.tfa.verifyToken": "Verify",
"auth.tfaFormTitle": "Enter the security code generated from your trusted device:",
"auth.tfaSetupInstrFirst": "1) Scan the QR code below from your mobile 2FA application:",
......@@ -1147,11 +1150,11 @@
"common.pageSelector.pages": "Pages",
"common.pageSelector.selectTitle": "Select a Page",
"common.pageSelector.virtualFolders": "Virtual Folders",
"common.password.good": "Good",
"common.password.average": "Average",
"common.password.strong": "Strong",
"common.password.veryStrong": "Very Strong",
"common.password.veryWeak": "Very Weak",
"common.password.weak": "Weak",
"common.password.poor": "Poor",
"common.sidebar.browse": "Browse",
"common.sidebar.currentDirectory": "Current Directory",
"common.sidebar.mainMenu": "Main Menu",
......
......@@ -20,7 +20,7 @@ q-page.admin-locale
icon='las la-question-circle'
flat
color='grey'
:href='siteStore.docsBase + `/admin/locale`'
:href='siteStore.docsBase + `/admin/localisation`'
target='_blank'
type='a'
)
......
......@@ -5,79 +5,7 @@
img(src='/_assets/logo-wikijs.svg' :alt='siteStore.title')
h2.auth-site-title {{ siteStore.title }}
p.text-grey-7 Login to continue
template(v-if='state.strategies?.length > 1 || true')
p Sign in with
.auth-strategies
q-btn(
label='GitHub'
icon='lab la-github'
push
no-caps
:color='$q.dark.isActive ? `blue-grey-9` : `grey-1`'
:text-color='$q.dark.isActive ? `white` : `blue-grey-9`'
)
q-btn(
label='Google'
icon='lab la-google-plus'
push
no-caps
:color='$q.dark.isActive ? `blue-grey-9` : `grey-1`'
:text-color='$q.dark.isActive ? `white` : `blue-grey-9`'
)
q-btn(
label='Twitter'
icon='lab la-twitter'
push
no-caps
:color='$q.dark.isActive ? `blue-grey-9` : `grey-1`'
:text-color='$q.dark.isActive ? `white` : `blue-grey-9`'
)
q-btn(
label='Local'
icon='las la-seedling'
push
color='primary'
no-caps
)
q-form.q-mt-md
q-input(
outlined
label='Email Address'
autocomplete='email'
)
template(#prepend)
i.las.la-user
q-input.q-mt-sm(
outlined
label='Password'
type='password'
autocomplete='current-password'
)
template(#prepend)
i.las.la-key
q-btn.full-width.q-mt-sm(
push
color='primary'
label='Login'
no-caps
icon='las la-sign-in-alt'
)
template(v-if='true')
q-separator.q-my-md
q-btn.acrylic-btn.full-width(
flat
color='primary'
label='Create an Account'
no-caps
icon='las la-user-plus'
)
q-btn.acrylic-btn.full-width.q-mt-sm(
flat
color='primary'
label='Forgot Password'
no-caps
icon='las la-life-ring'
)
auth-login-panel
.auth-bg(aria-hidden="true")
img(src='https://docs.requarks.io/_assets/img/splash/1.jpg' alt='')
</template>
......@@ -91,8 +19,9 @@ import { useI18n } from 'vue-i18n'
import { useMeta, useQuasar } from 'quasar'
import { onMounted, reactive, watch } from 'vue'
import AuthLoginPanel from 'src/components/AuthLoginPanel.vue'
import { useSiteStore } from 'src/stores/site'
import { useDataStore } from 'src/stores/data'
// QUASAR
......@@ -101,7 +30,6 @@ const $q = useQuasar()
// STORES
const siteStore = useSiteStore()
const dataStore = useDataStore()
// I18N
......@@ -116,27 +44,6 @@ useMeta({
// DATA
const state = reactive({
error: false,
strategies: [],
selectedStrategyKey: 'unselected',
selectedStrategy: { key: 'unselected', strategy: { useForm: false, usernameType: 'email' } },
screen: 'login',
username: '',
password: '',
hidePassword: true,
securityCode: '',
continuationToken: '',
isLoading: false,
loaderColor: 'grey darken-4',
loaderTitle: 'Working...',
isShown: false,
newPassword: '',
newPasswordVerify: '',
isTFAShown: false,
isTFASetupShown: false,
tfaQRImage: '',
errorShown: false,
errorMessage: '',
bgUrl: '_assets/bg/login-v3.jpg'
})
......@@ -186,37 +93,6 @@ const state = reactive({
// METHODS
async function fetchStrategies () {
const resp = await APOLLO_CLIENT.query({
query: gql`
query loginFetchSiteStrategies(
$siteId: UUID!
) {
authSiteStrategies(
siteId: $siteId
enabledOnly: true
) {
key
strategy {
key
logo
color
icon
useForm
usernameType
}
displayName
order
selfRegistration
}
}
`,
variables: {
siteId: siteStore.id
}
})
}
/**
* LOGIN
*/
......@@ -531,7 +407,7 @@ function handleLoginResponse (respObj) {
}
onMounted(() => {
fetchStrategies()
// fetchStrategies()
})
</script>
......
This diff was suppressed by a .gitattributes entry.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment