feat: admin auth UI + refs

pull/6775/head
NGPixel 1 year ago
parent 1fb130988a
commit 349f4e5730
No known key found for this signature in database
GPG Key ID: B755FB6870B30F63

@ -414,6 +414,7 @@ export async function up (knex) {
// -> GENERATE IDS
const groupAdminId = uuid()
const groupUserId = uuid()
const groupGuestId = '10000000-0000-4000-8000-000000000001'
const siteId = uuid()
const authModuleId = uuid()
@ -658,6 +659,24 @@ export async function up (knex) {
rules: JSON.stringify([]),
isSystem: true
},
{
id: groupUserId,
name: 'Users',
permissions: JSON.stringify(['read:pages', 'read:assets', 'read:comments']),
rules: JSON.stringify([
{
id: uuid(),
name: 'Default Rule',
roles: ['read:pages', 'read:assets', 'read:comments'],
match: 'START',
mode: 'ALLOW',
path: '',
locales: [],
sites: []
}
]),
isSystem: true
},
{
id: groupGuestId,
name: 'Guests',
@ -744,6 +763,10 @@ export async function up (knex) {
userId: userAdminId,
groupId: groupAdminId
},
{
userId: userAdminId,
groupId: groupUserId
},
{
userId: userGuestId,
groupId: groupGuestId

@ -40,7 +40,16 @@ export default {
* Fetch active authentication strategies
*/
async authActiveStrategies (obj, args, context) {
return WIKI.db.authentication.getStrategies({ enabledOnly: args.enabledOnly })
const strategies = await WIKI.db.authentication.getStrategies({ enabledOnly: args.enabledOnly })
return strategies.map(a => {
const str = _.find(WIKI.data.authentication, ['key', a.module]) || {}
return {
...a,
config: _.transform(str.props, (r, v, k) => {
r[k] = v.sensitive ? a.config[k] : '********'
}, {})
}
})
},
/**
* Fetch site authentication strategies

@ -86,6 +86,7 @@ extend type Mutation {
type AuthenticationStrategy {
key: String
props: JSON
refs: JSON
title: String
description: String
isAvailable: Boolean

@ -115,7 +115,8 @@ export function parseModuleProps (props) {
multiline: value.multiline || false,
sensitive: value.sensitive || false,
icon: value.icon || 'rename',
order: value.order || 100
order: value.order || 100,
if: value.if ?? []
})
return result
}, {})

@ -67,28 +67,32 @@
"admin.auth.activeStrategies": "Active Strategies",
"admin.auth.addStrategy": "Add Strategy",
"admin.auth.allowedWebOrigins": "Allowed Web Origins",
"admin.auth.autoEnrollGroups": "Assign to group",
"admin.auth.autoEnrollGroupsHint": "Automatically assign new users to these groups.",
"admin.auth.autoEnrollGroups": "Assign to group(s)",
"admin.auth.autoEnrollGroupsHint": "Automatically assign new users to these groups. New users are always added to the Users group regardless of this setting.",
"admin.auth.callbackUrl": "Callback URL / Redirect URI",
"admin.auth.configReference": "Configuration Reference",
"admin.auth.configReferenceSubtitle": "Some strategies may require some configuration values to be set on your provider. These are provided for reference only and may not be needed by the current strategy.",
"admin.auth.displayName": "Display Name",
"admin.auth.displayNameHint": "The title shown to the end user for this authentication strategy.",
"admin.auth.domainsWhitelist": "Limit to specific email domains",
"admin.auth.domainsWhitelistHint": "A list of domains authorized to register. The user email address domain must match one of these to gain access.",
"admin.auth.domainsWhitelist": "Email Address Allowlist",
"admin.auth.domainsWhitelistHint": "Only allow users to register with an email address that matches the regex expression.",
"admin.auth.enabled": "Enabled",
"admin.auth.enabledForced": "This strategy cannot be disabled.",
"admin.auth.enabledHint": "Should this strategy be available to sites for login.",
"admin.auth.force2fa": "Force all users to use Two-Factor Authentication (2FA)",
"admin.auth.force2faHint": "Users will be required to setup 2FA the first time they login and cannot be disabled by the user.",
"admin.auth.globalAdvSettings": "Global Advanced Settings",
"admin.auth.info": "Info",
"admin.auth.infoName": "Name",
"admin.auth.infoNameHint": "Display name for this strategy.",
"admin.auth.loginUrl": "Login URL",
"admin.auth.logoutUrl": "Logout URL",
"admin.auth.noConfigOption": "This strategy has no configuration options you can modify.",
"admin.auth.refreshSuccess": "List of strategies has been refreshed.",
"admin.auth.registration": "Registration",
"admin.auth.saveSuccess": "Authentication configuration saved successfully.",
"admin.auth.security": "Security",
"admin.auth.selfRegistration": "Allow self-registration",
"admin.auth.selfRegistration": "Allow Self-Registration",
"admin.auth.selfRegistrationHint": "Allow any user successfully authorized by the strategy to access the wiki.",
"admin.auth.siteUrlNotSetup": "You must set a valid {siteUrl} first! Click on {general} in the left sidebar.",
"admin.auth.status": "Status",

@ -27,4 +27,11 @@ props:
type: String
title: Client Secret
hint: Application Client Secret
sensitive: true
order: 3
refs:
callbackUrl:
title: Authorization Callback URL
hint: The callback endpoint to input on Auth0.
icon: back
value: '{host}/login/{id}/callback'

@ -18,9 +18,16 @@ props:
type: String
title: Client Secret
hint: Application Client Secret
sensitive: true
order: 2
guildId:
type: String
title: Server ID
hint: Optional - Your unique server identifier, such that only members are authorized
order: 3
refs:
callbackUrl:
title: Authorization Callback URL
hint: The callback endpoint to input on Discord.
icon: back
value: '{host}/login/{id}/callback'

@ -18,4 +18,11 @@ props:
type: String
title: App Secret
hint: Application Client Secret
sensitive: true
order: 2
refs:
callbackUrl:
title: Authorization Callback URL
hint: The callback endpoint to input on Dropbox.
icon: back
value: '{host}/login/{id}/callback'

@ -20,4 +20,11 @@ props:
type: String
title: App Secret
hint: Application Secret
sensitive: true
order: 2
refs:
callbackUrl:
title: Authorization Callback URL
hint: The callback endpoint to input on Facebook.
icon: back
value: '{host}/login/{id}/callback'

@ -18,6 +18,7 @@ props:
type: String
title: Client Secret
hint: Application Client Secret
sensitive: true
order: 2
useEnterprise:
type: Boolean
@ -31,10 +32,20 @@ props:
hint: GitHub Enterprise Only - Domain of your installation (e.g. github.company.com). Leave blank otherwise.
default: ''
order: 4
if:
- { key: 'useEnterprise', eq: true }
enterpriseUserEndpoint:
type: String
title: GitHub Enterprise User Endpoint
hint: GitHub Enterprise Only - Endpoint to fetch user details (e.g. https://api.github.com/user). Leave blank otherwise.
default: 'https://api.github.com/user'
order: 5
if:
- { key: 'useEnterprise', eq: true }
refs:
callbackUrl:
title: Authorization Callback URL
hint: The callback endpoint to input on GitHub.
icon: back
value: '{host}/login/{id}/callback'

@ -18,6 +18,7 @@ props:
type: String
title: Client Secret
hint: Application Client Secret
sensitive: true
order: 2
baseUrl:
type: String
@ -25,3 +26,9 @@ props:
hint: For self-managed GitLab instances, define the base URL (e.g. https://gitlab.example.com). Leave default for GitLab.com SaaS (https://gitlab.com).
default: https://gitlab.com
order: 3
refs:
callbackUrl:
title: Authorization Callback URL
hint: The callback endpoint to input on GitLab.
icon: back
value: '{host}/login/{id}/callback'

@ -22,9 +22,16 @@ props:
type: String
title: Client Secret
hint: Application Client Secret
sensitive: true
order: 2
hostedDomain:
type: String
title: Hosted Domain
hint: (optional) Only for G Suite hosted domain. Leave empty otherwise.
order: 3
refs:
callbackUrl:
title: Authorization Callback URL
hint: The callback endpoint to input on Google.
icon: back
value: '{host}/login/{id}/callback'

@ -28,6 +28,7 @@ props:
type: String
title: Client Secret
hint: Application Client Secret
sensitive: true
order: 4
authorizationURL:
type: String
@ -54,4 +55,9 @@ props:
title: Logout Endpoint URL
hint: e.g. https://KEYCLOAK-HOST/auth/realms/YOUR-REALM/protocol/openid-connect/logout
order: 9
refs:
callbackUrl:
title: Authorization Callback URL
hint: The callback endpoint to input on Keycloak.
icon: back
value: '{host}/login/{id}/callback'

@ -29,6 +29,7 @@ props:
type: String
hint: The password of the account used above for binding.
maxWidth: 600
sensitive: true
order: 3
searchBase:
title: Search Base

@ -22,4 +22,5 @@ props:
type: String
title: Client Secret
hint: Application Client Secret
sensitive: true
order: 2

@ -18,6 +18,7 @@ props:
type: String
title: Client Secret
hint: Application Client Secret
sensitive: true
order: 2
authorizationURL:
type: String
@ -71,3 +72,9 @@ props:
title: Pass access token via GET query string to User Info Endpoint
hint: (optional) Pass the access token in an `access_token` parameter attached to the GET query string of the User Info Endpoint URL. Otherwise the access token will be passed in the Authorization header.
order: 11
refs:
callbackUrl:
title: Authorization Callback URL
hint: The callback endpoint to input on the oauth2 server.
icon: back
value: '{host}/login/{id}/callback'

@ -22,6 +22,7 @@ props:
type: String
title: Client Secret
hint: Application Client Secret
sensitive: true
order: 2
authorizationURL:
type: String
@ -55,3 +56,9 @@ props:
title: Logout URL
hint: (optional) Logout URL on the OAuth2 provider where the user will be redirected to complete the logout process.
order: 8
refs:
callbackUrl:
title: Authorization Callback URL
hint: The callback endpoint to input on OIDC server.
icon: back
value: '{host}/login/{id}/callback'

@ -29,6 +29,7 @@ props:
type: String
hint: 40 chars alphanumeric string with a hyphen(s)
maxWidth: 600
sensitive: true
order: 3
idp:
title: Identity Provider ID (idp)
@ -36,3 +37,9 @@ props:
hint: (Optional) - 20 chars alphanumeric string
maxWidth: 400
order: 4
refs:
callbackUrl:
title: Authorization Callback URL
hint: The callback endpoint to input on Okta.
icon: back
value: '{host}/login/{id}/callback'

@ -22,9 +22,16 @@ props:
type: String
title: Client Secret
hint: Application Client Secret
sensitive: true
order: 2
siteURL:
type: String
title: Rocket.chat Site URL
hint: The base URL of your Rocket.chat site (e.g. https://example.rocket.chat)
order: 3
refs:
callbackUrl:
title: Authorization Callback URL
hint: The callback endpoint to input on Rocket.chat.
icon: back
value: '{host}/login/{id}/callback'

@ -22,9 +22,16 @@ props:
type: String
title: Client Secret
hint: Application Client Secret
sensitive: true
order: 2
team:
type: String
title: Team / Workspace ID
hint: Optional - Your unique team (workspace) identifier
order: 3
refs:
callbackUrl:
title: Authorization Callback URL
hint: The callback endpoint to input on Slack.
icon: back
value: '{host}/login/{id}/callback'

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 40 40" width="80px" height="80px"><path fill="#dff0fe" d="M19.5 30.5H22.5V34.5H19.5z"/><path fill="#4788c7" d="M22,31v3h-2v-3H22 M23,30h-4v5h4V30L23,30z"/><path fill="#dff0fe" d="M12.5 30.5H15.5V34.5H12.5z"/><path fill="#4788c7" d="M15,31v3h-2v-3H15 M16,30h-4v5h4V30L16,30z"/><g><path fill="#dff0fe" d="M5.5 30.5H8.5V34.5H5.5z"/><path fill="#4788c7" d="M8,31v3H6v-3H8 M9,30H5v5h4V30L9,30z"/></g><g><path fill="#dff0fe" d="M26.5,34.5v-4H27c4.136,0,7.5-3.364,7.5-7.5s-3.364-7.5-7.5-7.5H10.5v6.335L1.726,13.5L10.5,5.165 V11.5H27c6.341,0,11.5,5.159,11.5,11.5S33.341,34.5,27,34.5H26.5z"/><path fill="#4788c7" d="M10,6.329V11v1h1h16c6.065,0,11,4.935,11,11s-4.935,11-11,11h0v-3h0c4.411,0,8-3.589,8-8 s-3.589-8-8-8H11h-1v1v4.671L2.452,13.5L10,6.329 M11,4L1,13.5L11,23v-7h16c3.86,0,7,3.14,7,7s-3.14,7-7,7h-1v5h1 c6.617,0,12-5.383,12-12s-5.383-12-12-12H11V4L11,4z"/></g></svg>

After

Width:  |  Height:  |  Size: 920 B

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 40 40" width="80px" height="80px"><path fill="#98ccfd" d="M20,38.5C9.799,38.5,1.5,30.201,1.5,20S9.799,1.5,20,1.5S38.5,9.799,38.5,20S30.201,38.5,20,38.5z"/><path fill="#4788c7" d="M20,2c9.925,0,18,8.075,18,18s-8.075,18-18,18S2,29.925,2,20S10.075,2,20,2 M20,1 C9.507,1,1,9.507,1,20s8.507,19,19,19s19-8.507,19-19S30.493,1,20,1L20,1z"/><path fill="#fff" d="M23 20h3v-9.5C26 9.672 25.328 9 24.5 9h0C23.672 9 23 9.672 23 10.5V20zM15 20h3V8.5C18 7.672 17.328 7 16.5 7h0C15.672 7 15 7.672 15 8.5V20zM11 23h3V12.5c0-.828-.672-1.5-1.5-1.5h0c-.828 0-1.5.672-1.5 1.5V23zM19 20h3V7.5C22 6.672 21.328 6 20.5 6h0C19.672 6 19 6.672 19 7.5V20zM21.554 27.567l2.879 2.879 6.971-6.971c.795-.795.795-2.084 0-2.879l0 0c-.795-.795-2.084-.795-2.879 0L21.554 27.567z"/><path fill="#fff" d="M20.678,32H16c-2.761,0-5-2.239-5-5v-9h15v8.678C26,29.617,23.617,32,20.678,32z"/></svg>

After

Width:  |  Height:  |  Size: 905 B

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 40 40" width="80px" height="80px"><path fill="#b6dcfe" d="M1.512,35.5c0.269-5.559,4.982-10,10.738-10h7.5c5.756,0,10.47,4.441,10.738,10H1.512z"/><path fill="#4788c7" d="M19.75,26c5.306,0,9.683,3.954,10.199,9L2.051,35c0.516-5.046,4.893-9,10.199-9H19.75 M19.75,25 h-7.5C6.037,25,1,29.925,1,36v0h30v0C31,29.925,25.963,25,19.75,25L19.75,25z"/><path fill="#fff" d="M16,28.5c-3.075,0-4.297-2.625-4.5-3.123V19.91h9v5.468C20.297,25.875,19.075,28.5,16,28.5z"/><path fill="#4788c7" d="M20,20.41v4.865C19.737,25.865,18.607,28,16,28c-2.611,0-3.741-2.141-4-2.725V20.41H20 M21,19.41 H11v6.062c0,0,1.267,3.529,5,3.529s5-3.529,5-3.529V19.41L21,19.41z"/><path fill="#dff0fe" d="M22.429,17.643c-1.143,0-2.071-0.929-2.071-2.071s0.929-2.071,2.071-2.071 c1.725,0,2.071,0.465,2.071,1.214C24.5,15.966,23.476,17.643,22.429,17.643z M9.571,17.643c-1.047,0-2.071-1.677-2.071-2.928 c0-0.75,0.347-1.214,2.071-1.214c1.143,0,2.071,0.929,2.071,2.071S10.714,17.643,9.571,17.643z"/><path fill="#4788c7" d="M22.429,14C24,14,24,14.363,24,14.714c0,1.044-0.896,2.429-1.571,2.429 c-0.867,0-1.571-0.705-1.571-1.571S21.562,14,22.429,14 M9.571,14c0.867,0,1.571,0.705,1.571,1.571s-0.705,1.571-1.571,1.571 C8.896,17.143,8,15.758,8,14.714C8,14.363,8,14,9.571,14 M22.429,13c-1.42,0-2.571,1.151-2.571,2.571s1.151,2.571,2.571,2.571 S25,16.134,25,14.714C25,13.294,23.849,13,22.429,13L22.429,13z M9.571,13C8.151,13,7,13.294,7,14.714 c0,1.42,1.151,3.429,2.571,3.429s2.571-1.151,2.571-2.571S10.992,13,9.571,13L9.571,13z"/><path fill="#fff" d="M16,24.5c-0.58,0-1.134-0.224-1.56-0.631l-0.09-0.086l-0.12-0.034 c-2.785-0.788-4.73-3.357-4.73-6.25V9.364c0-1.564,1.272-2.836,2.836-2.836h7.328c1.563,0,2.836,1.272,2.836,2.836V17.5 c0,2.892-1.945,5.462-4.73,6.25l-0.12,0.034l-0.09,0.086C17.134,24.276,16.58,24.5,16,24.5z"/><path fill="#4788c7" d="M19.664,7.028C20.952,7.028,22,8.076,22,9.364V17.5c0,2.67-1.796,5.042-4.367,5.768l-0.239,0.068 l-0.18,0.172C16.882,23.825,16.451,24,16,24s-0.882-0.175-1.215-0.492l-0.18-0.172l-0.239-0.068C11.796,22.542,10,20.17,10,17.5 V9.364c0-1.288,1.048-2.336,2.336-2.336H19.664 M19.664,6.028h-7.328C10.494,6.028,9,7.521,9,9.364V17.5 c0,3.205,2.156,5.9,5.095,6.731C14.591,24.705,15.26,25,16,25s1.409-0.295,1.905-0.769C20.844,23.4,23,20.705,23,17.5V9.364 C23,7.521,21.506,6.028,19.664,6.028L19.664,6.028z"/><g><path fill="#b6dcfe" d="M22.5,15.5V12c0-3.483-2.218-4.425-2.312-4.463l-0.343-0.139l-0.234,0.288 C19.589,7.713,17.283,10.5,14,10.5c-0.264,0-0.529-0.009-0.791-0.017c-0.265-0.008-0.525-0.017-0.777-0.017 c-0.878,0-2.932,0-2.932,2.534v2.5H9.285C8.845,14.727,7.5,12.151,7.5,9.576C7.5,4.821,10.995,1.5,16,1.5 c3.095,0,4.543,2.704,4.557,2.731l0.119,0.227l0.253,0.037C22.462,4.714,24.5,5.524,24.5,10c0,2.329-1.338,4.761-1.778,5.5H22.5z"/><path fill="#4788c7" d="M16,2c2.762,0,4.062,2.367,4.113,2.463l0.237,0.454l0.507,0.073C22.321,5.199,24,5.958,24,10 c0,1.395-0.515,2.842-1,3.894V12c0-3.816-2.516-4.883-2.623-4.926l-0.687-0.279l-0.467,0.577C19.201,7.397,17.06,10,14,10 c-0.259,0-0.519-0.008-0.775-0.017c-0.27-0.009-0.536-0.017-0.794-0.017C11.582,9.966,9,9.966,9,13v0.807 c-0.486-1.122-1-2.676-1-4.23C8,5.115,11.29,2,16,2 M16,1c-5.36,0-9,3.667-9,8.576C7,12.828,9,16,9,16h1c0,0,0-2.105,0-3 c0-1.791,1.085-2.034,2.431-2.034C12.932,10.966,13.469,11,14,11c3.573,0,6-3,6-3s2,0.813,2,4c0,0.984,0,4,0,4h1c0,0,2-3.037,2-6 c0-4.161-1.703-5.671-4-6C21,4,19.434,1,16,1L16,1z"/></g><g><path fill="#98ccfd" d="M31,39.5c-4.687,0-8.5-3.813-8.5-8.5s3.813-8.5,8.5-8.5s8.5,3.813,8.5,8.5S35.687,39.5,31,39.5z"/><path fill="#4788c7" d="M31,23c4.411,0,8,3.589,8,8s-3.589,8-8,8s-8-3.589-8-8S26.589,23,31,23 M31,22 c-4.971,0-9,4.029-9,9s4.029,9,9,9s9-4.029,9-9S35.971,22,31,22L31,22z"/></g><path fill="none" stroke="#fff" stroke-miterlimit="10" stroke-width="2" d="M31 36L31 26M26 31L36 31"/></svg>

After

Width:  |  Height:  |  Size: 3.7 KiB

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 40 40" width="80px" height="80px"><path fill="#98ccfd" d="M20,38.5C9.799,38.5,1.5,30.201,1.5,20S9.799,1.5,20,1.5S38.5,9.799,38.5,20S30.201,38.5,20,38.5z"/><path fill="#4788c7" d="M20,2c9.925,0,18,8.075,18,18s-8.075,18-18,18S2,29.925,2,20S10.075,2,20,2 M20,1 C9.507,1,1,9.507,1,20s8.507,19,19,19s19-8.507,19-19S30.493,1,20,1L20,1z"/><path fill="#fff" d="M20.002,31.001L19.84,31C13.866,30.916,9,25.983,9,20.004l0.001-0.154 c0.04-2.825,1.144-5.493,3.109-7.515c0.384-0.396,1.017-0.405,1.414-0.02c0.396,0.385,0.405,1.018,0.02,1.414 c-1.608,1.654-2.511,3.836-2.543,6.145L11,20.004c0,4.891,3.981,8.926,8.874,8.996l0.128,0.001c4.893,0,8.928-3.981,8.997-8.874 L29,19.996c0-2.354-0.904-4.581-2.547-6.27c-0.385-0.396-0.376-1.029,0.02-1.414c0.396-0.385,1.028-0.376,1.414,0.02 C29.895,14.397,31,17.119,31,19.996l-0.001,0.165C30.916,26.136,25.982,31.001,20.002,31.001z"/><path fill="#fff" d="M20,19L20,19c-0.55,0-1-0.45-1-1V8c0-0.55,0.45-1,1-1l0,0c0.55,0,1,0.45,1,1v10 C21,18.55,20.55,19,20,19z"/></svg>

After

Width:  |  Height:  |  Size: 1.0 KiB

@ -27,7 +27,7 @@ q-page.admin-mail
)
q-separator(inset)
.row.q-pa-md.q-col-gutter-md
.col-auto
.col-12.col-lg-auto
q-card.rounded-borders.bg-dark
q-list(
style='min-width: 350px;'
@ -35,8 +35,8 @@ q-page.admin-mail
dark
)
q-item(
v-for='str of combinedActiveStrategies'
:key='str.key'
v-for='str of state.activeStrategies'
:key='str.id'
active-class='bg-primary text-white'
:active='state.selectedStrategy === str.id'
@click='state.selectedStrategy = str.id'
@ -77,20 +77,124 @@ q-page.admin-mail
.col
q-card.q-pb-sm
q-card-section
.text-subtitle1 {{t('admin.storage.contentTypes')}}
.text-body2.text-grey {{ t('admin.storage.contentTypesHint') }}
.text-subtitle1 {{t('admin.auth.info')}}
q-item
blueprint-icon(icon='information')
q-item-section
q-item-label {{t(`admin.auth.infoName`)}}
q-item-label(caption) {{t(`admin.auth.infoNameHint`)}}
q-item-section
q-input(
outlined
v-model='state.strategy.displayName'
dense
hide-bottom-space
:aria-label='t(`admin.auth.infoName`)'
)
q-separator.q-my-sm(inset)
q-item(tag='label')
blueprint-icon(icon='shutdown')
q-item-section
q-item-label {{t(`admin.auth.enabled`)}}
q-item-label(caption) {{t(`admin.auth.enabledHint`)}}
q-item-label.text-deep-orange(v-if='state.strategy.strategy.key === `local`', caption) {{t(`admin.auth.enabledForced`)}}
q-item-section(avatar)
q-toggle(
v-model='state.strategy.isEnabled'
:disable='state.strategy.strategy.key === `local`'
color='primary'
checked-icon='las la-check'
unchecked-icon='las la-times'
:aria-label='t(`admin.auth.enabled`)'
)
q-separator.q-my-sm(inset)
q-item(tag='label')
blueprint-icon(icon='register')
q-item-section
q-item-label {{t(`admin.auth.selfRegistration`)}}
q-item-label(caption) {{t(`admin.auth.selfRegistrationHint`)}}
q-item-section(avatar)
q-toggle(
v-model='state.strategy.selfRegistration'
color='primary'
checked-icon='las la-check'
unchecked-icon='las la-times'
:aria-label='t(`admin.auth.selfRegistration`)'
)
template(v-if='state.strategy.selfRegistration')
q-separator.q-my-sm(inset)
q-item
blueprint-icon(icon='team')
q-item-section
q-item-label {{t(`admin.auth.autoEnrollGroups`)}}
q-item-label(caption) {{t(`admin.auth.autoEnrollGroupsHint`)}}
q-item-section
q-select(
outlined
:options='state.groups'
v-model='state.strategy.autoEnrollGroups'
multiple
map-options
emit-value
option-value='id'
option-label='name'
options-dense
dense
hide-bottom-space
:aria-label='t(`admin.users.groups`)'
:loading='state.loadingGroups'
)
template(v-slot:selected)
.text-caption(v-if='state.strategy.autoEnrollGroups?.length > 1')
i18n-t(keypath='admin.users.groupsSelected')
template(#count)
strong {{ state.strategy.autoEnrollGroups?.length }}
.text-caption(v-else-if='state.strategy.autoEnrollGroups?.length === 1')
i18n-t(keypath='admin.users.groupSelected')
template(#group)
strong {{ selectedGroupName }}
span(v-else)
template(v-slot:option='{ itemProps, opt, selected, toggleOption }')
q-item(
v-bind='itemProps'
)
q-item-section(side)
q-checkbox(
size='sm'
:model-value='selected'
@update:model-value='toggleOption(opt)'
)
q-item-section
q-item-label {{opt.name}}
q-separator.q-my-sm(inset)
q-item
blueprint-icon(icon='private')
q-item-section
q-item-label {{t(`admin.auth.domainsWhitelist`)}}
q-item-label(caption) {{t(`admin.auth.domainsWhitelistHint`)}}
q-item-section
q-input(
outlined
v-model='state.strategy.domainWhitelist'
dense
hide-bottom-space
:aria-label='t(`admin.auth.domainsWhitelist`)'
prefix='/'
suffix='/'
)
//- -----------------------
//- Configuration
//- -----------------------
q-card.q-pb-sm.q-mt-md
q-card-section
.text-subtitle1 {{t('admin.storage.config')}}
.text-subtitle1 {{t('admin.auth.strategyConfiguration')}}
q-banner.q-mt-md(
v-if='!state.strategy.config || Object.keys(state.strategy.config).length < 1'
rounded
:class='$q.dark.isActive ? `bg-negative text-white` : `bg-grey-2 text-grey-7`'
) {{t('admin.storage.noConfigOption')}}
) {{t('admin.auth.noConfigOption')}}
template(
v-for='(cfg, cfgKey, idx) in state.strategy.config'
)
@ -148,228 +252,62 @@ q-page.admin-mail
outlined
v-model='cfg.value'
dense
:type='cfg.multiline ? `textarea` : `input`'
:type='cfg.multiline ? `textarea` : (cfg.sensitive ? `password` : `input`)'
:aria-label='cfg.title'
:disable='cfg.readOnly'
)
.col-auto(v-if='state.selectedStrategy && state.strategy')
//- -----------------------
//- Infobox
//- References
//- -----------------------
q-card.rounded-borders.q-pb-md(style='width: 350px;')
q-card.q-pb-sm.q-mt-md(v-if='strategyRefs.length > 0')
q-card-section
.text-subtitle1 {{state.strategy.strategy.title}}
q-img.q-mt-sm.rounded-borders(
:src='state.strategy.strategy.logo'
fit='contain'
no-spinner
style='height: 100px;'
)
.text-body2.q-mt-md {{state.strategy.strategy.description}}
q-separator.q-mb-sm(inset)
q-item
.text-subtitle1 {{t('admin.auth.configReference')}}
q-item(v-for='strRef of strategyRefs', :key='strRef.key')
blueprint-icon(:icon='strRef.icon', :hue-rotate='-45')
q-item-section
q-item-label.text-grey {{t(`admin.auth.vendor`)}}
q-item-label {{state.strategy.strategy.vendor}}
q-separator.q-my-sm(inset)
q-item
q-item-label {{strRef.title}}
q-item-label(caption) {{strRef.hint}}
q-item-section
q-item-label.text-grey {{t(`admin.auth.vendorWebsite`)}}
q-item-label: a(:href='state.strategy.strategy.website', target='_blank', rel='noreferrer') {{state.strategy.strategy.website}}
q-input(
outlined
v-model='strRef.value'
dense
:aria-label='strRef.title'
readonly
)
//- -----------------------
//- Status
//- Infobox
//- -----------------------
q-card.rounded-borders.q-pb-md.q-mt-md(style='width: 350px;')
q-card-section
.text-subtitle1 {{t(`admin.auth.status`)}}
q-item(tag='label')
q-item-section
q-item-label {{t(`admin.auth.enabled`)}}
q-item-label(caption) {{t(`admin.auth.enabledHint`)}}
q-item-label.text-deep-orange(v-if='state.strategy.strategy.key === `local`', caption) {{t(`admin.auth.enabledForced`)}}
q-item-section(avatar)
q-toggle(
v-model='state.strategy.isEnabled'
:disable='state.strategy.strategy.key === `local`'
color='primary'
checked-icon='las la-check'
unchecked-icon='las la-times'
:aria-label='t(`admin.auth.enabled`)'
q-card.q-mt-md
q-card-section.text-center
q-img.rounded-borders(
:src='state.strategy.strategy.logo'
fit='contain'
no-spinner
style='height: 100px; max-width: 300px;'
)
q-separator.q-my-sm(inset)
q-item
q-item-section
.text-subtitle2.q-mt-sm {{state.strategy.strategy.title}}
.text-caption.q-mt-sm {{state.strategy.strategy.description}}
.text-caption.q-mt-sm: strong {{state.strategy.strategy.vendor}}
.text-caption: a(:href='state.strategy.strategy.website', target='_blank', rel='noreferrer') {{state.strategy.strategy.website}}
.flex.q-mt-md
.text-caption.text-grey ID: {{ state.strategy.id }}
q-space
q-btn.acrylic-btn(
icon='las la-trash-alt'
flat
color='negative'
:disable='state.strategy.strategy.key === `local`'
label='Delete Strategy'
@click='deleteStrategy(state.strategy.id)'
)
//- v-flex(xs12, lg9)
//- v-card.animated.fadeInUp.wait-p2s
//- v-toolbar(color='primary', dense, flat, dark)
//- .subtitle-1 {{strategy.displayName}} #[em ({{strategy.strategy.title}})]
//- v-spacer
//- v-btn(small, outlined, dark, color='white', :disabled='strategy.key === `local`', @click='deleteStrategy()')
//- v-icon(left) mdi-close
//- span {{$t('common.actions.delete')}}
//- v-card-info(color='blue')
//- div
//- span {{strategy.strategy.description}}
//- .caption: a(:href='strategy.strategy.website') {{strategy.strategy.website}}
//- v-spacer
//- .admin-providerlogo
//- img(:src='strategy.strategy.logo', :alt='strategy.strategy.title')
//- v-card-text
//- .row
//- .col-8
//- v-text-field(
//- outlined
//- :label='$t(`admin.auth.displayName`)'
//- v-model='strategy.displayName'
//- prepend-icon='mdi-format-title'
//- :hint='$t(`admin.auth.displayNameHint`)'
//- persistent-hint
//- )
//- .col-4
//- v-switch.mt-1(
//- :label='$t(`admin.auth.strategyIsEnabled`)'
//- v-model='strategy.isEnabled'
//- color='primary'
//- prepend-icon='mdi-power'
//- :hint='$t(`admin.auth.strategyIsEnabledHint`)'
//- persistent-hint
//- inset
//- :disabled='strategy.key === `local`'
//- )
//- template(v-if='strategy.config && Object.keys(strategy.config).length > 0')
//- v-divider
//- .overline.my-5 {{$t('admin.auth.strategyConfiguration')}}
//- .pr-3
//- template(v-for='cfg in strategy.config')
//- v-select.mb-3(
//- v-if='cfg.value.type === "string" && cfg.value.enum'
//- outlined
//- :items='cfg.value.enum'
//- :key='cfg.key'
//- :label='cfg.value.title'
//- v-model='cfg.value.value'
//- prepend-icon='mdi-cog-box'
//- :hint='cfg.value.hint ? cfg.value.hint : ""'
//- persistent-hint
//- :class='cfg.value.hint ? "mb-2" : ""'
//- :style='cfg.value.maxWidth > 0 ? `max-width:` + cfg.value.maxWidth + `px;` : ``'
//- )
//- v-switch.mb-6(
//- v-else-if='cfg.value.type === "boolean"'
//- :key='cfg.key'
//- :label='cfg.value.title'
//- v-model='cfg.value.value'
//- color='primary'
//- prepend-icon='mdi-cog-box'
//- :hint='cfg.value.hint ? cfg.value.hint : ""'
//- persistent-hint
//- inset
//- )
//- v-textarea.mb-3(
//- v-else-if='cfg.value.type === "string" && cfg.value.multiline'
//- outlined
//- :key='cfg.key'
//- :label='cfg.value.title'
//- v-model='cfg.value.value'
//- prepend-icon='mdi-cog-box'
//- :hint='cfg.value.hint ? cfg.value.hint : ""'
//- persistent-hint
//- :class='cfg.value.hint ? "mb-2" : ""'
//- )
//- v-text-field.mb-3(
//- v-else
//- outlined
//- :key='cfg.key'
//- :label='cfg.value.title'
//- v-model='cfg.value.value'
//- prepend-icon='mdi-cog-box'
//- :hint='cfg.value.hint ? cfg.value.hint : ""'
//- persistent-hint
//- :class='cfg.value.hint ? "mb-2" : ""'
//- :style='cfg.value.maxWidth > 0 ? `max-width:` + cfg.value.maxWidth + `px;` : ``'
//- )
//- v-divider
//- .overline.my-5 {{$t('admin.auth.registration')}}
//- .pr-3
//- v-switch.ml-3(
//- v-model='strategy.selfRegistration'
//- :label='$t(`admin.auth.selfRegistration`)'
//- color='primary'
//- :hint='$t(`admin.auth.selfRegistrationHint`)'
//- persistent-hint
//- inset
//- )
//- v-combobox.ml-3.mt-5(
//- :label='$t(`admin.auth.domainsWhitelist`)'
//- v-model='strategy.domainWhitelist'
//- prepend-icon='mdi-email-check-outline'
//- outlined
//- :disabled='!strategy.selfRegistration'
//- :hint='$t(`admin.auth.domainsWhitelistHint`)'
//- persistent-hint
//- small-chips
//- deletable-chips
//- clearable
//- multiple
//- chips
//- )
//- v-autocomplete.mt-3.ml-3(
//- outlined
//- :disabled='!strategy.selfRegistration'
//- :items='groups'
//- item-text='name'
//- item-value='id'
//- :label='$t(`admin.auth.autoEnrollGroups`)'
//- v-model='strategy.autoEnrollGroups'
//- prepend-icon='mdi-account-group'
//- :hint='$t(`admin.auth.autoEnrollGroupsHint`)'
//- small-chips
//- persistent-hint
//- deletable-chips
//- clearable
//- multiple
//- chips
//- )
//- v-card.mt-4.wiki-form.animated.fadeInUp.wait-p4s(v-if='selectedStrategy !== `local`')
//- v-toolbar(color='primary', dense, flat, dark)
//- .subtitle-1 {{$t('admin.auth.configReference')}}
//- v-card-text
//- .body-2 {{$t('admin.auth.configReferenceSubtitle')}}
//- v-alert.mt-3.radius-7(v-if='host.length < 8', color='red', outlined, :value='true', icon='mdi-alert')
//- i18next(path='admin.auth.siteUrlNotSetup', tag='span')
//- strong(place='siteUrl') {{$t('admin.general.siteUrl')}}
//- strong(place='general') {{$t('admin.general.title')}}
//- .pa-3.mt-3.radius-7.grey(v-else, :class='$vuetify.theme.dark ? `darken-3-d5` : `lighten-3`')
//- .body-2: strong {{$t('admin.auth.allowedWebOrigins')}}
//- .body-2 {{host}}
//- v-divider.my-3
//- .body-2: strong {{$t('admin.auth.callbackUrl')}}
//- .body-2 {{host}}/login/{{strategy.key}}/callback
//- v-divider.my-3
//- .body-2: strong {{$t('admin.auth.loginUrl')}}
//- .body-2 {{host}}/login
//- v-divider.my-3
//- .body-2: strong {{$t('admin.auth.logoutUrl')}}
//- .body-2 {{host}}
//- v-divider.my-3
//- .body-2: strong {{$t('admin.auth.tokenEndpointAuthMethod')}}
//- .body-2 HTTP-POST
</template>
<script setup>
import gql from 'graphql-tag'
import { find, reject, transform } from 'lodash-es'
import { cloneDeep, find, reject, transform } from 'lodash-es'
import { v4 as uuid } from 'uuid'
import { useI18n } from 'vue-i18n'
@ -402,6 +340,7 @@ useMeta({
const state = reactive({
loading: 0,
loadingGroups: true,
groups: [],
strategies: [],
activeStrategies: [],
@ -417,25 +356,26 @@ const state = reactive({
const availableStrategies = computed(() => {
return state.strategies.filter(str => str.key !== 'local')
})
const combinedActiveStrategies = computed(() => {
return state.activeStrategies.map(str => ({
...str,
strategy: find(state.strategies, ['key', str.strategy?.key]) || {}
}))
const selectedGroupName = computed(() => {
return state.groups.filter(g => g.id === state.strategy?.autoEnrollGroups?.[0])[0]?.name
})
const strategyRefs = computed(() => {
if (!state.selectedStrategy) { return [] }
const str = find(state.strategies, ['key', state.strategy?.strategy.key])
if (!str || !str.refs) { return [] }
return Object.entries(str.refs).map(([k, v]) => {
return {
...v,
key: k,
value: v.value.replaceAll('{host}', window.location.origin).replaceAll('{id}', state.selectedStrategy)
}
}) ?? []
})
// WATCHERS
watch(() => state.selectedStrategy, (newValue, oldValue) => {
const str = find(combinedActiveStrategies.value, ['id', newValue]) || {}
str.config = transform(str.strategy.props, (cfg, v, k) => {
cfg[k] = {
...v,
value: str.config[k]
}
}, {})
state.strategy = str
state.strategy = find(state.activeStrategies, ['id', newValue]) || {}
})
watch(() => state.activeStrategies, (newValue, oldValue) => {
state.selectedStrategy = newValue[0]?.id
@ -443,6 +383,25 @@ watch(() => state.activeStrategies, (newValue, oldValue) => {
// METHODS
async function loadGroups () {
state.loading++
state.loadingGroups = true
const resp = await APOLLO_CLIENT.query({
query: gql`
query getGroupsForAdminAuth {
groups {
id
name
}
}
`,
fetchPolicy: 'network-only'
})
state.groups = cloneDeep(resp?.data?.groups?.filter(g => g.id !== '10000000-0000-4000-8000-000000000001') ?? [])
state.loadingGroups = false
state.loading--
}
async function load () {
state.loading++
$q.loading.show()
@ -452,6 +411,7 @@ async function load () {
authStrategies {
key
props
refs
title
description
isAvailable
@ -480,7 +440,33 @@ async function load () {
fetchPolicy: 'network-only'
})
state.strategies = resp?.data?.authStrategies || []
state.activeStrategies = resp?.data?.authActiveStrategies || []
state.activeStrategies = (cloneDeep(resp?.data?.authActiveStrategies) || []).map(a => {
const str = cloneDeep(find(state.strategies, ['key', a.strategy.key])) || {}
a.strategy = str
a.config = transform(str.props, (r, v, k) => {
r[k] = {
...v,
value: a.config?.[k],
...v.enum && {
enum: v.enum.map(o => {
if (o.indexOf('|') > 0) {
const oParsed = o.split('|')
return {
value: oParsed[0],
label: oParsed[1]
}
} else {
return {
value: o,
label: o
}
}
})
}
}
}, {})
return a
})
$q.loading.hide()
state.loading--
}
@ -493,15 +479,32 @@ function configIfCheck (ifs) {
function addStrategy (str) {
const newStr = {
id: uuid(),
strategy: {
key: str.key
},
config: transform(str.props, (cfg, v, k) => {
cfg[k] = v.default
strategy: str,
config: transform(str.props, (r, v, k) => {
r[k] = {
...v,
value: v.default,
...v.enum && {
enum: v.enum.map(o => {
if (o.indexOf('|') > 0) {
const oParsed = o.split('|')
return {
value: oParsed[0],
label: oParsed[1]
}
} else {
return {
value: o,
label: o
}
}
})
}
}
}, {}),
isEnabled: false,
isEnabled: true,
displayName: str.title,
selfRegistration: false,
selfRegistration: true,
domainWhitelist: [],
autoEnrollGroups: []
}
@ -660,5 +663,6 @@ async function save () {
onMounted(() => {
load()
loadGroups()
})
</script>

Loading…
Cancel
Save