feat: auth jwt, permissions, login ui (wip)
This commit is contained in:
@@ -5,6 +5,7 @@ const express = require('express')
|
||||
const router = express.Router()
|
||||
const ExpressBrute = require('express-brute')
|
||||
const ExpressBruteRedisStore = require('express-brute-redis')
|
||||
const jwt = require('jsonwebtoken')
|
||||
const moment = require('moment')
|
||||
const _ = require('lodash')
|
||||
|
||||
@@ -40,7 +41,7 @@ router.get('/login', function (req, res, next) {
|
||||
router.post('/login', bruteforce.prevent, function (req, res, next) {
|
||||
new Promise((resolve, reject) => {
|
||||
// [1] LOCAL AUTHENTICATION
|
||||
WIKI.auth.passport.authenticate('local', function (err, user, info) {
|
||||
WIKI.auth.passport.authenticate('local', { session: false }, function (err, user, info) {
|
||||
if (err) { return reject(err) }
|
||||
if (!user) { return reject(new Error('INVALID_LOGIN')) }
|
||||
resolve(user)
|
||||
@@ -49,7 +50,7 @@ router.post('/login', bruteforce.prevent, function (req, res, next) {
|
||||
if (_.has(WIKI.config.auth.strategy, 'ldap')) {
|
||||
// [2] LDAP AUTHENTICATION
|
||||
return new Promise((resolve, reject) => {
|
||||
WIKI.auth.passport.authenticate('ldapauth', function (err, user, info) {
|
||||
WIKI.auth.passport.authenticate('ldapauth', { session: false }, function (err, user, info) {
|
||||
if (err) { return reject(err) }
|
||||
if (info && info.message) { return reject(new Error(info.message)) }
|
||||
if (!user) { return reject(new Error('INVALID_LOGIN')) }
|
||||
@@ -61,12 +62,12 @@ router.post('/login', bruteforce.prevent, function (req, res, next) {
|
||||
}
|
||||
}).then((user) => {
|
||||
// LOGIN SUCCESS
|
||||
return req.logIn(user, function (err) {
|
||||
return req.logIn(user, { session: false }, function (err) {
|
||||
if (err) { return next(err) }
|
||||
req.brute.reset(function () {
|
||||
return res.redirect('/')
|
||||
})
|
||||
}) || true
|
||||
})
|
||||
}).catch(err => {
|
||||
// LOGIN FAIL
|
||||
if (err.message === 'INVALID_LOGIN') {
|
||||
|
||||
@@ -1,14 +1,10 @@
|
||||
const passport = require('passport')
|
||||
const passportJWT = require('passport-jwt')
|
||||
const fs = require('fs-extra')
|
||||
const _ = require('lodash')
|
||||
const path = require('path')
|
||||
const NodeCache = require('node-cache')
|
||||
|
||||
const userCache = new NodeCache({
|
||||
stdTTL: 10,
|
||||
checkperiod: 600,
|
||||
deleteOnExpire: true
|
||||
})
|
||||
const securityHelper = require('../helpers/security')
|
||||
|
||||
/* global WIKI */
|
||||
|
||||
@@ -24,22 +20,16 @@ module.exports = {
|
||||
})
|
||||
|
||||
passport.deserializeUser(function (id, done) {
|
||||
const usr = userCache.get(id)
|
||||
if (usr) {
|
||||
done(null, usr)
|
||||
} else {
|
||||
WIKI.models.users.query().findById(id).then((user) => {
|
||||
if (user) {
|
||||
userCache.set(id, user)
|
||||
done(null, user)
|
||||
} else {
|
||||
done(new Error(WIKI.lang.t('auth:errors:usernotfound')), null)
|
||||
}
|
||||
return true
|
||||
}).catch((err) => {
|
||||
done(err, null)
|
||||
})
|
||||
}
|
||||
WIKI.models.users.query().findById(id).then((user) => {
|
||||
if (user) {
|
||||
done(null, user)
|
||||
} else {
|
||||
done(new Error(WIKI.lang.t('auth:errors:usernotfound')), null)
|
||||
}
|
||||
return true
|
||||
}).catch((err) => {
|
||||
done(err, null)
|
||||
})
|
||||
})
|
||||
|
||||
return this
|
||||
@@ -52,6 +42,16 @@ module.exports = {
|
||||
_.pull(currentStrategies, 'session')
|
||||
_.forEach(currentStrategies, stg => { passport.unuse(stg) })
|
||||
|
||||
// Load JWT
|
||||
passport.use('jwt', new passportJWT.Strategy({
|
||||
jwtFromRequest: securityHelper.extractJWT,
|
||||
secretOrKey: WIKI.config.sessionSecret,
|
||||
audience: 'urn:wiki.js', // TODO: use value from admin
|
||||
issuer: 'urn:wiki.js'
|
||||
}, (jwtPayload, cb) => {
|
||||
cb(null, jwtPayload)
|
||||
}))
|
||||
|
||||
// Load enabled strategies
|
||||
const enabledStrategies = await WIKI.models.authentication.getStrategies()
|
||||
for (let idx in enabledStrategies) {
|
||||
|
||||
@@ -55,6 +55,8 @@ exports.up = knex => {
|
||||
table.charset('utf8mb4')
|
||||
table.increments('id').primary()
|
||||
table.string('name').notNullable()
|
||||
table.json('permissions').notNullable()
|
||||
table.boolean('isSystem').notNullable().defaultTo(false)
|
||||
table.string('createdAt').notNullable()
|
||||
table.string('updatedAt').notNullable()
|
||||
})
|
||||
@@ -118,6 +120,17 @@ exports.up = knex => {
|
||||
table.string('createdAt').notNullable()
|
||||
table.string('updatedAt').notNullable()
|
||||
})
|
||||
// PAGE TREE ---------------------------
|
||||
.createTable('pageTree', table => {
|
||||
table.charset('utf8mb4')
|
||||
table.increments('id').primary()
|
||||
table.string('path').notNullable()
|
||||
table.integer('depth').unsigned().notNullable()
|
||||
table.string('title').notNullable()
|
||||
table.boolean('isPrivate').notNullable().defaultTo(false)
|
||||
table.boolean('isFolder').notNullable().defaultTo(false)
|
||||
table.string('privateNS')
|
||||
})
|
||||
// RENDERERS ---------------------------
|
||||
.createTable('renderers', table => {
|
||||
table.charset('utf8mb4')
|
||||
@@ -166,7 +179,6 @@ exports.up = knex => {
|
||||
table.string('password')
|
||||
table.boolean('tfaIsActive').notNullable().defaultTo(false)
|
||||
table.string('tfaSecret')
|
||||
table.enum('role', ['admin', 'guest', 'user']).notNullable().defaultTo('guest')
|
||||
table.string('jobTitle').defaultTo('')
|
||||
table.string('location').defaultTo('')
|
||||
table.string('pictureUrl')
|
||||
@@ -221,6 +233,11 @@ exports.up = knex => {
|
||||
table.integer('authorId').unsigned().references('id').inTable('users')
|
||||
table.integer('creatorId').unsigned().references('id').inTable('users')
|
||||
})
|
||||
.table('pageTree', table => {
|
||||
table.integer('parent').unsigned().references('id').inTable('pageTree')
|
||||
table.integer('pageId').unsigned().references('id').inTable('pages')
|
||||
table.string('localeCode', 2).references('code').inTable('locales')
|
||||
})
|
||||
.table('users', table => {
|
||||
table.string('providerKey').references('key').inTable('authentication').notNullable().defaultTo('local')
|
||||
table.string('localeCode', 2).references('code').inTable('locales').notNullable().defaultTo('en')
|
||||
|
||||
54
server/graph/directives/auth.js
Normal file
54
server/graph/directives/auth.js
Normal file
@@ -0,0 +1,54 @@
|
||||
const { SchemaDirectiveVisitor } = require('graphql-tools')
|
||||
const { defaultFieldResolver } = require('graphql')
|
||||
|
||||
class AuthDirective extends SchemaDirectiveVisitor {
|
||||
visitObject(type) {
|
||||
this.ensureFieldsWrapped(type)
|
||||
type._requiredAuthScopes = this.args.requires
|
||||
}
|
||||
// Visitor methods for nested types like fields and arguments
|
||||
// also receive a details object that provides information about
|
||||
// the parent and grandparent types.
|
||||
visitFieldDefinition(field, details) {
|
||||
this.ensureFieldsWrapped(details.objectType)
|
||||
field._requiredAuthScopes = this.args.requires
|
||||
}
|
||||
|
||||
visitArgumentDefinition(argument, details) {
|
||||
this.ensureFieldsWrapped(details.objectType)
|
||||
argument._requiredAuthScopes = this.args.requires
|
||||
}
|
||||
|
||||
ensureFieldsWrapped(objectType) {
|
||||
// Mark the GraphQLObjectType object to avoid re-wrapping:
|
||||
if (objectType._authFieldsWrapped) return
|
||||
objectType._authFieldsWrapped = true
|
||||
|
||||
const fields = objectType.getFields()
|
||||
|
||||
Object.keys(fields).forEach(fieldName => {
|
||||
const field = fields[fieldName]
|
||||
const { resolve = defaultFieldResolver } = field
|
||||
field.resolve = async function (...args) {
|
||||
// Get the required scopes from the field first, falling back
|
||||
// to the objectType if no scopes is required by the field:
|
||||
const requiredScopes = field._requiredAuthScopes || objectType._requiredAuthScopes
|
||||
|
||||
if (!requiredScopes) {
|
||||
return resolve.apply(this, args)
|
||||
}
|
||||
|
||||
const context = args[2]
|
||||
console.info(context.req.user)
|
||||
// const user = await getUser(context.headers.authToken)
|
||||
// if (!user.hasRole(requiredScopes)) {
|
||||
// throw new Error('not authorized')
|
||||
// }
|
||||
|
||||
return resolve.apply(this, args)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = AuthDirective
|
||||
@@ -31,6 +31,10 @@ resolversObj.forEach(resolver => {
|
||||
_.merge(resolvers, resolver)
|
||||
})
|
||||
|
||||
// Directives
|
||||
|
||||
let schemaDirectives = autoload(path.join(WIKI.SERVERPATH, 'graph/directives'))
|
||||
|
||||
// Live Trail Logger (admin)
|
||||
|
||||
let LiveTrailLogger = winston.transports.LiveTrailLogger = function (options) {
|
||||
@@ -55,5 +59,6 @@ WIKI.logger.info(`GraphQL Schema: [ OK ]`)
|
||||
|
||||
module.exports = {
|
||||
typeDefs,
|
||||
resolvers
|
||||
resolvers,
|
||||
schemaDirectives
|
||||
}
|
||||
|
||||
@@ -3,8 +3,6 @@ const fs = require('fs-extra')
|
||||
const path = require('path')
|
||||
const graphHelper = require('../../helpers/graph')
|
||||
|
||||
// const getFieldNames = require('graphql-list-fields')
|
||||
|
||||
/* global WIKI */
|
||||
|
||||
module.exports = {
|
||||
@@ -16,7 +14,7 @@ module.exports = {
|
||||
},
|
||||
AuthenticationQuery: {
|
||||
async strategies(obj, args, context, info) {
|
||||
let strategies = await WIKI.models.authentication.getStrategies()
|
||||
let strategies = await WIKI.models.authentication.getStrategies(args.isEnabled)
|
||||
strategies = strategies.map(stg => {
|
||||
const strategyInfo = _.find(WIKI.data.authentication, ['key', stg.key]) || {}
|
||||
return {
|
||||
@@ -34,8 +32,6 @@ module.exports = {
|
||||
}, []), 'key')
|
||||
}
|
||||
})
|
||||
if (args.filter) { strategies = graphHelper.filter(strategies, args.filter) }
|
||||
if (args.orderBy) { strategies = graphHelper.orderBy(strategies, args.orderBy) }
|
||||
return strategies
|
||||
}
|
||||
},
|
||||
|
||||
@@ -16,8 +16,7 @@ extend type Mutation {
|
||||
|
||||
type AuthenticationQuery {
|
||||
strategies(
|
||||
filter: String
|
||||
orderBy: String
|
||||
isEnabled: Boolean
|
||||
): [AuthenticationStrategy]
|
||||
}
|
||||
|
||||
@@ -54,16 +53,18 @@ type AuthenticationStrategy {
|
||||
description: String
|
||||
useForm: Boolean!
|
||||
logo: String
|
||||
color: String
|
||||
website: String
|
||||
icon: String
|
||||
config: [KeyValuePair]
|
||||
config: [KeyValuePair] @auth(requires: ["manage:system"])
|
||||
selfRegistration: Boolean!
|
||||
domainWhitelist: [String]!
|
||||
autoEnrollGroups: [Int]!
|
||||
domainWhitelist: [String]! @auth(requires: ["manage:system"])
|
||||
autoEnrollGroups: [Int]! @auth(requires: ["manage:system"])
|
||||
}
|
||||
|
||||
type AuthenticationLoginResponse {
|
||||
responseResult: ResponseStatus
|
||||
jwt: String
|
||||
tfaRequired: Boolean
|
||||
tfaLoginToken: String
|
||||
}
|
||||
|
||||
@@ -13,6 +13,10 @@ enum RightRole {
|
||||
manage
|
||||
}
|
||||
|
||||
# DIRECTIVES
|
||||
|
||||
directive @auth(requires: [String]) on QUERY | FIELD_DEFINITION | ARGUMENT_DEFINITION
|
||||
|
||||
# TYPES
|
||||
|
||||
type KeyValuePair {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
const Promise = require('bluebird')
|
||||
const crypto = require('crypto')
|
||||
const passportJWT = require('passport-jwt')
|
||||
|
||||
module.exports = {
|
||||
sanitizeCommitUser (user) {
|
||||
@@ -21,5 +22,18 @@ module.exports = {
|
||||
}).then(buf => {
|
||||
return buf.toString('hex')
|
||||
})
|
||||
},
|
||||
|
||||
async extractJWT (req) {
|
||||
return passportJWT.ExtractJwt.fromExtractors([
|
||||
passportJWT.ExtractJwt.fromAuthHeaderAsBearerToken(),
|
||||
(req) => {
|
||||
let token = null
|
||||
if (req && req.cookies) {
|
||||
token = req.cookies['jwt']
|
||||
}
|
||||
return token
|
||||
}
|
||||
])(req)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,8 +7,6 @@ const express = require('express')
|
||||
const favicon = require('serve-favicon')
|
||||
const http = require('http')
|
||||
const path = require('path')
|
||||
const session = require('express-session')
|
||||
const SessionRedisStore = require('connect-redis')(session)
|
||||
const { ApolloServer } = require('apollo-server-express')
|
||||
// const oauth2orize = require('oauth2orize')
|
||||
|
||||
@@ -66,20 +64,9 @@ module.exports = async () => {
|
||||
// Passport Authentication
|
||||
// ----------------------------------------
|
||||
|
||||
let sessionStore = new SessionRedisStore({
|
||||
client: WIKI.redis
|
||||
})
|
||||
|
||||
app.use(cookieParser())
|
||||
app.use(session({
|
||||
name: 'wikijs.sid',
|
||||
store: sessionStore,
|
||||
secret: WIKI.config.sessionSecret,
|
||||
resave: false,
|
||||
saveUninitialized: false
|
||||
}))
|
||||
app.use(WIKI.auth.passport.initialize())
|
||||
app.use(WIKI.auth.passport.session())
|
||||
app.use(mw.auth.jwt)
|
||||
|
||||
// ----------------------------------------
|
||||
// SEO
|
||||
@@ -145,7 +132,7 @@ module.exports = async () => {
|
||||
|
||||
app.use('/', ctrl.auth)
|
||||
|
||||
app.use('/', mw.auth, ctrl.common)
|
||||
app.use('/', mw.auth.checkPath, ctrl.common)
|
||||
|
||||
// ----------------------------------------
|
||||
// Error handling
|
||||
|
||||
@@ -1,33 +1,75 @@
|
||||
const jwt = require('jsonwebtoken')
|
||||
const moment = require('moment')
|
||||
|
||||
const securityHelper = require('../helpers/security')
|
||||
|
||||
/* global WIKI */
|
||||
|
||||
/**
|
||||
* Authentication middleware
|
||||
*/
|
||||
module.exports = (req, res, next) => {
|
||||
// Is user authenticated ?
|
||||
module.exports = {
|
||||
jwt(req, res, next) {
|
||||
WIKI.auth.passport.authenticate('jwt', {session: false}, async (err, user, info) => {
|
||||
if (err) { return next() }
|
||||
|
||||
if (!req.isAuthenticated()) {
|
||||
if (WIKI.config.public !== true) {
|
||||
return res.redirect('/login')
|
||||
console.info(err, user, info)
|
||||
|
||||
// Expired but still valid within 7 days, just renew
|
||||
if (info instanceof jwt.TokenExpiredError && moment().subtract(7, 'days').isBefore(info.expiredAt)) {
|
||||
const jwtPayload = jwt.decode(securityHelper.extractJWT(req))
|
||||
console.info(jwtPayload)
|
||||
try {
|
||||
const newToken = await WIKI.models.users.refreshToken(jwtPayload.id)
|
||||
user = newToken.user
|
||||
|
||||
// Try headers, otherwise cookies for response
|
||||
if (req.get('content-type') === 'application/json') {
|
||||
res.headers('new-jwt', newToken.token)
|
||||
} else {
|
||||
res.cookie('jwt', newToken.token, { expires: moment().add(7, 'days').toDate() })
|
||||
}
|
||||
} catch (err) {
|
||||
return next()
|
||||
}
|
||||
}
|
||||
|
||||
// JWT is NOT valid
|
||||
if (!user) { return next() }
|
||||
|
||||
// JWT is valid
|
||||
req.logIn(user, { session: false }, (err) => {
|
||||
if (err) { return next(err) }
|
||||
next()
|
||||
})
|
||||
})(req, res, next)
|
||||
},
|
||||
checkPath(req, res, next) {
|
||||
// Is user authenticated ?
|
||||
|
||||
if (!req.isAuthenticated()) {
|
||||
if (WIKI.config.public !== true) {
|
||||
return res.redirect('/login')
|
||||
} else {
|
||||
// req.user = rights.guest
|
||||
res.locals.isGuest = true
|
||||
}
|
||||
} else {
|
||||
// req.user = rights.guest
|
||||
res.locals.isGuest = true
|
||||
res.locals.isGuest = false
|
||||
}
|
||||
} else {
|
||||
res.locals.isGuest = false
|
||||
|
||||
// Check permissions
|
||||
|
||||
// res.locals.rights = rights.check(req)
|
||||
|
||||
// if (!res.locals.rights.read) {
|
||||
// return res.render('error-forbidden')
|
||||
// }
|
||||
|
||||
// Expose user data
|
||||
|
||||
res.locals.user = req.user
|
||||
|
||||
return next()
|
||||
}
|
||||
|
||||
// Check permissions
|
||||
|
||||
// res.locals.rights = rights.check(req)
|
||||
|
||||
// if (!res.locals.rights.read) {
|
||||
// return res.render('error-forbidden')
|
||||
// }
|
||||
|
||||
// Expose user data
|
||||
|
||||
res.locals.user = req.user
|
||||
|
||||
return next()
|
||||
}
|
||||
|
||||
@@ -30,13 +30,13 @@ module.exports = class Authentication extends Model {
|
||||
}
|
||||
}
|
||||
|
||||
static async getStrategies() {
|
||||
const strategies = await WIKI.models.authentication.query()
|
||||
return strategies.map(str => ({
|
||||
static async getStrategies(isEnabled) {
|
||||
const strategies = await WIKI.models.authentication.query().where(_.isBoolean(isEnabled) ? { isEnabled } : {})
|
||||
return _.sortBy(strategies.map(str => ({
|
||||
...str,
|
||||
domainWhitelist: _.get(str.domainWhitelist, 'v', []),
|
||||
autoEnrollGroups: _.get(str.autoEnrollGroups, 'v', [])
|
||||
}))
|
||||
})), ['title'])
|
||||
}
|
||||
|
||||
static async refreshStrategiesFromDisk() {
|
||||
|
||||
@@ -4,6 +4,7 @@ const bcrypt = require('bcryptjs-then')
|
||||
const _ = require('lodash')
|
||||
const tfa = require('node-2fa')
|
||||
const securityHelper = require('../helpers/security')
|
||||
const jwt = require('jsonwebtoken')
|
||||
const Model = require('objection').Model
|
||||
|
||||
const bcryptRegexp = /^\$2[ayb]\$[0-9]{2}\$[A-Za-z0-9./]{53}$/
|
||||
@@ -199,7 +200,7 @@ module.exports = class User extends Model {
|
||||
|
||||
// Authenticate
|
||||
return new Promise((resolve, reject) => {
|
||||
WIKI.auth.passport.authenticate(opts.strategy, async (err, user, info) => {
|
||||
WIKI.auth.passport.authenticate(opts.strategy, { session: false }, async (err, user, info) => {
|
||||
if (err) { return reject(err) }
|
||||
if (!user) { return reject(new WIKI.Error.AuthLoginFailed()) }
|
||||
|
||||
@@ -218,9 +219,11 @@ module.exports = class User extends Model {
|
||||
}
|
||||
} else {
|
||||
// No 2FA, log in user
|
||||
return context.req.logIn(user, err => {
|
||||
return context.req.logIn(user, { session: false }, async err => {
|
||||
if (err) { return reject(err) }
|
||||
const jwtToken = await WIKI.models.users.refreshToken(user)
|
||||
resolve({
|
||||
jwt: jwtToken.token,
|
||||
tfaRequired: false
|
||||
})
|
||||
})
|
||||
@@ -232,6 +235,33 @@ module.exports = class User extends Model {
|
||||
}
|
||||
}
|
||||
|
||||
static async refreshToken(user) {
|
||||
if (_.isSafeInteger(user)) {
|
||||
user = await WIKI.models.users.query().findById(user)
|
||||
if (!user) {
|
||||
WIKI.logger.warn(`Failed to refresh token for user ${user}: Not found.`)
|
||||
throw new WIKI.Error.AuthGenericError()
|
||||
}
|
||||
}
|
||||
return {
|
||||
token: jwt.sign({
|
||||
id: user.id,
|
||||
email: user.email,
|
||||
name: user.name,
|
||||
pictureUrl: user.pictureUrl,
|
||||
timezone: user.timezone,
|
||||
localeCode: user.localeCode,
|
||||
defaultEditor: user.defaultEditor,
|
||||
permissions: []
|
||||
}, WIKI.config.sessionSecret, {
|
||||
expiresIn: '10s',
|
||||
audience: 'urn:wiki.js', // TODO: use value from admin
|
||||
issuer: 'urn:wiki.js'
|
||||
}),
|
||||
user
|
||||
}
|
||||
}
|
||||
|
||||
static async loginTFA(opts, context) {
|
||||
if (opts.securityCode.length === 6 && opts.loginToken.length === 64) {
|
||||
let result = await WIKI.redis.get(`tfa:${opts.loginToken}`)
|
||||
|
||||
@@ -3,6 +3,7 @@ title: Auth0
|
||||
description: Auth0 provides universal identity platform for web, mobile, IoT, and internal applications.
|
||||
author: requarks.io
|
||||
logo: https://static.requarks.io/logo/auth0.svg
|
||||
color: deep-orange
|
||||
website: https://auth0.com/
|
||||
useForm: false
|
||||
props:
|
||||
|
||||
@@ -3,6 +3,7 @@ title: Azure Active Directory
|
||||
description: Azure Active Directory (Azure AD) is Microsoft’s multi-tenant, cloud-based directory, and identity management service that combines core directory services, application access management, and identity protection into a single solution.
|
||||
author: requarks.io
|
||||
logo: https://static.requarks.io/logo/azure.svg
|
||||
color: blue darken-3
|
||||
website: https://azure.microsoft.com/services/active-directory/
|
||||
useForm: false
|
||||
props:
|
||||
|
||||
@@ -3,6 +3,7 @@ title: CAS
|
||||
description: The Central Authentication Service (CAS) is a single sign-on protocol for the web.
|
||||
author: requarks.io
|
||||
logo: https://static.requarks.io/logo/cas.svg
|
||||
color: green darken-2
|
||||
website: https://apereo.github.io/cas/
|
||||
useForm: false
|
||||
props:
|
||||
|
||||
@@ -3,6 +3,7 @@ title: Discord
|
||||
description: Discord is a proprietary freeware VoIP application designed for gaming communities, that specializes in text, video and audio communication between users in a chat channel.
|
||||
author: requarks.io
|
||||
logo: https://static.requarks.io/logo/discord.svg
|
||||
color: indigo lighten-2
|
||||
website: https://discordapp.com/
|
||||
useForm: false
|
||||
props:
|
||||
|
||||
@@ -3,6 +3,7 @@ title: Dropbox
|
||||
description: Dropbox is a file hosting service that offers cloud storage, file synchronization, personal cloud, and client software.
|
||||
author: requarks.io
|
||||
logo: https://static.requarks.io/logo/dropbox.svg
|
||||
color: blue darken-2
|
||||
website: https://dropbox.com
|
||||
useForm: false
|
||||
props:
|
||||
|
||||
@@ -3,6 +3,7 @@ title: Facebook
|
||||
description: Facebook is an online social media and social networking service company.
|
||||
author: requarks.io
|
||||
logo: https://static.requarks.io/logo/facebook.svg
|
||||
color: indigo
|
||||
website: https://facebook.com/
|
||||
useForm: false
|
||||
props:
|
||||
|
||||
@@ -3,6 +3,7 @@ title: GitHub
|
||||
description: GitHub Inc. is a web-based hosting service for version control using Git.
|
||||
author: requarks.io
|
||||
logo: https://static.requarks.io/logo/github.svg
|
||||
color: grey darken-3
|
||||
website: https://github.com
|
||||
useForm: false
|
||||
props:
|
||||
|
||||
@@ -3,6 +3,7 @@ title: Google
|
||||
description: Google specializes in Internet-related services and products, which include online advertising technologies, search engine, cloud computing, software, and hardware.
|
||||
author: requarks.io
|
||||
logo: https://static.requarks.io/logo/google.svg
|
||||
color: red darken-1
|
||||
website: https://console.developers.google.com/
|
||||
useForm: false
|
||||
props:
|
||||
|
||||
@@ -3,6 +3,7 @@ title: LDAP / Active Directory
|
||||
description: Active Directory is a directory service that Microsoft developed for the Windows domain networks.
|
||||
author: requarks.io
|
||||
logo: https://static.requarks.io/logo/active-directory.svg
|
||||
color: blue darken-3
|
||||
website: https://www.microsoft.com/windowsserver
|
||||
useForm: true
|
||||
props:
|
||||
|
||||
@@ -3,6 +3,7 @@ title: Local
|
||||
description: Built-in authentication for Wiki.js
|
||||
author: requarks.io
|
||||
logo: https://static.requarks.io/logo/wikijs.svg
|
||||
color: yellow darken-3
|
||||
website: https://wiki.js.org
|
||||
useForm: true
|
||||
props: {}
|
||||
|
||||
@@ -3,6 +3,7 @@ title: Microsoft
|
||||
description: Microsoft is a software company, best known for it's Windows, Office, Azure, Xbox and Surface products.
|
||||
author: requarks.io
|
||||
logo: https://static.requarks.io/logo/microsoft.svg
|
||||
color: blue
|
||||
website: https://apps.dev.microsoft.com/
|
||||
useForm: false
|
||||
props:
|
||||
|
||||
@@ -3,6 +3,7 @@ title: Generic OAuth2
|
||||
description: OAuth 2 is an authorization framework that enables applications to obtain limited access to user accounts on an HTTP service.
|
||||
author: requarks.io
|
||||
logo: https://static.requarks.io/logo/oauth2.svg
|
||||
color: grey darken-4
|
||||
website: https://oauth.net/2/
|
||||
useForm: false
|
||||
props:
|
||||
|
||||
@@ -3,6 +3,7 @@ title: Generic OpenID Connect
|
||||
description: OpenID Connect 1.0 is a simple identity layer on top of the OAuth 2.0 protocol.
|
||||
author: requarks.io
|
||||
logo: https://static.requarks.io/logo/oidc.svg
|
||||
color: blue-grey darken-2
|
||||
website: http://openid.net/connect/
|
||||
useForm: false
|
||||
props:
|
||||
|
||||
@@ -3,6 +3,7 @@ title: Okta
|
||||
description: Okta provide secure identity management and single sign-on to any application.
|
||||
author: requarks.io
|
||||
logo: https://static.requarks.io/logo/okta.svg
|
||||
color: blue darken-1
|
||||
website: https://www.okta.com/
|
||||
useForm: false
|
||||
props:
|
||||
|
||||
@@ -3,6 +3,7 @@ title: SAML 2.0
|
||||
description: Security Assertion Markup Language 2.0 (SAML 2.0) is a version of the SAML standard for exchanging authentication and authorization data between security domains.
|
||||
author: requarks.io
|
||||
logo: https://static.requarks.io/logo/saml.svg
|
||||
color: red darken-3
|
||||
website: https://wiki.oasis-open.org/security/FrontPage
|
||||
useForm: false
|
||||
props:
|
||||
|
||||
@@ -3,6 +3,7 @@ title: Slack
|
||||
description: Slack is a cloud-based set of proprietary team collaboration tools and services.
|
||||
author: requarks.io
|
||||
logo: https://static.requarks.io/logo/slack.svg
|
||||
color: green
|
||||
website: https://api.slack.com/docs/oauth
|
||||
useForm: false
|
||||
props:
|
||||
|
||||
@@ -3,6 +3,7 @@ title: Twitch
|
||||
description: Twitch is a live streaming video platform.
|
||||
author: requarks.io
|
||||
logo: https://static.requarks.io/logo/twitch.svg
|
||||
color: indigo darken-2
|
||||
website: https://dev.twitch.tv/docs/authentication/
|
||||
useForm: false
|
||||
props:
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
const path = require('path')
|
||||
const os = require('os')
|
||||
|
||||
/* global WIKI */
|
||||
|
||||
@@ -133,6 +132,20 @@ module.exports = () => {
|
||||
nativeName: 'English'
|
||||
})
|
||||
|
||||
// Create default locale
|
||||
|
||||
WIKI.logger.info('Creating default groups...')
|
||||
const adminGroup = await WIKI.models.groups.query().insert({
|
||||
name: 'Administrators',
|
||||
permissions: JSON.stringify(['manage:system']),
|
||||
isSystem: true
|
||||
})
|
||||
const guestGroup = await WIKI.models.groups.query().insert({
|
||||
name: 'Guests',
|
||||
permissions: JSON.stringify(['read:page:/']),
|
||||
isSystem: true
|
||||
})
|
||||
|
||||
// Load authentication strategies + enable local
|
||||
await WIKI.models.authentication.refreshStrategiesFromDisk()
|
||||
await WIKI.models.authentication.query().patch({ isEnabled: true }).where('key', 'local')
|
||||
@@ -160,35 +173,33 @@ module.exports = () => {
|
||||
providerKey: 'local',
|
||||
email: req.body.adminEmail
|
||||
})
|
||||
await WIKI.models.users.query().insert({
|
||||
const adminUser = await WIKI.models.users.query().insert({
|
||||
email: req.body.adminEmail,
|
||||
provider: 'local',
|
||||
password: req.body.adminPassword,
|
||||
name: 'Administrator',
|
||||
role: 'admin',
|
||||
locale: 'en',
|
||||
defaultEditor: 'markdown',
|
||||
tfaIsActive: false
|
||||
})
|
||||
await adminUser.$relatedQuery('groups').relate(adminGroup.id)
|
||||
|
||||
// Create Guest account
|
||||
WIKI.logger.info('Creating guest account...')
|
||||
const guestUsr = await WIKI.models.users.query().findOne({
|
||||
await WIKI.models.users.query().delete().where({
|
||||
providerKey: 'local',
|
||||
email: 'guest@example.com'
|
||||
})
|
||||
if (!guestUsr) {
|
||||
await WIKI.models.users.query().insert({
|
||||
provider: 'local',
|
||||
email: 'guest@example.com',
|
||||
name: 'Guest',
|
||||
password: '',
|
||||
role: 'guest',
|
||||
locale: 'en',
|
||||
defaultEditor: 'markdown',
|
||||
tfaIsActive: false
|
||||
})
|
||||
}
|
||||
const guestUser = await WIKI.models.users.query().insert({
|
||||
provider: 'local',
|
||||
email: 'guest@example.com',
|
||||
name: 'Guest',
|
||||
password: '',
|
||||
locale: 'en',
|
||||
defaultEditor: 'markdown',
|
||||
tfaIsActive: false
|
||||
})
|
||||
await guestUser.$relatedQuery('groups').relate(guestGroup.id)
|
||||
|
||||
WIKI.logger.info('Setup is complete!')
|
||||
res.json({
|
||||
|
||||
@@ -22,7 +22,7 @@ html
|
||||
var siteConfig = !{JSON.stringify({ title: config.title, theme: config.theming.theme, darkMode: config.theming.darkMode, lang: config.lang.code })}
|
||||
|
||||
//- CSS
|
||||
link(type='text/css', rel='stylesheet', href='https://fonts.googleapis.com/icon?family=Roboto:400,500,700|Source+Code+Pro:400,700|Material+Icons')
|
||||
link(type='text/css', rel='stylesheet', href='https://fonts.googleapis.com/icon?family=Roboto:400,500,700|Varela+Round|Source+Code+Pro:400,700|Material+Icons')
|
||||
link(type='text/css', rel='stylesheet', href='https://cdnjs.cloudflare.com/ajax/libs/material-design-iconic-font/2.2.0/css/material-design-iconic-font.min.css')
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user