feat: new login experience (#2139)
* feat: multiple auth instances * fix: auth setup + strategy initialization * feat: admin auth - add strategy * feat: redirect on login - group setting * feat: oauth2 generic - props definitions * feat: new login UI (wip) * feat: new login UI (wip) * feat: admin security login settings * feat: tabset editor indicators + print view improvements * fix: code styling
This commit is contained in:
		| @@ -34,16 +34,28 @@ module.exports = { | ||||
|     apiState () { | ||||
|       return WIKI.config.api.isEnabled | ||||
|     }, | ||||
|     async strategies () { | ||||
|       return WIKI.data.authentication.map(stg => ({ | ||||
|         ...stg, | ||||
|         isAvailable: stg.isAvailable === true, | ||||
|         props: _.sortBy(_.transform(stg.props, (res, value, key) => { | ||||
|           res.push({ | ||||
|             key, | ||||
|             value: JSON.stringify(value) | ||||
|           }) | ||||
|         }, []), 'key') | ||||
|       })) | ||||
|     }, | ||||
|     /** | ||||
|      * Fetch active authentication strategies | ||||
|      */ | ||||
|     async strategies (obj, args, context, info) { | ||||
|       let strategies = await WIKI.models.authentication.getStrategies(args.isEnabled) | ||||
|     async activeStrategies (obj, args, context, info) { | ||||
|       let strategies = await WIKI.models.authentication.getStrategies() | ||||
|       strategies = strategies.map(stg => { | ||||
|         const strategyInfo = _.find(WIKI.data.authentication, ['key', stg.key]) || {} | ||||
|         const strategyInfo = _.find(WIKI.data.authentication, ['key', stg.strategyKey]) || {} | ||||
|         return { | ||||
|           ...strategyInfo, | ||||
|           ...stg, | ||||
|           strategy: strategyInfo, | ||||
|           config: _.sortBy(_.transform(stg.config, (res, value, key) => { | ||||
|             const configData = _.get(strategyInfo.props, key, false) | ||||
|             if (configData) { | ||||
| @@ -174,16 +186,18 @@ module.exports = { | ||||
|      */ | ||||
|     async updateStrategies (obj, args, context) { | ||||
|       try { | ||||
|         WIKI.config.auth = { | ||||
|           audience: _.get(args, 'config.audience', WIKI.config.auth.audience), | ||||
|           tokenExpiration: _.get(args, 'config.tokenExpiration', WIKI.config.auth.tokenExpiration), | ||||
|           tokenRenewal: _.get(args, 'config.tokenRenewal', WIKI.config.auth.tokenRenewal) | ||||
|         } | ||||
|         await WIKI.configSvc.saveToDb(['auth']) | ||||
|         // WIKI.config.auth = { | ||||
|         //   audience: _.get(args, 'config.audience', WIKI.config.auth.audience), | ||||
|         //   tokenExpiration: _.get(args, 'config.tokenExpiration', WIKI.config.auth.tokenExpiration), | ||||
|         //   tokenRenewal: _.get(args, 'config.tokenRenewal', WIKI.config.auth.tokenRenewal) | ||||
|         // } | ||||
|         // await WIKI.configSvc.saveToDb(['auth']) | ||||
|  | ||||
|         for (let str of args.strategies) { | ||||
|           await WIKI.models.authentication.query().patch({ | ||||
|             isEnabled: str.isEnabled, | ||||
|         const previousStrategies = await WIKI.models.authentication.getStrategies() | ||||
|         for (const str of args.strategies) { | ||||
|           const newStr = { | ||||
|             displayName: str.displayName, | ||||
|             order: str.order, | ||||
|             config: _.reduce(str.config, (result, value, key) => { | ||||
|               _.set(result, `${value.key}`, _.get(JSON.parse(value.value), 'v', null)) | ||||
|               return result | ||||
| @@ -191,8 +205,32 @@ module.exports = { | ||||
|             selfRegistration: str.selfRegistration, | ||||
|             domainWhitelist: { v: str.domainWhitelist }, | ||||
|             autoEnrollGroups: { v: str.autoEnrollGroups } | ||||
|           }).where('key', str.key) | ||||
|           } | ||||
|  | ||||
|           if (_.some(previousStrategies, ['key', str.key])) { | ||||
|             await WIKI.models.authentication.query().patch({ | ||||
|               key: str.key, | ||||
|               strategyKey: str.strategyKey, | ||||
|               ...newStr | ||||
|             }).where('key', str.key) | ||||
|           } else { | ||||
|             await WIKI.models.authentication.query().insert({ | ||||
|               key: str.key, | ||||
|               strategyKey: str.strategyKey, | ||||
|               ...newStr | ||||
|             }) | ||||
|           } | ||||
|         } | ||||
|  | ||||
|         for (const str of _.differenceBy(previousStrategies, args.strategies, 'key')) { | ||||
|           const hasUsers = await WIKI.models.users.query().count('* as total').where({ providerKey: str.key }).first() | ||||
|           if (_.toSafeInteger(hasUsers.total) > 0) { | ||||
|             throw new Error(`Cannot delete ${str.displayName} as 1 or more users are still using it.`) | ||||
|           } else { | ||||
|             await WIKI.models.authentication.query().delete().where('key', str.key) | ||||
|           } | ||||
|         } | ||||
|  | ||||
|         await WIKI.auth.activateStrategies() | ||||
|         WIKI.events.outbound.emit('reloadAuthStrategies') | ||||
|         return { | ||||
|   | ||||
| @@ -102,8 +102,13 @@ module.exports = { | ||||
|         throw new gql.GraphQLError('Some Page Rules contains unsafe or exponential time regex.') | ||||
|       } | ||||
|  | ||||
|       if (_.isEmpty(args.redirectOnLogin)) { | ||||
|         args.redirectOnLogin = '/' | ||||
|       } | ||||
|  | ||||
|       await WIKI.models.groups.query().patch({ | ||||
|         name: args.name, | ||||
|         redirectOnLogin: args.redirectOnLogin, | ||||
|         permissions: JSON.stringify(args.permissions), | ||||
|         pageRules: JSON.stringify(args.pageRules) | ||||
|       }).where('id', args.id) | ||||
|   | ||||
| @@ -171,18 +171,18 @@ module.exports = { | ||||
|      * FETCH TAGS | ||||
|      */ | ||||
|     async tags (obj, args, context, info) { | ||||
|       const pages = await WIKI.models.pages.query().column([ | ||||
|         'path', | ||||
|         { locale: 'localeCode' }, | ||||
|       ]) | ||||
|       const pages = await WIKI.models.pages.query() | ||||
|         .column([ | ||||
|           'path', | ||||
|           { locale: 'localeCode' } | ||||
|         ]) | ||||
|         .withGraphJoined('tags') | ||||
|       const allTags = _.filter(pages, r => { | ||||
|         return WIKI.auth.checkAccess(context.req.user, ['read:pages'], { | ||||
|           path: r.path, | ||||
|           locale: r.locale | ||||
|         }) | ||||
|       }) | ||||
|         .flatMap(r => r.tags) | ||||
|       }).flatMap(r => r.tags) | ||||
|       return _.orderBy(_.uniqBy(allTags, 'id'), ['tag'], ['asc']) | ||||
|     }, | ||||
|     /** | ||||
| @@ -190,10 +190,11 @@ module.exports = { | ||||
|      */ | ||||
|     async searchTags (obj, args, context, info) { | ||||
|       const query = _.trim(args.query) | ||||
|       const pages = await WIKI.models.pages.query().column([ | ||||
|         'path', | ||||
|         { locale: 'localeCode' }, | ||||
|       ]) | ||||
|       const pages = await WIKI.models.pages.query() | ||||
|         .column([ | ||||
|           'path', | ||||
|           { locale: 'localeCode' } | ||||
|         ]) | ||||
|         .withGraphJoined('tags') | ||||
|         .modifyGraph('tags', builder => { | ||||
|           builder.select('tag') | ||||
| @@ -212,9 +213,7 @@ module.exports = { | ||||
|           path: r.path, | ||||
|           locale: r.locale | ||||
|         }) | ||||
|       }) | ||||
|         .flatMap(r => r.tags) | ||||
|         .map(t => t.tag) | ||||
|       }).flatMap(r => r.tags).map(t => t.tag) | ||||
|       return _.uniq(allTags).slice(0, 5) | ||||
|     }, | ||||
|     /** | ||||
| @@ -271,7 +270,7 @@ module.exports = { | ||||
|      * FETCH PAGE LINKS | ||||
|      */ | ||||
|     async links (obj, args, context, info) { | ||||
|       let results; | ||||
|       let results | ||||
|  | ||||
|       if (WIKI.config.db.type === 'mysql' || WIKI.config.db.type === 'mariadb' || WIKI.config.db.type === 'sqlite') { | ||||
|         results = await WIKI.models.knex('pages') | ||||
|   | ||||
| @@ -21,6 +21,11 @@ module.exports = { | ||||
|         ...WIKI.config.seo, | ||||
|         ...WIKI.config.features, | ||||
|         ...WIKI.config.security, | ||||
|         authAutoLogin: WIKI.config.auth.autoLogin, | ||||
|         authLoginBgUrl: WIKI.config.auth.loginBgUrl, | ||||
|         authJwtAudience: WIKI.config.auth.audience, | ||||
|         authJwtExpiration: WIKI.config.auth.tokenExpiration, | ||||
|         authJwtRenewablePeriod: WIKI.config.auth.tokenRenewal, | ||||
|         uploadMaxFileSize: WIKI.config.uploads.maxFileSize, | ||||
|         uploadMaxFiles: WIKI.config.uploads.maxFiles | ||||
|       } | ||||
| @@ -60,6 +65,14 @@ module.exports = { | ||||
|           analyticsId: _.get(args, 'analyticsId', WIKI.config.seo.analyticsId) | ||||
|         } | ||||
|  | ||||
|         WIKI.config.auth = { | ||||
|           autoLogin: _.get(args, 'authAutoLogin', WIKI.config.auth.autoLogin), | ||||
|           loginBgUrl: _.get(args, 'authLoginBgUrl', WIKI.config.auth.loginBgUrl), | ||||
|           audience: _.get(args, 'authJwtAudience', WIKI.config.auth.audience), | ||||
|           tokenExpiration: _.get(args, 'authJwtExpiration', WIKI.config.auth.tokenExpiration), | ||||
|           tokenRenewal: _.get(args, 'authJwtRenewablePeriod', WIKI.config.auth.tokenRenewal) | ||||
|         } | ||||
|  | ||||
|         WIKI.config.features = { | ||||
|           featurePageRatings: _.get(args, 'featurePageRatings', WIKI.config.features.featurePageRatings), | ||||
|           featurePageComments: _.get(args, 'featurePageComments', WIKI.config.features.featurePageComments), | ||||
| @@ -83,7 +96,7 @@ module.exports = { | ||||
|           maxFiles: _.get(args, 'uploadMaxFiles', WIKI.config.uploads.maxFiles) | ||||
|         } | ||||
|  | ||||
|         await WIKI.configSvc.saveToDb(['host', 'title', 'company', 'contentLicense', 'seo', 'logoUrl', 'features', 'security', 'uploads']) | ||||
|         await WIKI.configSvc.saveToDb(['host', 'title', 'company', 'contentLicense', 'seo', 'logoUrl', 'auth', 'features', 'security', 'uploads']) | ||||
|  | ||||
|         if (WIKI.config.security.securityTrustProxy) { | ||||
|           WIKI.app.enable('trust proxy') | ||||
|   | ||||
| @@ -19,9 +19,8 @@ type AuthenticationQuery { | ||||
|  | ||||
|   apiState: Boolean! @auth(requires: ["manage:system", "manage:api"]) | ||||
|  | ||||
|   strategies( | ||||
|     isEnabled: Boolean | ||||
|   ): [AuthenticationStrategy] | ||||
|   strategies: [AuthenticationStrategy] @auth(requires: ["manage:system"]) | ||||
|   activeStrategies: [AuthenticationActiveStrategy] | ||||
| } | ||||
|  | ||||
| # ----------------------------------------------- | ||||
| @@ -68,7 +67,6 @@ type AuthenticationMutation { | ||||
|  | ||||
|   updateStrategies( | ||||
|     strategies: [AuthenticationStrategyInput]! | ||||
|     config: AuthenticationConfigInput | ||||
|   ): DefaultResponse @auth(requires: ["manage:system"]) | ||||
|  | ||||
|   regenerateCertificates: DefaultResponse @auth(requires: ["manage:system"]) | ||||
| @@ -81,9 +79,8 @@ type AuthenticationMutation { | ||||
| # ----------------------------------------------- | ||||
|  | ||||
| type AuthenticationStrategy { | ||||
|   isEnabled: Boolean! | ||||
|   key: String! | ||||
|   props: [String] | ||||
|   props: [KeyValuePair] @auth(requires: ["manage:system"]) | ||||
|   title: String! | ||||
|   description: String | ||||
|   isAvailable: Boolean | ||||
| @@ -92,6 +89,13 @@ type AuthenticationStrategy { | ||||
|   color: String | ||||
|   website: String | ||||
|   icon: String | ||||
| } | ||||
|  | ||||
| type AuthenticationActiveStrategy { | ||||
|   key: String! | ||||
|   strategy: AuthenticationStrategy! | ||||
|   displayName: String! | ||||
|   order: Int! | ||||
|   config: [KeyValuePair] @auth(requires: ["manage:system"]) | ||||
|   selfRegistration: Boolean! | ||||
|   domainWhitelist: [String]! @auth(requires: ["manage:system"]) | ||||
| @@ -112,20 +116,16 @@ type AuthenticationRegisterResponse { | ||||
| } | ||||
|  | ||||
| input AuthenticationStrategyInput { | ||||
|   isEnabled: Boolean! | ||||
|   key: String! | ||||
|   strategyKey: String! | ||||
|   config: [KeyValuePairInput] | ||||
|   displayName: String! | ||||
|   order: Int! | ||||
|   selfRegistration: Boolean! | ||||
|   domainWhitelist: [String]! | ||||
|   autoEnrollGroups: [Int]! | ||||
| } | ||||
|  | ||||
| input AuthenticationConfigInput { | ||||
|   audience: String! | ||||
|   tokenExpiration: String! | ||||
|   tokenRenewal: String! | ||||
| } | ||||
|  | ||||
| type AuthenticationApiKey { | ||||
|   id: Int! | ||||
|   name: String! | ||||
|   | ||||
| @@ -37,6 +37,7 @@ type GroupMutation { | ||||
|   update( | ||||
|     id: Int! | ||||
|     name: String! | ||||
|     redirectOnLogin: String! | ||||
|     permissions: [String]! | ||||
|     pageRules: [PageRuleInput]! | ||||
|   ): DefaultResponse @auth(requires: ["write:groups", "manage:groups", "manage:system"]) | ||||
| @@ -78,6 +79,7 @@ type Group { | ||||
|   id: Int! | ||||
|   name: String! | ||||
|   isSystem: Boolean! | ||||
|   redirectOnLogin: String | ||||
|   permissions: [String]! | ||||
|   pageRules: [PageRule] | ||||
|   users: [UserMinimal] | ||||
|   | ||||
| @@ -33,6 +33,11 @@ type SiteMutation { | ||||
|     company: String | ||||
|     contentLicense: String | ||||
|     logoUrl: String | ||||
|     authAutoLogin: Boolean | ||||
|     authLoginBgUrl: String | ||||
|     authJwtAudience: String | ||||
|     authJwtExpiration: String | ||||
|     authJwtRenewablePeriod: String | ||||
|     featurePageRatings: Boolean | ||||
|     featurePageComments: Boolean | ||||
|     featurePersonalWikis: Boolean | ||||
| @@ -65,6 +70,11 @@ type SiteConfig { | ||||
|   company: String! | ||||
|   contentLicense: String! | ||||
|   logoUrl: String! | ||||
|   authAutoLogin: Boolean | ||||
|   authLoginBgUrl: String | ||||
|   authJwtAudience: String | ||||
|   authJwtExpiration: String | ||||
|   authJwtRenewablePeriod: String | ||||
|   featurePageRatings: Boolean! | ||||
|   featurePageComments: Boolean! | ||||
|   featurePersonalWikis: Boolean! | ||||
|   | ||||
		Reference in New Issue
	
	Block a user