feat: auth jwt, permissions, login ui (wip)

This commit is contained in:
Nicolas Giard
2018-10-08 00:17:31 -04:00
parent 563d1a4f98
commit 3abd2f917c
53 changed files with 550 additions and 438 deletions

View 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

View File

@@ -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
}

View File

@@ -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
}
},

View File

@@ -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
}

View File

@@ -13,6 +13,10 @@ enum RightRole {
manage
}
# DIRECTIVES
directive @auth(requires: [String]) on QUERY | FIELD_DEFINITION | ARGUMENT_DEFINITION
# TYPES
type KeyValuePair {