Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
W
wiki-js
Project
Project
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
1
Issues
1
List
Board
Labels
Milestones
Merge Requests
1
Merge Requests
1
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Registry
Registry
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
Jacklull
wiki-js
Commits
097833d7
Unverified
Commit
097833d7
authored
Aug 29, 2022
by
Nicolas Giard
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
feat: login page + auth panel + various improvements
parent
82ebac2d
Hide whitespace changes
Inline
Side-by-side
Showing
24 changed files
with
1236 additions
and
434 deletions
+1236
-434
common.js
server/controllers/common.js
+3
-4
auth.js
server/core/auth.js
+6
-7
3.0.0.js
server/db/migrations/3.0.0.js
+15
-4
authentication.js
server/graph/resolvers/authentication.js
+17
-25
authentication.graphql
server/graph/schemas/authentication.graphql
+11
-4
user.graphql
server/graph/schemas/user.graphql
+0
-5
error.js
server/helpers/error.js
+60
-120
graph.js
server/helpers/graph.js
+1
-1
authentication.js
server/models/authentication.js
+3
-2
pages.js
server/models/pages.js
+2
-12
userKeys.js
server/models/userKeys.js
+7
-3
users.js
server/models/users.js
+19
-58
authentication.js
server/modules/authentication/local/authentication.js
+12
-8
base.pug
server/views/base.pug
+7
-7
error.pug
server/views/error.pug
+77
-12
package.json
ux/package.json
+11
-10
logo-wikijs-full.svg
ux/public/_assets/logo-wikijs-full.svg
+159
-0
quasar.config.js
ux/quasar.config.js
+2
-1
AuthLoginPanel.vue
ux/src/components/AuthLoginPanel.vue
+788
-0
app.scss
ux/src/css/app.scss
+7
-1
en.json
ux/src/i18n/locales/en.json
+24
-21
AdminLocale.vue
ux/src/pages/AdminLocale.vue
+1
-1
Login.vue
ux/src/pages/Login.vue
+4
-128
yarn.lock
ux/yarn.lock
+0
-0
No files found.
server/controllers/common.js
View file @
097833d7
...
...
@@ -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
}
`
)
...
...
server/core/auth.js
View file @
097833d7
...
...
@@ -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
.
get
Global
Permissions
()
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
],
get
Global
Permissions
()
{
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
.
get
Global
Permissions
()
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
.
get
Global
Permissions
()
const
userPermissions
=
user
.
permissions
?
user
.
permissions
:
user
.
getPermissions
()
// Check Inclusion Permissions
if
(
_
.
intersection
(
userPermissions
,
includePermissions
).
length
<
1
)
{
...
...
server/db/migrations/3.0.0.js
View file @
097833d7
...
...
@@ -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
:
[],
...
...
server/graph/resolvers/authentication.js
View file @
097833d7
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
loginC
hangePassword
(
obj
,
args
,
context
)
{
async
c
hangePassword
(
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
{
...
...
server/graph/schemas/authentication.graphql
View file @
097833d7
...
...
@@ -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
(
...
...
server/graph/schemas/user.graphql
View file @
097833d7
...
...
@@ -73,11 +73,6 @@ extend type Mutation {
dateFormat
:
String
!
appearance
:
String
!
):
UserTokenResponse
changePassword
(
current
:
String
!
new
:
String
!
):
UserTokenResponse
}
# -----------------------------------------------
...
...
server/helpers/error.js
View file @
097833d7
...
...
@@ -2,243 +2,183 @@ const CustomError = require('custom-error-instance')
module
.
exports
=
{
AssetDeleteForbidden
:
CustomError
(
'AssetDeleteForbidden'
,
{
message
:
'You are not authorized to delete this asset.'
,
code
:
2003
message
:
'You are not authorized to delete this asset.'
}),
AssetFolderExists
:
CustomError
(
'AssetFolderExists'
,
{
message
:
'An asset folder with the same name already exists.'
,
code
:
2002
message
:
'An asset folder with the same name already exists.'
}),
AssetGenericError
:
CustomError
(
'AssetGenericError'
,
{
message
:
'An unexpected error occured during asset operation.'
,
code
:
2001
message
:
'An unexpected error occured during asset operation.'
}),
AssetInvalid
:
CustomError
(
'AssetInvalid'
,
{
message
:
'This asset does not exist or is invalid.'
,
code
:
2004
message
:
'This asset does not exist or is invalid.'
}),
AssetRenameCollision
:
CustomError
(
'AssetRenameCollision'
,
{
message
:
'An asset with the same filename in the same folder already exists.'
,
code
:
2005
message
:
'An asset with the same filename in the same folder already exists.'
}),
AssetRenameForbidden
:
CustomError
(
'AssetRenameForbidden'
,
{
message
:
'You are not authorized to rename this asset.'
,
code
:
2006
message
:
'You are not authorized to rename this asset.'
}),
AssetRenameInvalid
:
CustomError
(
'AssetRenameInvalid'
,
{
message
:
'The new asset filename is invalid.'
,
code
:
2007
message
:
'The new asset filename is invalid.'
}),
AssetRenameInvalidExt
:
CustomError
(
'AssetRenameInvalidExt'
,
{
message
:
'The file extension cannot be changed on an existing asset.'
,
code
:
2008
message
:
'The file extension cannot be changed on an existing asset.'
}),
AssetRenameTargetForbidden
:
CustomError
(
'AssetRenameTargetForbidden'
,
{
message
:
'You are not authorized to rename this asset to the requested name.'
,
code
:
2009
message
:
'You are not authorized to rename this asset to the requested name.'
}),
AuthAccountBanned
:
CustomError
(
'AuthAccountBanned'
,
{
message
:
'Your account has been disabled.'
,
code
:
1013
message
:
'Your account has been disabled.'
}),
AuthAccountAlreadyExists
:
CustomError
(
'AuthAccountAlreadyExists'
,
{
message
:
'An account already exists using this email address.'
,
code
:
1004
message
:
'An account already exists using this email address.'
}),
AuthAccountNotVerified
:
CustomError
(
'AuthAccountNotVerified'
,
{
message
:
'You must verify your account before your can login.'
,
code
:
1014
message
:
'You must verify your account before your can login.'
}),
AuthGenericError
:
CustomError
(
'AuthGenericError'
,
{
message
:
'An unexpected error occured during login.'
,
code
:
1001
message
:
'An unexpected error occured during login.'
}),
AuthLoginFailed
:
CustomError
(
'AuthLoginFailed'
,
{
message
:
'Invalid email / username or password.'
,
code
:
1002
message
:
'Invalid email / username or password.'
}),
AuthPasswordInvalid
:
CustomError
(
'AuthPasswordInvalid'
,
{
message
:
'Password is incorrect.'
,
code
:
1020
message
:
'Password is incorrect.'
}),
AuthProviderInvalid
:
CustomError
(
'AuthProviderInvalid'
,
{
message
:
'Invalid authentication provider.'
,
code
:
1003
message
:
'Invalid authentication provider.'
}),
AuthRegistrationDisabled
:
CustomError
(
'AuthRegistrationDisabled'
,
{
message
:
'Registration is disabled. Contact your system administrator.'
,
code
:
1010
message
:
'Registration is disabled. Contact your system administrator.'
}),
AuthRegistrationDomainUnauthorized
:
CustomError
(
'AuthRegistrationDomainUnauthorized'
,
{
message
:
'You are not authorized to register. Your domain is not whitelisted.'
,
code
:
1011
message
:
'You are not authorized to register. Your domain is not whitelisted.'
}),
AuthRequired
:
CustomError
(
'AuthRequired'
,
{
message
:
'You must be authenticated to access this resource.'
,
code
:
1019
message
:
'You must be authenticated to access this resource.'
}),
AuthTFAFailed
:
CustomError
(
'AuthTFAFailed'
,
{
message
:
'Incorrect TFA Security Code.'
,
code
:
1005
message
:
'Incorrect TFA Security Code.'
}),
AuthTFAInvalid
:
CustomError
(
'AuthTFAInvalid'
,
{
message
:
'Invalid TFA Security Code or Login Token.'
,
code
:
1006
message
:
'Invalid TFA Security Code or Login Token.'
}),
AuthValidationTokenInvalid
:
CustomError
(
'AuthValidationTokenInvalid'
,
{
message
:
'Invalid validation token.'
,
code
:
1015
message
:
'Invalid validation token.'
}),
BruteInstanceIsInvalid
:
CustomError
(
'BruteInstanceIsInvalid'
,
{
message
:
'Invalid Brute Force Instance.'
,
code
:
1007
message
:
'Invalid Brute Force Instance.'
}),
BruteTooManyAttempts
:
CustomError
(
'BruteTooManyAttempts'
,
{
message
:
'Too many attempts! Try again later.'
,
code
:
1008
message
:
'Too many attempts! Try again later.'
}),
CommentContentMissing
:
CustomError
(
'CommentContentMissing'
,
{
message
:
'Comment content is missing or too short.'
,
code
:
8003
message
:
'Comment content is missing or too short.'
}),
CommentGenericError
:
CustomError
(
'CommentGenericError'
,
{
message
:
'An unexpected error occured.'
,
code
:
8001
message
:
'An unexpected error occured.'
}),
CommentManageForbidden
:
CustomError
(
'CommentManageForbidden'
,
{
message
:
'You are not authorized to manage comments on this page.'
,
code
:
8004
message
:
'You are not authorized to manage comments on this page.'
}),
CommentNotFound
:
CustomError
(
'CommentNotFound'
,
{
message
:
'This comment does not exist.'
,
code
:
8005
message
:
'This comment does not exist.'
}),
CommentPostForbidden
:
CustomError
(
'CommentPostForbidden'
,
{
message
:
'You are not authorized to post a comment on this page.'
,
code
:
8002
message
:
'You are not authorized to post a comment on this page.'
}),
CommentViewForbidden
:
CustomError
(
'CommentViewForbidden'
,
{
message
:
'You are not authorized to view comments for this page.'
,
code
:
8006
message
:
'You are not authorized to view comments for this page.'
}),
InputInvalid
:
CustomError
(
'InputInvalid'
,
{
message
:
'Input data is invalid.'
,
code
:
1012
message
:
'Input data is invalid.'
}),
LocaleGenericError
:
CustomError
(
'LocaleGenericError'
,
{
message
:
'An unexpected error occured during locale operation.'
,
code
:
5001
message
:
'An unexpected error occured during locale operation.'
}),
LocaleInvalidNamespace
:
CustomError
(
'LocaleInvalidNamespace'
,
{
message
:
'Invalid locale or namespace.'
,
code
:
5002
message
:
'Invalid locale or namespace.'
}),
MailGenericError
:
CustomError
(
'MailGenericError'
,
{
message
:
'An unexpected error occured during mail operation.'
,
code
:
3001
message
:
'An unexpected error occured during mail operation.'
}),
MailInvalidRecipient
:
CustomError
(
'MailInvalidRecipient'
,
{
message
:
'The recipient email address is invalid.'
,
code
:
3004
message
:
'The recipient email address is invalid.'
}),
MailNotConfigured
:
CustomError
(
'MailNotConfigured'
,
{
message
:
'The mail configuration is incomplete or invalid.'
,
code
:
3002
message
:
'The mail configuration is incomplete or invalid.'
}),
MailTemplateFailed
:
CustomError
(
'MailTemplateFailed'
,
{
message
:
'Mail template failed to load.'
,
code
:
3003
message
:
'Mail template failed to load.'
}),
PageCreateForbidden
:
CustomError
(
'PageCreateForbidden'
,
{
message
:
'You are not authorized to create this page.'
,
code
:
6008
message
:
'You are not authorized to create this page.'
}),
PageDeleteForbidden
:
CustomError
(
'PageDeleteForbidden'
,
{
message
:
'You are not authorized to delete this page.'
,
code
:
6010
message
:
'You are not authorized to delete this page.'
}),
PageGenericError
:
CustomError
(
'PageGenericError'
,
{
message
:
'An unexpected error occured during a page operation.'
,
code
:
6001
message
:
'An unexpected error occured during a page operation.'
}),
PageDuplicateCreate
:
CustomError
(
'PageDuplicateCreate'
,
{
message
:
'Cannot create this page because an entry already exists at the same path.'
,
code
:
6002
message
:
'Cannot create this page because an entry already exists at the same path.'
}),
PageEmptyContent
:
CustomError
(
'PageEmptyContent'
,
{
message
:
'Page content cannot be empty.'
,
code
:
6004
message
:
'Page content cannot be empty.'
}),
PageHistoryForbidden
:
CustomError
(
'PageHistoryForbidden'
,
{
message
:
'You are not authorized to view the history of this page.'
,
code
:
6012
message
:
'You are not authorized to view the history of this page.'
}),
PageIllegalPath
:
CustomError
(
'PageIllegalPath'
,
{
message
:
'Page path cannot contains illegal characters.'
,
code
:
6005
message
:
'Page path cannot contains illegal characters.'
}),
PageMoveForbidden
:
CustomError
(
'PageMoveForbidden'
,
{
message
:
'You are not authorized to move this page.'
,
code
:
6007
message
:
'You are not authorized to move this page.'
}),
PageNotFound
:
CustomError
(
'PageNotFound'
,
{
message
:
'This page does not exist.'
,
code
:
6003
message
:
'This page does not exist.'
}),
PagePathCollision
:
CustomError
(
'PagePathCollision'
,
{
message
:
'Destination page path already exists.'
,
code
:
6006
message
:
'Destination page path already exists.'
}),
PageRestoreForbidden
:
CustomError
(
'PageRestoreForbidden'
,
{
message
:
'You are not authorized to restore this page version.'
,
code
:
6011
message
:
'You are not authorized to restore this page version.'
}),
PageUpdateForbidden
:
CustomError
(
'PageUpdateForbidden'
,
{
message
:
'You are not authorized to update this page.'
,
code
:
6009
message
:
'You are not authorized to update this page.'
}),
PageViewForbidden
:
CustomError
(
'PageViewForbidden'
,
{
message
:
'You are not authorized to view this page.'
,
code
:
6013
message
:
'You are not authorized to view this page.'
}),
SearchActivationFailed
:
CustomError
(
'SearchActivationFailed'
,
{
message
:
'Search Engine activation failed.'
,
code
:
4002
message
:
'Search Engine activation failed.'
}),
SearchGenericError
:
CustomError
(
'SearchGenericError'
,
{
message
:
'An unexpected error occured during search operation.'
,
code
:
4001
message
:
'An unexpected error occured during search operation.'
}),
SystemGenericError
:
CustomError
(
'SystemGenericError'
,
{
message
:
'An unexpected error occured.'
,
code
:
7001
message
:
'An unexpected error occured.'
}),
SystemSSLDisabled
:
CustomError
(
'SystemSSLDisabled'
,
{
message
:
'SSL is not enabled.'
,
code
:
7002
message
:
'SSL is not enabled.'
}),
SystemSSLLEUnavailable
:
CustomError
(
'SystemSSLLEUnavailable'
,
{
message
:
'Let
\'
s Encrypt is not initialized.'
,
code
:
7004
message
:
'Let
\'
s Encrypt is not initialized.'
}),
SystemSSLRenewInvalidProvider
:
CustomError
(
'SystemSSLRenewInvalidProvider'
,
{
message
:
'Current provider does not support SSL certificate renewal.'
,
code
:
7003
message
:
'Current provider does not support SSL certificate renewal.'
}),
UserCreationFailed
:
CustomError
(
'UserCreationFailed'
,
{
message
:
'An unexpected error occured during user creation.'
,
code
:
1009
message
:
'An unexpected error occured during user creation.'
}),
UserDeleteForeignConstraint
:
CustomError
(
'UserDeleteForeignConstraint'
,
{
message
:
'Cannot delete user because of content relational constraints.'
,
code
:
1017
message
:
'Cannot delete user because of content relational constraints.'
}),
UserDeleteProtected
:
CustomError
(
'UserDeleteProtected'
,
{
message
:
'Cannot delete a protected system account.'
,
code
:
1018
message
:
'Cannot delete a protected system account.'
}),
UserNotFound
:
CustomError
(
'UserNotFound'
,
{
message
:
'This user does not exist.'
,
code
:
1016
message
:
'This user does not exist.'
})
}
server/helpers/graph.js
View file @
097833d7
...
...
@@ -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
}
}
server/models/authentication.js
View file @
097833d7
...
...
@@ -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'
,
[]),
...
...
server/models/pages.js
View file @
097833d7
...
...
@@ -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'
)
{
...
...
server/models/userKeys.js
View file @
097833d7
...
...
@@ -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
()
}
...
...
server/models/users.js
View file @
097833d7
/* 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
)
}
get
Global
Permissions
()
{
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
&&
use
r
.
tfaSecret
)
{
if
(
authStr
.
tfaRequired
&&
authSt
r
.
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
&&
!
use
r
.
tfaSecret
))
{
}
else
if
(
WIKI
.
config
.
auth
.
enforce2FA
||
(
authStr
.
tfaIsActive
&&
!
authSt
r
.
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
&&
use
r
.
mustChangePwd
)
{
if
(
!
skipChangePwd
&&
authSt
r
.
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
.
sessionS
ecret
key
:
WIKI
.
config
.
auth
.
certs
.
private
,
passphrase
:
WIKI
.
config
.
auth
.
s
ecret
},
{
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
.
get
Global
Permissions
()
user
.
permissions
=
user
.
getPermissions
()
return
user
}
...
...
server/modules/authentication/local/authentication.js
View file @
097833d7
/* 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
)
...
...
server/views/base.pug
View file @
097833d7
...
...
@@ -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.
...
...
server/views/error.pug
View file @
097833d7
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}
ux/package.json
View file @
097833d7
...
...
@@ -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.18
2
"
,
"@intlify/vite-plugin-vue-i18n"
:
"
6
.0.1"
,
"@quasar/app-vite"
:
"1.0.
6
"
,
"@types/lodash"
:
"4.14.18
4
"
,
"browserlist"
:
"latest"
,
"eslint"
:
"8.2
0
.0"
,
"eslint"
:
"8.2
2
.0"
,
"eslint-config-standard"
:
"17.0.0"
,
"eslint-plugin-import"
:
"2.26.0"
,
"eslint-plugin-n"
:
"15.2.4"
,
...
...
ux/public/_assets/logo-wikijs-full.svg
0 → 100644
View file @
097833d7
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg
width=
"100%"
height=
"100%"
viewBox=
"0 0 398 98"
version=
"1.1"
xmlns=
"http://www.w3.org/2000/svg"
xmlns:xlink=
"http://www.w3.org/1999/xlink"
xml:space=
"preserve"
xmlns:serif=
"http://www.serif.com/"
style=
"fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;"
>
<path
d=
"M296.485,61.384C295.581,61.761 294.801,62.289 294.147,62.968C293.495,63.646 292.979,64.425 292.602,65.304C292.227,66.184 292.037,67.126 292.037,68.131C292.037,70.192 292.74,71.938 294.147,73.37C295.554,74.803 297.288,75.518 299.349,75.518C301.41,75.518 303.157,74.803 304.59,73.37C306.021,71.938 306.737,70.192 306.737,68.131C306.737,67.126 306.549,66.184 306.172,65.304C305.795,64.425 305.268,63.646 304.59,62.968C303.911,62.289 303.119,61.761 302.214,61.384C301.309,61.007 300.355,60.818 299.349,60.818C298.344,60.818 297.39,61.007 296.485,61.384Z"
style=
"fill:url(#_Linear1);fill-rule:nonzero;"
/>
<path
d=
"M312.466,20.338C311.259,21.544 310.656,23.026 310.656,24.785C310.656,26.545 311.259,28.04 312.466,29.271C313.671,30.502 315.153,31.117 316.914,31.117C318.672,31.117 320.166,30.502 321.399,29.271C322.63,28.04 323.245,26.545 323.245,24.785C323.245,23.026 322.63,21.544 321.399,20.338C320.166,19.132 318.672,18.528 316.914,18.528C315.153,18.528 313.671,19.132 312.466,20.338ZM312.804,34.999C311.674,36.13 311.109,37.501 311.109,39.108L311.109,75.518C311.109,76.924 310.907,78.018 310.506,78.798C310.103,79.576 309.612,80.23 309.036,80.758C308.457,81.286 307.83,81.712 307.152,82.039C306.473,82.365 305.844,82.767 305.266,83.245C304.689,83.722 304.199,84.325 303.796,85.055C303.395,85.783 303.193,86.776 303.193,88.033C303.193,89.64 303.785,90.859 304.965,91.689C306.147,92.517 307.566,92.932 309.225,92.932C311.082,92.932 312.843,92.567 314.502,91.839C316.158,91.11 317.603,90.03 318.836,88.598C320.067,87.165 321.033,85.356 321.738,83.17C322.442,80.984 322.793,78.433 322.793,75.518L322.793,39.108C322.793,38.305 322.642,37.551 322.341,36.847C322.04,36.144 321.626,35.527 321.097,34.999C320.569,34.472 319.94,34.057 319.212,33.756C318.483,33.454 317.717,33.304 316.914,33.304C315.303,33.304 313.935,33.869 312.804,34.999Z"
style=
"fill:url(#_Linear2);fill-rule:nonzero;"
/>
<path
d=
"M329.615,60.253C328.584,61.234 328.07,62.729 328.07,64.738C328.07,66.397 328.659,67.905 329.841,69.262C331.021,70.618 332.492,71.8 334.251,72.805C336.01,73.81 337.894,74.589 339.905,75.141C341.915,75.694 343.749,75.97 345.408,75.97C347.718,75.97 349.893,75.67 351.929,75.066C353.964,74.463 355.76,73.572 357.318,72.39C357.9,71.948 358.422,71.45 358.914,70.924C358.142,66.628 354.984,62.484 347.473,59.489C347.967,59.709 348.428,59.937 348.837,60.178C349.818,60.756 350.308,61.523 350.308,62.477C350.308,63.483 349.829,64.198 348.876,64.625C347.92,65.053 346.94,65.266 345.936,65.266C344.779,65.266 343.711,64.927 342.732,64.249C341.751,63.571 340.797,62.83 339.867,62.025C338.937,61.222 337.969,60.479 336.965,59.801C335.959,59.122 334.904,58.783 333.799,58.783C332.039,58.783 330.644,59.273 329.615,60.253Z"
style=
"fill:url(#_Linear3);fill-rule:nonzero;"
/>
<g
opacity=
"0.1"
>
<path
d=
"M358.914,70.921C358.142,66.624 354.985,62.48 347.473,59.486C347.967,59.705 348.428,59.933 348.838,60.175C349.068,60.31 349.265,60.458 349.441,60.615C349.441,60.614 349.441,60.614 349.44,60.613C349.441,60.614 349.441,60.615 349.442,60.616C349.465,60.636 349.487,60.656 349.51,60.677C354.092,64.728 356.551,68.598 357.532,72.206C358.027,71.809 358.486,71.379 358.914,70.921"
style=
"fill-rule:nonzero;"
/>
</g>
<path
d=
"M338.322,33.831C336.562,34.485 335.091,35.389 333.912,36.545C332.73,37.702 331.863,39.059 331.311,40.615C330.757,42.174 330.482,43.883 330.482,45.742C330.482,48.105 330.972,50.014 331.952,51.471C332.932,52.93 334.15,54.123 335.608,55.052C337.065,55.983 338.661,56.699 340.395,57.2C342.128,57.703 343.724,58.18 345.182,58.633C346.029,58.896 346.787,59.183 347.473,59.489C354.984,62.484 358.142,66.628 358.914,70.924C359.736,70.044 360.446,69.059 361.012,67.942C361.917,66.159 362.369,64.06 362.369,61.648C362.369,59.135 361.879,57.113 360.898,55.579C359.919,54.046 358.701,52.828 357.243,51.923C355.785,51.019 354.201,50.329 352.494,49.85C350.784,49.373 349.201,48.921 347.745,48.493C346.286,48.067 345.068,47.564 344.089,46.986C343.108,46.409 342.618,45.541 342.618,44.385C342.618,43.833 342.807,43.405 343.185,43.104C343.56,42.802 344.024,42.652 344.579,42.652C345.533,42.652 346.35,42.84 347.028,43.216C347.707,43.594 348.396,43.996 349.101,44.423C349.804,44.851 350.571,45.251 351.401,45.628C352.23,46.006 353.248,46.195 354.453,46.195C355.86,46.195 357.054,45.692 358.035,44.686C358.982,43.715 359.46,42.576 359.492,41.275L353.412,41.275C352.118,41.275 351.06,40.216 351.06,38.924L351.06,33.601C350.921,33.566 350.79,33.526 350.646,33.493C348.811,33.066 346.79,32.851 344.579,32.851C342.166,32.851 340.08,33.178 338.322,33.831Z"
style=
"fill:url(#_Linear4);fill-rule:nonzero;"
/>
<path
d=
"M364.105,34.43C364.105,35.566 365.034,36.495 366.17,36.495L372.392,36.495C373.528,36.495 374.457,35.566 374.457,34.43L374.457,28.208C374.457,27.072 373.528,26.142 372.392,26.142L366.17,26.142C365.034,26.142 364.105,27.072 364.105,28.208L364.105,34.43Z"
style=
"fill:rgb(26,141,219);fill-rule:nonzero;"
/>
<path
d=
"M381.136,36.6C381.136,37.737 382.066,38.666 383.202,38.666L386.418,38.666C387.554,38.666 388.483,37.737 388.483,36.6L388.483,33.384C388.483,32.248 387.554,31.319 386.418,31.319L383.202,31.319C382.066,31.319 381.136,32.248 381.136,33.384L381.136,36.6Z"
style=
"fill:rgb(23,163,243);fill-rule:nonzero;"
/>
<path
d=
"M374.457,53.131C374.457,54.267 375.386,55.197 376.523,55.197L381.074,55.197C382.211,55.197 383.14,54.267 383.14,53.131L383.14,48.579C383.14,47.443 382.211,46.514 381.074,46.514L376.523,46.514C375.386,46.514 374.457,47.443 374.457,48.579L374.457,53.131Z"
style=
"fill:rgb(21,147,219);fill-rule:nonzero;"
/>
<path
d=
"M392.491,44.615C392.491,45.752 393.42,46.681 394.556,46.681L395.435,46.681C396.571,46.681 397.5,45.752 397.5,44.615L397.5,43.736C397.5,42.601 396.571,41.672 395.435,41.672L394.556,41.672C393.42,41.672 392.491,42.601 392.491,43.736L392.491,44.615Z"
style=
"fill:rgb(5,186,243);fill-rule:nonzero;"
/>
<path
d=
"M65.686,50.653C65.686,50.653 86.498,51.403 96.061,59.653C105.623,67.903 112.186,83.653 109.373,97.341C103.748,96.028 79.748,91.903 69.998,79.903C60.248,67.903 60.623,59.466 61.748,54.403C62.873,49.341 65.686,50.653 65.686,50.653"
style=
"fill:rgb(28,156,243);fill-rule:nonzero;"
/>
<path
d=
"M62.038,54.454C61.563,56.593 61.05,59.952 62.084,64.407C63.209,69.255 65.885,74.475 70.282,79.717C80.342,91.71 108.673,96.574 109.017,96.649C110.094,90.415 109.367,83.92 106.842,77.133C104.348,70.429 100.342,64.465 95.563,60.34C86.148,52.219 65.303,51.377 65.094,51.37L64.879,51.362L64.724,51.281C64.722,51.281 64.547,51.214 64.306,51.214C63.308,51.214 62.503,52.364 62.038,54.454Z"
style=
"fill:url(#_Radial5);fill-rule:nonzero;"
/>
<path
d=
"M60.248,50.653C60.248,50.653 62.873,56.091 56.686,64.341C50.498,72.591 37.561,85.903 5.311,83.091C6.436,75.403 12.061,67.528 18.248,61.903C24.436,56.278 38.311,50.466 47.498,50.278C56.686,50.091 60.248,50.653 60.248,50.653"
style=
"fill:rgb(4,187,243);fill-rule:nonzero;"
/>
<path
d=
"M47.661,51.007C43.375,51.094 37.624,52.476 31.878,54.799C26.281,57.061 21.348,59.926 18.344,62.657C11.582,68.805 7.229,76.074 5.931,82.597C8.527,82.798 13.378,82.899 13.378,82.899C24.927,82.899 34.573,80.782 42.488,76.273C49.505,72.277 53.719,67.278 56.278,63.866C61.187,57.321 60.427,52.744 59.995,51.311C58.915,51.195 56.185,50.967 51.326,50.967C50.161,50.967 48.928,50.98 47.661,51.007Z"
style=
"fill:url(#_Linear6);fill-rule:nonzero;"
/>
<path
d=
"M125.873,31.528C125.873,31.528 122.123,42.403 103.748,47.653C85.373,52.903 65.686,49.528 65.686,49.528C65.686,49.528 70.373,37.153 83.873,31.341C97.373,25.528 121.186,30.778 125.873,31.528"
style=
"fill:rgb(2,190,243);fill-rule:nonzero;"
/>
<path
d=
"M83.538,32.552C72.55,37.283 67.364,46.439 65.843,49.607C68.109,49.916 73.306,50.509 79.826,50.509C88.482,50.509 96.392,49.503 103.336,47.519C118.161,43.284 123.571,34.847 125.056,32.014C124.813,31.971 107.93,28.636 98.298,28.636C91.395,28.636 87.705,30.757 83.538,32.552Z"
style=
"fill:url(#_Radial7);fill-rule:nonzero;"
/>
<path
d=
"M59.915,51.278C59.915,51.278 33.175,50.019 17.48,38.568C1.784,27.117 2.146,23.342 2.146,23.342C2.146,23.342 19.745,23.288 28.432,26.42C37.12,29.553 50.768,38.368 59.915,51.278"
style=
"fill:rgb(2,190,243);fill-rule:nonzero;"
/>
<path
d=
"M18.403,38.359C24.472,42.787 31.799,45.917 40.409,48.023C40.411,48.024 40.411,48.024 40.413,48.024C49.562,49.122 57.148,50.815 58.972,50.971C54.956,45.508 49.957,40.566 44.591,36.453C39.398,32.463 33.917,29.31 29.363,27.511C28.518,27.174 27.623,26.872 26.701,26.596C18.683,24.199 5.236,24.042 3.168,24.012C3.677,24.954 5.467,28.921 18.403,38.359Z"
style=
"fill:url(#_Radial8);fill-rule:nonzero;"
/>
<path
d=
"M60.107,53.87C62.484,54.174 63.767,59.49 62.969,65.735C62.171,71.979 59.593,76.802 57.215,76.498C54.838,76.195 53.555,70.878 54.353,64.634C55.151,58.389 57.729,53.566 60.107,53.87Z"
style=
"fill:url(#_Radial9);"
/>
<g>
<path
d=
"M63.623,50.523C55.666,52.02 66.914,87.642 78.96,93.052C102.116,103.453 114.953,83.441 114.953,76.193C114.953,68.944 83.069,46.867 63.623,50.523Z"
style=
"fill:url(#_Radial10);fill-rule:nonzero;"
/>
<path
d=
"M52.38,60.705C49.485,65.966 72.746,83.87 83.148,81.871C93.55,79.872 107.419,69.342 106.34,63.619C105.261,57.895 86.345,42.036 71.209,47.706C61.28,51.425 55.275,55.444 52.38,60.705Z"
style=
"fill:url(#_Radial11);fill-rule:nonzero;"
/>
<path
d=
"M62.304,63.032C60.127,68.629 75.384,84.566 85.432,81.214C95.48,77.862 107.841,65.597 106.018,60.066C104.194,54.535 83.354,41.305 69.097,48.92C59.745,53.914 64.481,57.435 62.304,63.032Z"
style=
"fill:url(#_Radial12);fill-rule:nonzero;"
/>
<path
d=
"M60.721,60.475C58.867,65.196 71.802,78.616 80.337,75.779C88.872,72.942 99.38,62.588 97.838,57.926C96.296,53.264 78.617,42.133 66.504,48.569C58.559,52.79 62.576,55.754 60.721,60.475Z"
style=
"fill:url(#_Radial13);fill-rule:nonzero;"
/>
</g>
<g>
<path
d=
"M63.547,59.973C61.97,64.766 44.374,84.569 32.235,87.347C20.095,90.126 8.142,77.425 7.023,71.562C5.904,65.7 24.225,48.99 42.998,47.542C55.312,46.592 66.193,51.937 63.547,59.973"
style=
"fill:url(#_Radial14);fill-rule:nonzero;"
/>
<path
d=
"M63.205,57.315C61.991,62.895 45.836,85.099 33.848,87.464C21.859,89.832 8.86,73.791 7.281,66.728C5.703,59.665 22.826,41.199 41.586,40.93C53.89,40.753 65.242,47.956 63.205,57.315"
style=
"fill:url(#_Radial15);fill-rule:nonzero;"
/>
<path
d=
"M54.504,60.754C52.787,63.856 38.701,74.587 30.622,74.27C22.543,73.954 16.914,62.693 17.091,58.324C17.268,53.955 31.346,45.556 43.41,48.065C51.323,49.712 57.384,55.553 54.504,60.754"
style=
"fill:url(#_Radial16);fill-rule:nonzero;"
/>
</g>
<path
d=
"M36.741,55.538C33.222,55.974 16.259,50.894 11.763,44.173C7.266,37.454 13.062,26.278 16.7,23.851C20.337,21.425 35.41,27.869 40.477,39.102C43.8,46.47 42.641,54.807 36.741,55.538"
style=
"fill:url(#_Radial17);fill-rule:nonzero;"
/>
<g>
<path
d=
"M98.43,26.371C113.661,24.356 126.815,28.679 127.786,36.02C128.758,43.36 117.181,50.956 101.95,52.971C86.719,54.986 73.566,50.663 72.594,43.323C71.623,35.982 83.2,28.387 98.43,26.371Z"
style=
"fill:url(#_Radial18);"
/>
<path
d=
"M101.52,27.364C115.256,28.781 125.812,35.705 125.079,42.816C124.345,49.927 112.597,54.55 98.861,53.132C85.124,51.715 74.568,44.791 75.302,37.68C76.036,30.569 87.783,25.946 101.52,27.364Z"
style=
"fill:url(#_Radial19);"
/>
<path
d=
"M91.728,29.668C103.309,28.238 113.329,32.075 114.088,38.23C114.848,44.384 106.061,50.541 94.48,51.97C82.898,53.399 72.878,49.563 72.119,43.408C71.359,37.254 80.146,31.097 91.728,29.668Z"
style=
"fill:url(#_Radial20);"
/>
</g>
<path
d=
"M61.347,51.035C61.369,51.403 62.326,58.88 55.857,61.688C50.854,63.859 44.704,64.93 31.066,58.741C17.427,52.553 12.688,43.562 12.688,43.562C12.688,43.562 20.317,46.364 25.055,46.715C29.794,47.065 42.045,48.816 45.166,49.4C48.286,49.984 53.834,49.517 55.915,49.05C57.995,48.582 61.116,47.298 61.347,51.035"
style=
"fill:rgb(179,195,206);fill-rule:nonzero;"
/>
<path
d=
"M31.065,58.366C44.704,64.555 50.855,63.484 55.857,61.314C62.325,58.505 61.369,51.028 61.347,50.659C61.115,46.923 57.995,48.208 55.915,48.675C53.834,49.142 48.425,48.812 43.936,48.497C38.592,48.122 29.967,46.997 25.056,46.339C20.346,45.709 12.688,43.186 12.688,43.186C12.688,43.186 17.428,52.177 31.065,58.366Z"
style=
"fill:url(#_Linear21);fill-rule:nonzero;"
/>
<path
d=
"M57.256,44.227C57.256,44.227 52.914,40.794 49.381,37.059C45.847,33.323 34.438,22.52 31.611,19.592C28.785,16.665 21.111,6.669 20.101,2.428C17.779,7.578 16.265,16.967 19.092,26.054C21.918,35.141 28.482,41.703 37.164,45.338C45.847,48.972 53.419,48.771 55.539,48.771C57.66,48.771 60.588,47.357 57.256,44.227"
style=
"fill:rgb(245,246,242);fill-rule:nonzero;"
/>
<path
d=
"M19.467,26.054C22.294,35.14 28.857,41.703 37.54,45.337C46.222,48.973 53.794,49.024 55.915,49.024C57.112,49.024 60.904,48.872 61.092,48.31C61.495,47.099 59.166,46.202 57.717,44.841C53.869,41.799 53.941,41.483 49.756,37.059C46.222,33.323 34.813,22.52 31.987,19.592C29.16,16.664 21.485,6.669 20.476,2.428C18.154,7.577 16.639,16.967 19.467,26.054Z"
style=
"fill:url(#_Linear22);fill-rule:nonzero;"
/>
<path
d=
"M105.331,7.54C101.581,10.915 94.936,15.403 88.561,19.528C82.186,23.653 78.061,30.591 73.186,37.903C68.311,45.216 63.436,48.778 63.436,48.778C63.436,48.778 71.123,49.903 78.436,49.903C89.692,49.903 102.436,40.528 107.123,35.466C111.811,30.403 114.961,22.156 114.998,13.716C115.041,3.855 110.479,1.924 110.479,1.924C110.479,1.924 109.081,4.165 105.331,7.54Z"
style=
"fill:url(#_Linear23);fill-rule:nonzero;"
/>
<path
d=
"M105.859,6.779C102.108,10.154 94.561,15.403 88.186,19.528C81.811,23.653 77.686,30.591 72.811,37.903C69.061,43.529 64.367,46.627 62.401,48.232C61.811,48.714 61.467,49.997 61.467,49.997C68.236,50.987 69.818,49.903 78.061,49.903C89.316,49.903 102.061,40.528 106.748,35.466C111.436,30.403 114.436,22.153 114.623,13.716C114.811,5.278 110.498,1.903 110.498,1.903C110.498,1.903 109.608,3.404 105.859,6.779Z"
style=
"fill:url(#_Linear24);fill-rule:nonzero;"
/>
<g>
<path
d=
"M44.632,31.45C52.765,36.708 57.294,44.185 54.739,48.136C52.184,52.088 43.507,51.026 35.375,45.768C27.242,40.509 22.713,33.032 25.268,29.081C27.823,25.13 36.5,26.191 44.632,31.45Z"
style=
"fill:url(#_Radial25);"
/>
<path
d=
"M43.849,30.894C51.575,36.734 55.915,44.033 53.533,47.184C51.151,50.335 42.945,48.152 35.219,42.313C27.493,36.474 23.154,29.174 25.535,26.023C27.917,22.872 36.123,25.055 43.849,30.894Z"
style=
"fill:url(#_Radial26);"
/>
</g>
<g>
<path
d=
"M78.741,36.006C86.779,31.512 94.699,30.358 96.416,33.429C98.132,36.5 93.001,42.641 84.963,47.135C76.925,51.628 69.006,52.783 67.289,49.712C65.572,46.641 70.704,40.5 78.741,36.006Z"
style=
"fill:url(#_Radial27);"
/>
<path
d=
"M81.132,32.187C89.083,27.742 97.072,26.877 98.962,30.257C100.852,33.637 95.93,39.99 87.979,44.435C80.028,48.88 72.039,49.745 70.15,46.365C68.26,42.985 73.181,36.632 81.132,32.187Z"
style=
"fill:url(#_Radial28);"
/>
<path
d=
"M80.126,35.267C87.627,30.098 95.608,28.646 97.938,32.027C100.269,35.408 96.071,42.35 88.571,47.52C81.071,52.689 73.09,54.141 70.759,50.76C68.429,47.379 72.626,40.437 80.126,35.267Z"
style=
"fill:url(#_Radial29);"
/>
<path
d=
"M89.308,27.918C92.187,21.47 96.816,17.259 99.641,18.519C102.465,19.78 102.421,26.039 99.543,32.486C96.664,38.934 92.034,43.145 89.21,41.885C86.386,40.624 86.43,34.366 89.308,27.918Z"
style=
"fill:url(#_Radial30);"
/>
</g>
<path
d=
"M26.696,18.134C25.891,24.037 27.282,30.112 30.059,34.111C32.837,38.113 40.822,45.919 48.69,47.029C53.801,47.748 59.286,47.719 59.286,47.719C59.286,47.719 56.228,44.749 53.541,39.157C50.854,33.566 48.653,28.311 44.604,24.799C40.554,21.289 36.35,17.497 34.061,14.768C31.773,12.04 31.017,10.337 31.017,10.337C31.017,10.337 27.637,11.236 26.696,18.134Z"
style=
"fill:url(#_Linear31);fill-rule:nonzero;"
/>
<path
d=
"M26.959,18.171C26.259,24.087 27.544,30.149 30.322,34.149C33.1,38.149 41.084,45.957 48.952,47.065C54.714,47.877 55.262,48.192 60.091,48.166C60.091,48.166 56.491,44.785 53.803,39.194C51.115,33.603 48.916,28.347 44.866,24.836C40.816,21.326 36.058,16.913 33.769,14.185C31.48,11.457 31.005,10.32 31.005,10.32C31.005,10.32 27.658,12.254 26.959,18.171Z"
style=
"fill:url(#_Linear32);fill-rule:nonzero;"
/>
<g>
<path
d=
"M57.708,51.938C58.768,55.006 54.871,59.141 49.011,61.165C43.15,63.189 37.532,62.341 36.472,59.273C35.413,56.204 39.31,52.07 45.17,50.046C51.03,48.022 56.649,48.87 57.708,51.938Z"
style=
"fill:url(#_Radial33);"
/>
<path
d=
"M55.199,53.999C55.318,56.901 51.021,59.439 45.607,59.662C40.194,59.885 35.703,57.71 35.583,54.808C35.463,51.905 39.761,49.368 45.174,49.144C50.587,48.921 55.079,51.097 55.199,53.999Z"
style=
"fill:url(#_Radial34);"
/>
<path
d=
"M60.024,52.648C60.876,54.662 57.946,57.827 53.486,59.712C49.027,61.597 44.715,61.493 43.864,59.479C43.013,57.465 45.943,54.3 50.402,52.415C54.862,50.53 59.173,50.635 60.024,52.648Z"
style=
"fill:url(#_Radial35);"
/>
</g>
<path
d=
"M33.667,51.566C35.833,54.697 39.109,56.964 42.144,57.652C45.179,58.342 52.312,58.579 56.502,55.711C59.224,53.845 61.797,51.466 61.797,51.466C61.797,51.466 59.074,51.385 55.396,49.906C51.717,48.427 48.412,46.897 44.989,46.987C41.564,47.077 37.946,47.102 35.69,46.802C33.433,46.503 32.342,46.025 32.342,46.025C32.342,46.025 31.137,47.907 33.667,51.566Z"
style=
"fill:url(#_Linear36);fill-rule:nonzero;"
/>
<path
d=
"M33.807,51.471C36.028,54.562 39.248,56.867 42.283,57.557C45.319,58.245 52.453,58.485 56.641,55.615C59.709,53.512 60.103,53.425 62.37,51.33C62.37,51.33 59.215,51.289 55.535,49.81C51.856,48.332 48.553,46.801 45.128,46.892C41.704,46.983 37.556,46.954 35.3,46.654C33.044,46.354 32.33,46.022 32.33,46.022C32.33,46.022 31.585,48.378 33.807,51.471Z"
style=
"fill:url(#_Linear37);fill-rule:nonzero;"
/>
<path
d=
"M100.92,50.491C97.547,51.911 82.546,51.113 77.044,50.491C71.541,49.87 65.855,49.966 64.794,49.87C60.998,49.528 61.688,50.757 61.421,54.574C60.8,65.137 64.794,67.267 67.723,67.711C70.653,68.155 73.316,66.735 73.316,66.735C73.316,66.735 102.34,53.598 105.003,52.177C105.499,49.453 105.379,48.591 104.771,48.591C104.071,48.591 102.724,49.732 100.92,50.491Z"
style=
"fill:url(#_Radial38);fill-rule:nonzero;"
/>
<g>
<path
d=
"M73.82,53.958C78.768,57.469 81.437,62.218 79.778,64.556C78.118,66.895 72.754,65.942 67.807,62.43C62.859,58.919 60.19,54.17 61.849,51.831C63.509,49.493 68.873,50.446 73.82,53.958Z"
style=
"fill:url(#_Radial39);"
/>
<path
d=
"M72.54,53.958C76.339,56.654 78.075,60.741 76.416,63.079C74.756,65.417 70.325,65.126 66.527,62.43C62.728,59.734 60.992,55.647 62.651,53.309C64.311,50.971 68.742,51.262 72.54,53.958Z"
style=
"fill:url(#_Radial40);"
/>
<path
d=
"M76.317,54.251C80.115,56.947 81.851,61.034 80.192,63.372C78.532,65.71 74.101,65.42 70.303,62.724C66.505,60.028 64.768,55.941 66.428,53.602C68.087,51.264 72.518,51.555 76.317,54.251Z"
style=
"fill:url(#_Radial41);"
/>
</g>
<path
d=
"M63.496,55.18C66.133,59.521 72.732,62.239 75.782,62.861C78.832,63.483 82.75,62.764 86.013,60.802C89.824,58.51 89.5,56.299 89.5,56.299C89.5,56.299 88.309,56.284 86.13,55.63C83.95,54.973 80.662,53.462 77.579,51.97C74.496,50.479 70.855,50.513 66.893,50.346C62.932,50.179 60.417,49.134 60.417,49.134C60.417,49.134 61.783,52.362 63.496,55.18Z"
style=
"fill:url(#_Linear42);fill-rule:nonzero;"
/>
<path
d=
"M63.409,55.036C66.044,59.377 72.644,62.095 75.694,62.716C78.744,63.338 82.627,62.562 85.924,60.658C89.222,58.755 89.512,56.302 89.512,56.302C89.512,56.302 88.726,56.309 86.547,55.654C84.367,54.998 80.575,53.317 77.491,51.826C74.408,50.334 70.767,50.368 66.805,50.202C62.844,50.034 59.951,48.773 59.951,48.773C61.154,51.616 61.478,51.858 63.409,55.036Z"
style=
"fill:url(#_Linear43);fill-rule:nonzero;"
/>
<path
d=
"M104.029,52.622C104.029,52.622 105.248,51.685 105.154,49.81C105.061,47.935 105.529,45.122 95.592,39.028C92.311,44.935 80.967,60.685 75.998,64.247C71.029,67.81 70.233,67.689 70.233,67.689C70.233,67.689 73.104,67.346 75.623,65.466C78.346,63.434 104.029,52.622 104.029,52.622"
style=
"fill:rgb(203,211,221);fill-rule:nonzero;"
/>
<path
d=
"M76.373,64.435C73.288,66.647 72.347,67.09 71.657,67.38C71.914,67.303 73.846,66.797 75.998,65.654C78.998,64.06 104.404,52.81 104.404,52.81C104.404,52.81 105.623,51.872 105.529,49.997C105.436,48.122 105.904,45.31 95.967,39.216C92.686,45.122 81.342,60.873 76.373,64.435Z"
style=
"fill:url(#_Linear44);fill-rule:nonzero;"
/>
<path
d=
"M60.909,53.32C60.291,53.319 54.887,63.139 57.418,74.389C57.418,74.389 61.033,70.051 61.476,59.142C61.659,54.642 61.343,53.32 60.909,53.32Z"
style=
"fill:url(#_Linear45);fill-rule:nonzero;"
/>
<path
d=
"M46.823,22.051C48.095,20.811 52.421,21.803 53.438,23.167C54.433,24.5 59.556,29.625 59.313,37.38C60.101,37.413 61.145,37.55 62.344,37.55C63.38,37.55 64.338,37.448 65.125,37.398C64.873,29.633 70.003,24.501 70.998,23.167C72.015,21.803 76.341,20.811 77.613,22.051C78.886,23.291 75.578,23.167 74.051,22.795C69.11,23.889 65.954,34.834 65.282,37.389C66.434,37.326 67.178,37.407 67.178,38.17C67.178,39.326 65.559,41.191 63.306,41.391C63.398,41.855 63.933,44.444 63.33,47.168C63.085,48.275 60.811,49.528 60.811,49.528C58.396,41.616 60.788,40.846 60.821,40.78C58.843,40.484 57.764,39.231 57.764,38.17C57.764,37.494 58.281,37.353 59.151,37.374C58.47,34.792 55.316,23.887 50.386,22.795C48.859,23.167 45.551,23.291 46.823,22.051"
style=
"fill:rgb(0,198,249);fill-rule:nonzero;"
/>
<path
d=
"M70.998,23.542C70.003,24.877 64.873,30.007 65.125,37.774C64.338,37.823 63.379,37.925 62.344,37.925C61.145,37.925 60.101,37.789 59.313,37.755C59.556,30 54.433,24.875 53.439,23.542C52.421,22.177 48.095,21.186 46.823,22.426C45.551,23.666 48.859,23.542 50.386,23.17C55.316,24.262 58.47,35.167 59.151,37.749C58.281,37.729 57.764,37.869 57.764,38.545C57.764,39.606 58.55,40.935 60.527,41.229C60.494,41.295 58.869,42.472 60.811,49.903C60.811,49.903 63.913,49.611 63.439,43.264C63.385,42.531 63.309,41.945 63.216,41.481C65.468,41.28 67.178,39.7 67.178,38.545C67.178,37.783 66.434,37.702 65.281,37.764C65.955,35.209 69.11,24.264 74.051,23.17C75.578,23.542 78.886,23.666 77.613,22.426C77.205,22.028 76.483,21.86 75.657,21.86C73.907,21.86 71.689,22.615 70.998,23.542Z"
style=
"fill:url(#_Radial46);fill-rule:nonzero;"
/>
<path
d=
"M50.005,10.069C49.068,11.959 52.373,12.241 53.498,11.674C57.133,13.339 59.456,29.962 59.958,33.898C59.317,33.866 58.936,34.08 58.936,35.11C58.936,36.727 59.809,39.039 61.265,39.49C61.241,39.59 61.228,39.65 61.228,39.65C61.228,39.65 58.748,44.278 60.811,54.403C60.811,54.403 63.118,49.341 63.118,42.304C63.118,41.185 63.022,40.294 62.953,39.586C64.613,39.28 65.873,36.872 65.873,35.11C65.873,33.949 65.326,33.825 64.476,33.921C64.863,29.277 66.445,13.536 70.939,11.674C72.064,12.241 75.225,12.028 74.287,10.138C73.35,8.248 70.032,9.755 68.716,11.525C67.271,13.467 63.784,22.154 63.969,33.991C63.39,34.067 63.073,34.165 62.311,34.165C61.428,34.165 60.658,33.957 60.077,33.906C60.256,22.084 56.4,14.3 55.748,12.241C55.132,10.291 53.215,9.139 51.721,9.139C50.966,9.139 50.32,9.434 50.005,10.069Z"
style=
"fill:url(#_Linear47);fill-rule:nonzero;"
/>
<path
d=
"M50.005,10.437C49.068,12.311 52.373,12.591 53.498,12.028C57.133,13.68 59.456,30.17 59.958,34.075C59.317,34.044 58.936,34.256 58.936,35.278C58.936,36.882 59.809,39.175 61.265,39.622C61.241,39.721 61.228,39.781 61.228,39.781C61.228,39.781 56.933,54.615 59.758,64.329C59.758,64.329 63.466,52.012 63.118,42.414C63.078,41.305 63.022,40.42 62.953,39.717C64.613,39.415 65.873,37.026 65.873,35.278C65.873,34.125 65.326,34.003 64.476,34.097C64.863,29.491 66.445,13.876 70.939,12.028C72.064,12.591 75.225,12.38 74.287,10.505C73.35,8.63 70.032,10.125 68.716,11.881C67.271,13.807 63.784,22.425 63.969,34.168C63.39,34.243 63.073,34.341 62.311,34.341C61.428,34.341 60.658,34.134 60.077,34.084C60.256,22.355 56.4,14.635 55.748,12.591C55.132,10.657 53.215,9.514 51.721,9.514C50.967,9.515 50.32,9.807 50.005,10.437Z"
style=
"fill:url(#_Linear48);fill-rule:nonzero;"
/>
<path
d=
"M66.278,44.291C66.278,44.291 70.021,41.331 73.069,38.11C76.116,34.888 85.954,25.573 88.392,23.047C90.83,20.523 97.447,11.903 98.318,8.246C100.321,12.687 101.626,20.784 99.188,28.619C96.751,36.455 91.091,42.115 83.604,45.249C76.116,48.383 69.586,48.209 67.759,48.209C65.93,48.209 63.405,46.99 66.278,44.291"
style=
"fill:rgb(245,246,242);fill-rule:nonzero;"
/>
<path
d=
"M88.069,23.047C85.63,25.573 75.793,34.888 72.746,38.11C69.137,41.925 69.199,42.197 65.881,44.821C64.631,45.994 62.676,47.328 62.935,47.893C63.194,48.457 66.402,48.209 67.435,48.209C69.263,48.209 75.793,48.383 83.281,45.249C90.768,42.115 96.427,36.455 98.865,28.62C101.304,20.784 99.997,12.687 97.994,8.246C97.124,11.904 90.507,20.523 88.069,23.047Z"
style=
"fill:url(#_Linear49);fill-rule:nonzero;"
/>
<g>
<path
d=
"M218.013,20.338C216.807,21.544 216.204,23.026 216.204,24.785C216.204,26.545 216.807,28.04 218.013,29.271C219.219,30.502 220.701,31.117 222.462,31.117C224.22,31.117 225.714,30.502 226.946,29.271C228.177,28.04 228.793,26.545 228.793,24.785C228.793,23.026 228.177,21.544 226.946,20.338C225.714,19.132 224.22,18.528 222.462,18.528C220.701,18.528 219.219,19.132 218.013,20.338ZM218.352,34.999C217.222,36.13 216.657,37.501 216.657,39.108L216.657,69.638C216.657,70.443 216.807,71.21 217.109,71.938C217.41,72.667 217.825,73.294 218.352,73.822C218.88,74.35 219.495,74.765 220.2,75.066C220.903,75.367 221.657,75.518 222.462,75.518C224.068,75.518 225.45,74.941 226.607,73.784C227.762,72.629 228.341,71.248 228.341,69.638L228.341,39.108C228.341,38.305 228.191,37.551 227.889,36.847C227.586,36.144 227.172,35.527 226.646,34.999C226.117,34.472 225.488,34.057 224.76,33.756C224.031,33.454 223.265,33.304 222.462,33.304C220.852,33.304 219.484,33.869 218.352,34.999Z"
style=
"fill:url(#_Linear50);fill-rule:nonzero;"
/>
<path
d=
"M277.263,20.338C276.057,21.544 275.454,23.026 275.454,24.785C275.454,26.545 276.057,28.04 277.263,29.271C278.47,30.502 279.951,31.117 281.712,31.117C283.47,31.117 284.965,30.502 286.196,29.271C287.427,28.04 288.044,26.545 288.044,24.785C288.044,23.026 287.427,21.544 286.196,20.338C284.965,19.132 283.47,18.528 281.712,18.528C279.951,18.528 278.47,19.132 277.263,20.338ZM277.603,34.999C276.473,36.13 275.907,37.501 275.907,39.108L275.907,69.638C275.907,70.443 276.057,71.21 276.359,71.938C276.66,72.667 277.075,73.294 277.603,73.822C278.13,74.35 278.745,74.765 279.45,75.066C280.153,75.367 280.907,75.518 281.712,75.518C283.319,75.518 284.7,74.941 285.858,73.784C287.012,72.629 287.592,71.248 287.592,69.638L287.592,39.108C287.592,38.305 287.441,37.551 287.139,36.847C286.836,36.144 286.422,35.527 285.896,34.999C285.367,34.472 284.739,34.057 284.01,33.756C283.281,33.454 282.515,33.304 281.712,33.304C280.102,33.304 278.734,33.869 277.603,34.999Z"
style=
"fill:url(#_Linear51);fill-rule:nonzero;"
/>
<path
d=
"M143.046,19.621C142.218,20.049 141.538,20.59 141.01,21.242C140.483,21.895 140.079,22.612 139.804,23.391C139.527,24.171 139.389,24.912 139.389,25.615C139.389,26.368 139.49,27.047 139.692,27.649L153.093,70.222C155.244,64.281 157.567,57.825 159.957,51.124L152.205,23.579C151.803,22.173 151.024,21.054 149.868,20.224C148.712,19.395 147.405,18.98 145.947,18.98C144.843,18.98 143.874,19.195 143.046,19.621Z"
style=
"fill:url(#_Linear52);fill-rule:nonzero;"
/>
<path
d=
"M172.22,20.149C171.138,20.929 170.322,22.047 169.769,23.504L159.969,51.17L159.957,51.124C157.567,57.825 155.244,64.281 153.093,70.222L153.336,70.996C153.788,72.403 154.542,73.509 155.597,74.313C156.653,75.117 157.908,75.518 159.366,75.518C160.824,75.518 162.081,75.13 163.136,74.35C164.191,73.572 164.97,72.478 165.472,71.071L176.103,41.143L186.596,70.692C188.409,64.621 190.348,57.87 192.178,51.013L182.434,23.504C181.931,22.047 181.126,20.929 180.022,20.149C178.916,19.371 177.609,18.98 176.103,18.98C174.594,18.98 173.3,19.371 172.22,20.149Z"
style=
"fill:url(#_Linear53);fill-rule:nonzero;"
/>
<path
d=
"M202.335,20.224C201.178,21.054 200.4,22.173 199.998,23.579L192.234,51.17L192.178,51.013C190.348,57.87 188.409,64.621 186.596,70.692L186.73,71.071C187.233,72.478 188.013,73.572 189.068,74.35C190.122,75.13 191.379,75.518 192.837,75.518C194.295,75.518 195.551,75.117 196.606,74.313C197.661,73.509 198.415,72.403 198.867,70.996L212.512,27.649C212.712,27.047 212.813,26.368 212.813,25.615C212.813,24.912 212.674,24.171 212.398,23.391C212.122,22.612 211.72,21.895 211.192,21.242C210.666,20.59 209.988,20.049 209.157,19.621C208.328,19.195 207.36,18.98 206.256,18.98C204.798,18.98 203.491,19.395 202.335,20.224Z"
style=
"fill:url(#_Linear54);fill-rule:nonzero;"
/>
<path
d=
"M262.038,34.66L246.355,47.249L246.355,58.11L260.605,73.408C261.257,74.113 262.024,74.64 262.904,74.992C263.782,75.343 264.65,75.518 265.504,75.518C266.058,75.518 266.661,75.418 267.315,75.217C267.966,75.017 268.569,74.689 269.124,74.236C269.676,73.784 270.141,73.182 270.519,72.427C270.894,71.674 271.083,70.77 271.083,69.714C271.083,68.96 270.944,68.206 270.669,67.452C270.392,66.699 269.952,66.046 269.349,65.493L256.233,52.828L269.5,43.405C270.204,42.903 270.706,42.274 271.008,41.521C271.309,40.767 271.46,39.988 271.46,39.184C271.46,38.128 271.258,37.224 270.857,36.469C270.454,35.716 269.964,35.101 269.387,34.623C268.809,34.146 268.193,33.807 267.54,33.605C266.886,33.405 266.283,33.304 265.73,33.304C264.373,33.304 263.142,33.756 262.038,34.66Z"
style=
"fill:url(#_Linear55);fill-rule:nonzero;"
/>
<g
opacity=
"0.1"
>
<path
d=
"M159.969,51.17L153.092,70.222L151.486,65.12L159.969,51.17Z"
style=
"fill-rule:nonzero;"
/>
</g>
<g
opacity=
"0.1"
>
<path
d=
"M192.234,51.17L186.596,70.692L184.788,65.6L192.234,51.17Z"
style=
"fill-rule:nonzero;"
/>
</g>
<g
opacity=
"0.1"
>
<path
d=
"M246.433,47.174L250.037,62.054L246.355,58.109L246.433,47.174Z"
style=
"fill-rule:nonzero;"
/>
</g>
<path
d=
"M236.444,20.677C235.314,21.808 234.748,23.178 234.748,24.785L234.748,69.638C234.748,70.443 234.899,71.21 235.2,71.938C235.503,72.667 235.917,73.294 236.444,73.822C236.972,74.35 237.588,74.765 238.29,75.066C238.995,75.367 239.748,75.518 240.552,75.518C242.16,75.518 243.543,74.941 244.698,73.784C245.854,72.629 246.433,71.248 246.433,69.638L246.433,24.785C246.433,23.982 246.282,23.228 245.981,22.524C245.679,21.821 245.264,21.205 244.737,20.677C244.209,20.149 243.581,19.735 242.852,19.432C242.123,19.132 241.356,18.98 240.552,18.98C238.944,18.98 237.575,19.546 236.444,20.677Z"
style=
"fill:url(#_Linear56);fill-rule:nonzero;"
/>
</g>
<defs>
<linearGradient
id=
"_Linear1"
x1=
"0"
y1=
"0"
x2=
"1"
y2=
"0"
gradientUnits=
"userSpaceOnUse"
gradientTransform=
"matrix(3.46999e-15,-56.6692,56.6692,3.46999e-15,299.387,73.7943)"
><stop
offset=
"0"
style=
"stop-color:rgb(33,150,243);stop-opacity:1"
/><stop
offset=
"0.01"
style=
"stop-color:rgb(33,150,243);stop-opacity:1"
/><stop
offset=
"1"
style=
"stop-color:rgb(0,192,243);stop-opacity:1"
/></linearGradient>
<linearGradient
id=
"_Linear2"
x1=
"0"
y1=
"0"
x2=
"1"
y2=
"0"
gradientUnits=
"userSpaceOnUse"
gradientTransform=
"matrix(3.46999e-15,-56.6692,56.6692,3.46999e-15,313.219,73.7943)"
><stop
offset=
"0"
style=
"stop-color:rgb(33,150,243);stop-opacity:1"
/><stop
offset=
"0.01"
style=
"stop-color:rgb(33,150,243);stop-opacity:1"
/><stop
offset=
"1"
style=
"stop-color:rgb(0,192,243);stop-opacity:1"
/></linearGradient>
<linearGradient
id=
"_Linear3"
x1=
"0"
y1=
"0"
x2=
"1"
y2=
"0"
gradientUnits=
"userSpaceOnUse"
gradientTransform=
"matrix(-12.1464,7.83636,-7.83636,-12.1464,353.869,59.5849)"
><stop
offset=
"0"
style=
"stop-color:rgb(33,150,243);stop-opacity:1"
/><stop
offset=
"0.01"
style=
"stop-color:rgb(33,150,243);stop-opacity:1"
/><stop
offset=
"1"
style=
"stop-color:rgb(0,192,243);stop-opacity:1"
/></linearGradient>
<linearGradient
id=
"_Linear4"
x1=
"0"
y1=
"0"
x2=
"1"
y2=
"0"
gradientUnits=
"userSpaceOnUse"
gradientTransform=
"matrix(-6.465,27.8191,-27.8191,-6.465,351.575,31.532)"
><stop
offset=
"0"
style=
"stop-color:rgb(33,150,243);stop-opacity:1"
/><stop
offset=
"0.01"
style=
"stop-color:rgb(33,150,243);stop-opacity:1"
/><stop
offset=
"1"
style=
"stop-color:rgb(0,192,243);stop-opacity:1"
/></linearGradient>
<radialGradient
id=
"_Radial5"
cx=
"0"
cy=
"0"
r=
"1"
gradientUnits=
"userSpaceOnUse"
gradientTransform=
"matrix(-66.1387,0,0,66.1387,62.5747,49.8221)"
><stop
offset=
"0"
style=
"stop-color:rgb(61,78,173);stop-opacity:1"
/><stop
offset=
"1"
style=
"stop-color:rgb(0,192,243);stop-opacity:1"
/></radialGradient>
<linearGradient
id=
"_Linear6"
x1=
"0"
y1=
"0"
x2=
"1"
y2=
"0"
gradientUnits=
"userSpaceOnUse"
gradientTransform=
"matrix(-54.7355,6.70317e-15,-6.70317e-15,-54.7355,60.3147,67.0176)"
><stop
offset=
"0"
style=
"stop-color:rgb(63,81,180);stop-opacity:1"
/><stop
offset=
"1"
style=
"stop-color:rgb(0,192,243);stop-opacity:1"
/></linearGradient>
<radialGradient
id=
"_Radial7"
cx=
"0"
cy=
"0"
r=
"1"
gradientUnits=
"userSpaceOnUse"
gradientTransform=
"matrix(-66.1374,0,0,66.1374,62.5749,49.8221)"
><stop
offset=
"0"
style=
"stop-color:rgb(61,78,173);stop-opacity:1"
/><stop
offset=
"1"
style=
"stop-color:rgb(0,192,243);stop-opacity:1"
/></radialGradient>
<radialGradient
id=
"_Radial8"
cx=
"0"
cy=
"0"
r=
"1"
gradientUnits=
"userSpaceOnUse"
gradientTransform=
"matrix(-52.6361,-107.604,-12.8428,112.241,50.7804,124.457)"
><stop
offset=
"0"
style=
"stop-color:rgb(0,114,233);stop-opacity:1"
/><stop
offset=
"1"
style=
"stop-color:rgb(0,253,254);stop-opacity:1"
/></radialGradient>
<radialGradient
id=
"_Radial9"
cx=
"0"
cy=
"0"
r=
"1"
gradientUnits=
"userSpaceOnUse"
gradientTransform=
"matrix(4.0052,0.526563,-1.46889,11.3112,58.6611,65.1843)"
><stop
offset=
"0"
style=
"stop-color:rgb(21,62,104);stop-opacity:0.34"
/><stop
offset=
"1"
style=
"stop-color:rgb(21,62,104);stop-opacity:0.01"
/></radialGradient>
<radialGradient
id=
"_Radial10"
cx=
"0"
cy=
"0"
r=
"1"
gradientUnits=
"userSpaceOnUse"
gradientTransform=
"matrix(-10.3568,10.999,26.0381,24.5177,76.5301,64.2632)"
><stop
offset=
"0"
style=
"stop-color:rgb(0,51,111);stop-opacity:1"
/><stop
offset=
"1"
style=
"stop-color:rgb(0,51,111);stop-opacity:0"
/></radialGradient>
<radialGradient
id=
"_Radial11"
cx=
"0"
cy=
"0"
r=
"1"
gradientUnits=
"userSpaceOnUse"
gradientTransform=
"matrix(-6.51962,11.4057,24.5674,14.4231,74.7463,58.6441)"
><stop
offset=
"0"
style=
"stop-color:rgb(0,51,111);stop-opacity:0.5"
/><stop
offset=
"1"
style=
"stop-color:rgb(0,51,111);stop-opacity:0"
/></radialGradient>
<radialGradient
id=
"_Radial12"
cx=
"0"
cy=
"0"
r=
"1"
gradientUnits=
"userSpaceOnUse"
gradientTransform=
"matrix(-4.96046,12.1651,26.2531,11.0614,74.0442,59.2965)"
><stop
offset=
"0"
style=
"stop-color:rgb(0,51,111);stop-opacity:0.5"
/><stop
offset=
"1"
style=
"stop-color:rgb(0,51,111);stop-opacity:0"
/></radialGradient>
<radialGradient
id=
"_Radial13"
cx=
"0"
cy=
"0"
r=
"1"
gradientUnits=
"userSpaceOnUse"
gradientTransform=
"matrix(-4.22491,10.2622,22.277,9.29717,70.6931,57.3125)"
><stop
offset=
"0"
style=
"stop-color:rgb(0,51,111);stop-opacity:1"
/><stop
offset=
"1"
style=
"stop-color:rgb(0,51,111);stop-opacity:0"
/></radialGradient>
<radialGradient
id=
"_Radial14"
cx=
"0"
cy=
"0"
r=
"1"
gradientUnits=
"userSpaceOnUse"
gradientTransform=
"matrix(-9.78901,-10.0451,-28.1355,22.8978,44.6851,58.4131)"
><stop
offset=
"0"
style=
"stop-color:rgb(0,45,104);stop-opacity:1"
/><stop
offset=
"1"
style=
"stop-color:rgb(0,45,104);stop-opacity:0"
/></radialGradient>
<radialGradient
id=
"_Radial15"
cx=
"0"
cy=
"0"
r=
"1"
gradientUnits=
"userSpaceOnUse"
gradientTransform=
"matrix(-10.6184,-12.7117,-26.5102,25.0697,44.123,53.9971)"
><stop
offset=
"0"
style=
"stop-color:rgb(0,45,104);stop-opacity:1"
/><stop
offset=
"1"
style=
"stop-color:rgb(0,45,104);stop-opacity:0"
/></radialGradient>
<radialGradient
id=
"_Radial16"
cx=
"0"
cy=
"0"
r=
"1"
gradientUnits=
"userSpaceOnUse"
gradientTransform=
"matrix(-4.66402,-8.96904,-21.2029,10.9396,42.8368,56.0939)"
><stop
offset=
"0"
style=
"stop-color:rgb(0,45,104);stop-opacity:1"
/><stop
offset=
"1"
style=
"stop-color:rgb(0,45,104);stop-opacity:0"
/></radialGradient>
<radialGradient
id=
"_Radial17"
cx=
"0"
cy=
"0"
r=
"1"
gradientUnits=
"userSpaceOnUse"
gradientTransform=
"matrix(4.51007,-9.04744,-21.3185,-10.7126,33.6469,43.3607)"
><stop
offset=
"0"
style=
"stop-color:rgb(0,45,104);stop-opacity:1"
/><stop
offset=
"1"
style=
"stop-color:rgb(0,45,104);stop-opacity:0"
/></radialGradient>
<radialGradient
id=
"_Radial18"
cx=
"0"
cy=
"0"
r=
"1"
gradientUnits=
"userSpaceOnUse"
gradientTransform=
"matrix(55.5618,-7.25333,2.08197,15.9483,72.5849,43.2523)"
><stop
offset=
"0"
style=
"stop-color:rgb(0,50,109);stop-opacity:1"
/><stop
offset=
"1"
style=
"stop-color:rgb(0,59,121);stop-opacity:0"
/></radialGradient>
<radialGradient
id=
"_Radial19"
cx=
"0"
cy=
"0"
r=
"1"
gradientUnits=
"userSpaceOnUse"
gradientTransform=
"matrix(50.0892,5.26339,-1.61895,15.4438,75.3088,37.6118)"
><stop
offset=
"0"
style=
"stop-color:rgb(0,50,109);stop-opacity:1"
/><stop
offset=
"1"
style=
"stop-color:rgb(0,59,121);stop-opacity:0"
/></radialGradient>
<radialGradient
id=
"_Radial20"
cx=
"0"
cy=
"0"
r=
"1"
gradientUnits=
"userSpaceOnUse"
gradientTransform=
"matrix(42.2509,-5.13094,1.62846,13.3715,72.1116,43.3493)"
><stop
offset=
"0"
style=
"stop-color:rgb(0,50,109);stop-opacity:1"
/><stop
offset=
"1"
style=
"stop-color:rgb(0,59,121);stop-opacity:0"
/></radialGradient>
<linearGradient
id=
"_Linear21"
x1=
"0"
y1=
"0"
x2=
"1"
y2=
"0"
gradientUnits=
"userSpaceOnUse"
gradientTransform=
"matrix(-48.7206,5.96655e-15,-5.96655e-15,-48.7206,61.4089,53.1008)"
><stop
offset=
"0"
style=
"stop-color:rgb(245,247,250);stop-opacity:1"
/><stop
offset=
"0.53"
style=
"stop-color:rgb(241,244,242);stop-opacity:1"
/><stop
offset=
"1"
style=
"stop-color:rgb(186,215,233);stop-opacity:1"
/></linearGradient>
<linearGradient
id=
"_Linear22"
x1=
"0"
y1=
"0"
x2=
"1"
y2=
"0"
gradientUnits=
"userSpaceOnUse"
gradientTransform=
"matrix(-43.2077,5.29142e-15,-5.29142e-15,-43.2077,61.1382,25.6029)"
><stop
offset=
"0"
style=
"stop-color:rgb(250,250,250);stop-opacity:1"
/><stop
offset=
"0.26"
style=
"stop-color:rgb(241,246,248);stop-opacity:1"
/><stop
offset=
"0.73"
style=
"stop-color:rgb(220,229,235);stop-opacity:1"
/><stop
offset=
"1"
style=
"stop-color:rgb(187,207,218);stop-opacity:1"
/></linearGradient>
<linearGradient
id=
"_Linear23"
x1=
"0"
y1=
"0"
x2=
"1"
y2=
"0"
gradientUnits=
"userSpaceOnUse"
gradientTransform=
"matrix(2.93787e-15,-47.9791,47.9791,2.93787e-15,89.2171,49.9033)"
><stop
offset=
"0"
style=
"stop-color:rgb(253,253,255);stop-opacity:1"
/><stop
offset=
"0.26"
style=
"stop-color:rgb(241,242,242);stop-opacity:1"
/><stop
offset=
"0.73"
style=
"stop-color:rgb(191,210,224);stop-opacity:1"
/><stop
offset=
"1"
style=
"stop-color:rgb(175,192,204);stop-opacity:1"
/></linearGradient>
<linearGradient
id=
"_Linear24"
x1=
"0"
y1=
"0"
x2=
"1"
y2=
"0"
gradientUnits=
"userSpaceOnUse"
gradientTransform=
"matrix(2.94248e-15,-48.0543,48.0543,2.94248e-15,88.4231,49.9576)"
><stop
offset=
"0"
style=
"stop-color:rgb(241,246,246);stop-opacity:1"
/><stop
offset=
"0.41"
style=
"stop-color:rgb(253,253,253);stop-opacity:1"
/><stop
offset=
"0.55"
style=
"stop-color:rgb(245,248,250);stop-opacity:1"
/><stop
offset=
"0.89"
style=
"stop-color:rgb(214,225,231);stop-opacity:1"
/><stop
offset=
"0.98"
style=
"stop-color:rgb(195,211,221);stop-opacity:1"
/><stop
offset=
"1"
style=
"stop-color:rgb(195,211,221);stop-opacity:1"
/></linearGradient>
<radialGradient
id=
"_Radial25"
cx=
"0"
cy=
"0"
r=
"1"
gradientUnits=
"userSpaceOnUse"
gradientTransform=
"matrix(14.7354,9.52776,-4.62893,7.15901,40.0035,38.6087)"
><stop
offset=
"0"
style=
"stop-color:rgb(21,62,104);stop-opacity:1"
/><stop
offset=
"1"
style=
"stop-color:rgb(21,62,104);stop-opacity:0"
/></radialGradient>
<radialGradient
id=
"_Radial26"
cx=
"0"
cy=
"0"
r=
"1"
gradientUnits=
"userSpaceOnUse"
gradientTransform=
"matrix(13.9988,10.5803,-4.31511,5.70934,39.5341,36.6037)"
><stop
offset=
"0"
style=
"stop-color:rgb(21,62,104);stop-opacity:1"
/><stop
offset=
"1"
style=
"stop-color:rgb(21,62,104);stop-opacity:0"
/></radialGradient>
<radialGradient
id=
"_Radial27"
cx=
"0"
cy=
"0"
r=
"1"
gradientUnits=
"userSpaceOnUse"
gradientTransform=
"matrix(14.5634,-8.14176,3.11074,5.56427,81.8521,41.5703)"
><stop
offset=
"0"
style=
"stop-color:rgb(21,62,104);stop-opacity:1"
/><stop
offset=
"1"
style=
"stop-color:rgb(21,62,104);stop-opacity:0"
/></radialGradient>
<radialGradient
id=
"_Radial28"
cx=
"0"
cy=
"0"
r=
"1"
gradientUnits=
"userSpaceOnUse"
gradientTransform=
"matrix(14.4062,-8.05386,3.42371,6.12408,84.5558,38.3112)"
><stop
offset=
"0"
style=
"stop-color:rgb(21,62,104);stop-opacity:1"
/><stop
offset=
"1"
style=
"stop-color:rgb(21,62,104);stop-opacity:0"
/></radialGradient>
<radialGradient
id=
"_Radial29"
cx=
"0"
cy=
"0"
r=
"1"
gradientUnits=
"userSpaceOnUse"
gradientTransform=
"matrix(13.5894,-9.36638,4.22241,6.1262,84.3487,41.3934)"
><stop
offset=
"0"
style=
"stop-color:rgb(21,62,104);stop-opacity:1"
/><stop
offset=
"1"
style=
"stop-color:rgb(26,66,107);stop-opacity:0"
/></radialGradient>
<radialGradient
id=
"_Radial30"
cx=
"0"
cy=
"0"
r=
"1"
gradientUnits=
"userSpaceOnUse"
gradientTransform=
"matrix(5.21516,-11.6826,5.1171,2.2843,94.4254,30.2021)"
><stop
offset=
"0"
style=
"stop-color:rgb(21,62,104);stop-opacity:1"
/><stop
offset=
"1"
style=
"stop-color:rgb(27,67,108);stop-opacity:0"
/></radialGradient>
<linearGradient
id=
"_Linear31"
x1=
"0"
y1=
"0"
x2=
"1"
y2=
"0"
gradientUnits=
"userSpaceOnUse"
gradientTransform=
"matrix(4.72457,-33.536,33.536,4.72457,41.1538,45.9665)"
><stop
offset=
"0"
style=
"stop-color:rgb(253,253,255);stop-opacity:1"
/><stop
offset=
"0.26"
style=
"stop-color:rgb(241,242,242);stop-opacity:1"
/><stop
offset=
"0.73"
style=
"stop-color:rgb(191,210,224);stop-opacity:1"
/><stop
offset=
"1"
style=
"stop-color:rgb(175,192,204);stop-opacity:1"
/></linearGradient>
<linearGradient
id=
"_Linear32"
x1=
"0"
y1=
"0"
x2=
"1"
y2=
"0"
gradientUnits=
"userSpaceOnUse"
gradientTransform=
"matrix(-33.3203,-0.000343575,0.000343575,-33.3203,60.0913,29.2441)"
><stop
offset=
"0"
style=
"stop-color:rgb(250,250,250);stop-opacity:1"
/><stop
offset=
"0.26"
style=
"stop-color:rgb(241,246,248);stop-opacity:1"
/><stop
offset=
"0.73"
style=
"stop-color:rgb(220,229,235);stop-opacity:1"
/><stop
offset=
"1"
style=
"stop-color:rgb(187,207,218);stop-opacity:1"
/></linearGradient>
<radialGradient
id=
"_Radial33"
cx=
"0"
cy=
"0"
r=
"1"
gradientUnits=
"userSpaceOnUse"
gradientTransform=
"matrix(1.92018,5.55941,-10.6179,3.66735,47.0904,55.6053)"
><stop
offset=
"0"
style=
"stop-color:rgb(21,62,104);stop-opacity:1"
/><stop
offset=
"1"
style=
"stop-color:rgb(21,62,104);stop-opacity:0"
/></radialGradient>
<radialGradient
id=
"_Radial34"
cx=
"0"
cy=
"0"
r=
"1"
gradientUnits=
"userSpaceOnUse"
gradientTransform=
"matrix(0.21678,5.25887,-9.80783,0.404296,45.3907,54.4033)"
><stop
offset=
"0"
style=
"stop-color:rgb(21,62,104);stop-opacity:1"
/><stop
offset=
"1"
style=
"stop-color:rgb(21,62,104);stop-opacity:0"
/></radialGradient>
<radialGradient
id=
"_Radial35"
cx=
"0"
cy=
"0"
r=
"1"
gradientUnits=
"userSpaceOnUse"
gradientTransform=
"matrix(1.54211,3.64848,-8.0801,3.41524,51.9443,56.0636)"
><stop
offset=
"0"
style=
"stop-color:rgb(21,62,104);stop-opacity:1"
/><stop
offset=
"1"
style=
"stop-color:rgb(21,62,104);stop-opacity:0"
/></radialGradient>
<linearGradient
id=
"_Linear36"
x1=
"0"
y1=
"0"
x2=
"1"
y2=
"0"
gradientUnits=
"userSpaceOnUse"
gradientTransform=
"matrix(-12.2342,-17.8556,17.8556,-12.2342,52.4894,58.4592)"
><stop
offset=
"0"
style=
"stop-color:rgb(253,253,255);stop-opacity:1"
/><stop
offset=
"0.26"
style=
"stop-color:rgb(241,242,242);stop-opacity:1"
/><stop
offset=
"0.73"
style=
"stop-color:rgb(191,210,224);stop-opacity:1"
/><stop
offset=
"1"
style=
"stop-color:rgb(175,192,204);stop-opacity:1"
/></linearGradient>
<linearGradient
id=
"_Linear37"
x1=
"0"
y1=
"0"
x2=
"1"
y2=
"0"
gradientUnits=
"userSpaceOnUse"
gradientTransform=
"matrix(-15.7162,14.3692,-14.3692,-15.7162,54.2101,42.4051)"
><stop
offset=
"0"
style=
"stop-color:rgb(250,250,250);stop-opacity:1"
/><stop
offset=
"0.26"
style=
"stop-color:rgb(241,246,248);stop-opacity:1"
/><stop
offset=
"0.73"
style=
"stop-color:rgb(220,229,235);stop-opacity:1"
/><stop
offset=
"1"
style=
"stop-color:rgb(187,207,218);stop-opacity:1"
/></linearGradient>
<radialGradient
id=
"_Radial38"
cx=
"0"
cy=
"0"
r=
"1"
gradientUnits=
"userSpaceOnUse"
gradientTransform=
"matrix(-16.9591,0,0,16.9591,83.3344,58.1937)"
><stop
offset=
"0"
style=
"stop-color:rgb(249,249,250);stop-opacity:1"
/><stop
offset=
"1"
style=
"stop-color:rgb(245,247,247);stop-opacity:1"
/></radialGradient>
<radialGradient
id=
"_Radial39"
cx=
"0"
cy=
"0"
r=
"1"
gradientUnits=
"userSpaceOnUse"
gradientTransform=
"matrix(8.96408,6.36252,-3.00685,4.23632,70.8134,58.194)"
><stop
offset=
"0"
style=
"stop-color:rgb(21,62,104);stop-opacity:1"
/><stop
offset=
"1"
style=
"stop-color:rgb(21,62,104);stop-opacity:0"
/></radialGradient>
<radialGradient
id=
"_Radial40"
cx=
"0"
cy=
"0"
r=
"1"
gradientUnits=
"userSpaceOnUse"
gradientTransform=
"matrix(6.8821,4.88477,-3.00685,4.23632,69.5335,58.194)"
><stop
offset=
"0"
style=
"stop-color:rgb(21,62,104);stop-opacity:1"
/><stop
offset=
"1"
style=
"stop-color:rgb(21,62,104);stop-opacity:0"
/></radialGradient>
<radialGradient
id=
"_Radial41"
cx=
"0"
cy=
"0"
r=
"1"
gradientUnits=
"userSpaceOnUse"
gradientTransform=
"matrix(6.8821,4.88477,-3.00685,4.23632,73.3098,58.4873)"
><stop
offset=
"0"
style=
"stop-color:rgb(21,62,104);stop-opacity:1"
/><stop
offset=
"1"
style=
"stop-color:rgb(21,62,104);stop-opacity:0"
/></radialGradient>
<linearGradient
id=
"_Linear42"
x1=
"0"
y1=
"0"
x2=
"1"
y2=
"0"
gradientUnits=
"userSpaceOnUse"
gradientTransform=
"matrix(18.4986,-11.2373,11.2373,18.4986,66.0219,59.3376)"
><stop
offset=
"0"
style=
"stop-color:rgb(253,253,255);stop-opacity:1"
/><stop
offset=
"0.26"
style=
"stop-color:rgb(241,242,242);stop-opacity:1"
/><stop
offset=
"0.73"
style=
"stop-color:rgb(191,210,224);stop-opacity:1"
/><stop
offset=
"1"
style=
"stop-color:rgb(175,192,204);stop-opacity:1"
/></linearGradient>
<linearGradient
id=
"_Linear43"
x1=
"0"
y1=
"0"
x2=
"1"
y2=
"0"
gradientUnits=
"userSpaceOnUse"
gradientTransform=
"matrix(8.40915,19.5646,-19.5646,8.40915,71.0611,43.9983)"
><stop
offset=
"0"
style=
"stop-color:rgb(250,250,250);stop-opacity:1"
/><stop
offset=
"0.26"
style=
"stop-color:rgb(241,246,248);stop-opacity:1"
/><stop
offset=
"0.73"
style=
"stop-color:rgb(220,229,235);stop-opacity:1"
/><stop
offset=
"1"
style=
"stop-color:rgb(187,207,218);stop-opacity:1"
/></linearGradient>
<linearGradient
id=
"_Linear44"
x1=
"0"
y1=
"0"
x2=
"1"
y2=
"0"
gradientUnits=
"userSpaceOnUse"
gradientTransform=
"matrix(6.46873,14.3437,-14.3437,6.46873,82.3507,45.5017)"
><stop
offset=
"0"
style=
"stop-color:rgb(174,188,208);stop-opacity:1"
/><stop
offset=
"0.22"
style=
"stop-color:rgb(133,155,185);stop-opacity:1"
/><stop
offset=
"0.53"
style=
"stop-color:rgb(161,177,201);stop-opacity:1"
/><stop
offset=
"1"
style=
"stop-color:rgb(151,169,195);stop-opacity:1"
/></linearGradient>
<linearGradient
id=
"_Linear45"
x1=
"0"
y1=
"0"
x2=
"1"
y2=
"0"
gradientUnits=
"userSpaceOnUse"
gradientTransform=
"matrix(1.29012e-15,-21.0692,21.0692,1.29012e-15,59.0903,74.3882)"
><stop
offset=
"0"
style=
"stop-color:rgb(192,203,218);stop-opacity:1"
/><stop
offset=
"0.02"
style=
"stop-color:rgb(192,203,218);stop-opacity:1"
/><stop
offset=
"0.11"
style=
"stop-color:rgb(212,219,229);stop-opacity:1"
/><stop
offset=
"0.45"
style=
"stop-color:rgb(245,246,249);stop-opacity:1"
/><stop
offset=
"0.59"
style=
"stop-color:rgb(253,253,253);stop-opacity:1"
/><stop
offset=
"1"
style=
"stop-color:rgb(252,253,253);stop-opacity:1"
/></linearGradient>
<radialGradient
id=
"_Radial46"
cx=
"0"
cy=
"0"
r=
"1"
gradientUnits=
"userSpaceOnUse"
gradientTransform=
"matrix(-18.2184,0,0,12.4951,62.2182,35.8819)"
><stop
offset=
"0"
style=
"stop-color:rgb(63,81,181);stop-opacity:1"
/><stop
offset=
"1"
style=
"stop-color:rgb(4,187,243);stop-opacity:1"
/></radialGradient>
<linearGradient
id=
"_Linear47"
x1=
"0"
y1=
"0"
x2=
"1"
y2=
"0"
gradientUnits=
"userSpaceOnUse"
gradientTransform=
"matrix(2.7716e-15,-45.2637,45.2637,2.7716e-15,62.1493,54.4033)"
><stop
offset=
"0"
style=
"stop-color:rgb(175,192,204);stop-opacity:1"
/><stop
offset=
"0.24"
style=
"stop-color:rgb(175,192,204);stop-opacity:1"
/><stop
offset=
"0.74"
style=
"stop-color:rgb(241,242,242);stop-opacity:1"
/><stop
offset=
"1"
style=
"stop-color:rgb(253,253,255);stop-opacity:1"
/></linearGradient>
<linearGradient
id=
"_Linear48"
x1=
"0"
y1=
"0"
x2=
"1"
y2=
"0"
gradientUnits=
"userSpaceOnUse"
gradientTransform=
"matrix(2.84888e-15,-46.5257,46.5257,2.84888e-15,62.1493,56.0403)"
><stop
offset=
"0"
style=
"stop-color:rgb(250,250,250);stop-opacity:1"
/><stop
offset=
"0.26"
style=
"stop-color:rgb(241,246,248);stop-opacity:1"
/><stop
offset=
"0.73"
style=
"stop-color:rgb(220,229,235);stop-opacity:1"
/><stop
offset=
"1"
style=
"stop-color:rgb(187,207,218);stop-opacity:1"
/></linearGradient>
<linearGradient
id=
"_Linear49"
x1=
"0"
y1=
"0"
x2=
"1"
y2=
"0"
gradientUnits=
"userSpaceOnUse"
gradientTransform=
"matrix(2.44737e-15,-39.9686,39.9686,2.44737e-15,81.5604,48.215)"
><stop
offset=
"0"
style=
"stop-color:rgb(241,246,246);stop-opacity:1"
/><stop
offset=
"0.41"
style=
"stop-color:rgb(253,253,253);stop-opacity:1"
/><stop
offset=
"0.55"
style=
"stop-color:rgb(245,248,250);stop-opacity:1"
/><stop
offset=
"0.89"
style=
"stop-color:rgb(214,225,231);stop-opacity:1"
/><stop
offset=
"0.98"
style=
"stop-color:rgb(195,211,221);stop-opacity:1"
/><stop
offset=
"1"
style=
"stop-color:rgb(195,211,221);stop-opacity:1"
/></linearGradient>
<linearGradient
id=
"_Linear50"
x1=
"0"
y1=
"0"
x2=
"1"
y2=
"0"
gradientUnits=
"userSpaceOnUse"
gradientTransform=
"matrix(3.48963e-15,56.99,-56.99,3.48963e-15,222.499,18.5284)"
><stop
offset=
"0"
style=
"stop-color:rgb(52,56,65);stop-opacity:1"
/><stop
offset=
"1"
style=
"stop-color:rgb(34,37,43);stop-opacity:1"
/></linearGradient>
<linearGradient
id=
"_Linear51"
x1=
"0"
y1=
"0"
x2=
"1"
y2=
"0"
gradientUnits=
"userSpaceOnUse"
gradientTransform=
"matrix(3.48965e-15,56.9903,-56.9903,3.48965e-15,281.749,18.5279)"
><stop
offset=
"0"
style=
"stop-color:rgb(52,56,65);stop-opacity:1"
/><stop
offset=
"1"
style=
"stop-color:rgb(34,37,43);stop-opacity:1"
/></linearGradient>
<linearGradient
id=
"_Linear52"
x1=
"0"
y1=
"0"
x2=
"1"
y2=
"0"
gradientUnits=
"userSpaceOnUse"
gradientTransform=
"matrix(17.0441,33.7943,-33.7943,17.0441,139.694,21.0801)"
><stop
offset=
"0"
style=
"stop-color:rgb(52,56,65);stop-opacity:1"
/><stop
offset=
"1"
style=
"stop-color:rgb(34,37,43);stop-opacity:1"
/></linearGradient>
<linearGradient
id=
"_Linear53"
x1=
"0"
y1=
"0"
x2=
"1"
y2=
"0"
gradientUnits=
"userSpaceOnUse"
gradientTransform=
"matrix(23.1173,-6.36705,6.36705,23.1173,153.941,52.2409)"
><stop
offset=
"0"
style=
"stop-color:rgb(52,56,65);stop-opacity:1"
/><stop
offset=
"1"
style=
"stop-color:rgb(34,37,43);stop-opacity:1"
/></linearGradient>
<linearGradient
id=
"_Linear54"
x1=
"0"
y1=
"0"
x2=
"1"
y2=
"0"
gradientUnits=
"userSpaceOnUse"
gradientTransform=
"matrix(11.4607,-35.1657,35.1657,11.4607,195.608,59.0142)"
><stop
offset=
"0"
style=
"stop-color:rgb(52,56,65);stop-opacity:1"
/><stop
offset=
"1"
style=
"stop-color:rgb(34,37,43);stop-opacity:1"
/></linearGradient>
<linearGradient
id=
"_Linear55"
x1=
"0"
y1=
"0"
x2=
"1"
y2=
"0"
gradientUnits=
"userSpaceOnUse"
gradientTransform=
"matrix(-19.0116,-0.0985055,0.0985055,-19.0116,266.325,54.4147)"
><stop
offset=
"0"
style=
"stop-color:rgb(52,56,65);stop-opacity:1"
/><stop
offset=
"1"
style=
"stop-color:rgb(34,37,43);stop-opacity:1"
/></linearGradient>
<linearGradient
id=
"_Linear56"
x1=
"0"
y1=
"0"
x2=
"1"
y2=
"0"
gradientUnits=
"userSpaceOnUse"
gradientTransform=
"matrix(7.05273,-52.9934,52.9934,7.05273,237.313,71.8385)"
><stop
offset=
"0"
style=
"stop-color:rgb(52,56,65);stop-opacity:1"
/><stop
offset=
"1"
style=
"stop-color:rgb(34,37,43);stop-opacity:1"
/></linearGradient>
</defs>
</svg>
ux/quasar.config.js
View file @
097833d7
...
...
@@ -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'
,
...
...
ux/src/components/AuthLoginPanel.vue
0 → 100644
View file @
097833d7
<
template
lang=
"pug"
>
.auth-login
//- -----------------------------------------------------
//- LOGIN SCREEN
//- -----------------------------------------------------
template(v-if='state.screen === `login`')
template(v-if='state.strategies?.length > 1')
p
{{
t
(
'auth.selectAuthProvider'
)
}}
.auth-strategies.q-mb-md
q-btn(
v-for='str of state.strategies'
:label='str.activeStrategy.displayName'
:icon='`img:` + str.activeStrategy.strategy.icon'
push
no-caps
:color='str.id === state.selectedStrategyId ? `primary` : ($q.dark.isActive ? `blue-grey-9` : `grey-1`)'
:text-color='str.id === state.selectedStrategyId || $q.dark.isActive ? `white` : `blue-grey-9`'
@click='state.selectedStrategyId = str.id'
)
q-form(ref='loginForm', @submit='login')
q-input(
ref='loginEmailIpt'
v-model='state.username'
autofocus
outlined
:label='t(`auth.fields.` + (selectedStrategy.activeStrategy?.strategy?.usernameType ?? `email`))'
:rules='selectedStrategy.activeStrategy?.strategy?.usernameType === `username` ? loginUsernameValidation : userEmailValidation'
lazy-rules='ondemand'
hide-bottom-space
:autocomplete='selectedStrategy.activeStrategy?.strategy?.usernameType ?? `email`'
)
template(#prepend)
i.las.la-user
q-input.q-mt-sm(
v-model='state.password'
outlined
:label='t(`auth.fields.password`)'
:rules='loginPasswordValidation'
lazy-rules='ondemand'
hide-bottom-space
type='password'
autocomplete='current-password'
)
template(#prepend)
i.las.la-key
q-btn.full-width.q-mt-sm(
type='submit'
push
color='primary'
:label='t(`auth.actions.login`)'
no-caps
icon='las la-sign-in-alt'
)
template(v-if='selectedStrategy.activeStrategy?.strategy?.key === `local`')
q-separator.q-my-md
q-btn.acrylic-btn.full-width(
flat
color='primary'
:label='t(`auth.switchToRegister.link`)'
no-caps
icon='las la-user-plus'
@click='switchTo(`register`)'
)
q-btn.acrylic-btn.full-width.q-mt-sm(
flat
color='primary'
:label='t(`auth.forgotPasswordLink`)'
no-caps
icon='las la-life-ring'
@click='switchTo(`forgot`)'
)
//- -----------------------------------------------------
//- FORGOT PASSWORD SCREEN
//- -----------------------------------------------------
template(v-else-if='state.screen === `forgot`')
p
{{
t
(
'auth.forgotPasswordSubtitle'
)
}}
q-form(ref='forgotForm', @submit='forgotPassword')
q-input(
ref='forgotEmailIpt'
v-model='state.username'
outlined
:rules='userEmailValidation'
lazy-rules='ondemand'
hide-bottom-space
:label='t(`auth.fields.email`)'
autocomplete='email'
)
template(#prepend)
i.las.la-envelope
q-btn.full-width.q-mt-sm(
type='submit'
push
color='primary'
:label='t(`auth.sendResetPassword`)'
no-caps
icon='las la-life-ring'
)
q-separator.q-my-md
q-btn.acrylic-btn.full-width(
flat
color='primary'
:label='t(`auth.forgotPasswordCancel`)'
no-caps
icon='las la-arrow-circle-left'
@click='switchTo(`login`)'
)
//- -----------------------------------------------------
//- REGISTER SCREEN
//- -----------------------------------------------------
template(v-else-if='state.screen === `register`')
p
{{
t
(
'auth.registerSubTitle'
)
}}
q-form(ref='registerForm', @submit='register')
q-input(
ref='registerNameIpt'
v-model='state.newName'
outlined
:rules='userNameValidation'
lazy-rules='ondemand'
hide-bottom-space
:label='t(`auth.fields.name`)'
autocomplete='name'
)
template(#prepend)
i.las.la-user-circle
q-input.q-mt-sm(
type='email'
v-model='state.newEmail'
outlined
:rules='userEmailValidation'
lazy-rules='ondemand'
hide-bottom-space
:label='t(`auth.fields.email`)'
autocomplete='email'
)
template(#prepend)
i.las.la-envelope
q-input.q-mt-sm(
v-model='state.newPassword'
outlined
:label='t(`auth.fields.password`)'
type='password'
autocomplete='new-password'
:rules='userPasswordValidation'
hide-bottom-space
lazy-rules='ondemand'
)
template(#append)
q-badge(
v-show='state.newPassword'
:color='passwordStrength.color'
:label='passwordStrength.label'
)
template(#prepend)
i.las.la-key
q-input.q-mt-sm(
v-model='state.newPasswordVerify'
outlined
:label='t(`auth.fields.verifyPassword`)'
type='password'
autocomplete='new-password'
:rules='userPasswordVerifyValidation'
hide-bottom-space
lazy-rules='ondemand'
)
template(#prepend)
i.las.la-key
q-btn.full-width.q-mt-sm(
type='submit'
push
color='primary'
:label='t(`auth.actions.register`)'
no-caps
icon='las la-user-plus'
)
q-separator.q-my-md
q-btn.acrylic-btn.full-width(
flat
color='primary'
:label='t(`auth.switchToLogin.link`)'
no-caps
icon='las la-arrow-circle-left'
@click='switchTo(`login`)'
)
//- -----------------------------------------------------
//- CHANGE PASSWORD SCREEN
//- -----------------------------------------------------
template(v-else-if='state.screen === `changePwd`')
p(v-if='state.continuationToken')
{{
t
(
'auth.changePwd.instructions'
)
}}
q-form(ref='changePwdForm', @submit='changePwd')
q-input(
v-if='!state.continuationToken'
ref='changePwdCurrentIpt'
v-model='state.password'
outlined
type='password'
:rules='loginPasswordValidation'
lazy-rules='ondemand'
hide-bottom-space
:label='t(`auth.changePwd.currentPassword`)'
autocomplete='password'
)
template(#prepend)
i.las.la-key
q-input.q-mt-sm(
ref='changePwdNewPwdIpt'
v-model='state.newPassword'
outlined
:label='t(`auth.changePwd.newPassword`)'
type='password'
autocomplete='new-password'
:rules='userPasswordValidation'
hide-bottom-space
lazy-rules='ondemand'
)
template(#append)
q-badge(
v-show='state.newPassword'
:color='passwordStrength.color'
:label='passwordStrength.label'
)
template(#prepend)
i.las.la-key
q-input.q-mt-sm(
v-model='state.newPasswordVerify'
outlined
:label='t(`auth.changePwd.newPasswordVerify`)'
type='password'
autocomplete='new-password'
:rules='userPasswordVerifyValidation'
hide-bottom-space
lazy-rules='ondemand'
)
template(#prepend)
i.las.la-key
q-btn.full-width.q-mt-sm(
type='submit'
push
color='primary'
:label='t(`auth.changePwd.proceed`)'
no-caps
icon='las la-sync-alt'
)
//- -----------------------------------------------------
//- TFA SCREEN
//- -----------------------------------------------------
template(v-else-if='state.screen === `tfa`')
p
{{
t
(
'auth.tfa.subtitle'
)
}}
.auth-login-tfa
v-otp-input(
ref='tfaIpt'
:num-inputs='6'
:should-auto-focus='true'
input-classes='otp-input'
input-type='number'
separator=''
@on-change='v => state.securityCode = v'
@on-complete='verifyTFA'
)
q-btn.full-width.q-mt-md(
push
color='primary'
:label='t(`auth.tfa.verifyToken`)'
no-caps
icon='las la-sign-in-alt'
@click='verifyTFA'
)
//- -----------------------------------------------------
//- TFA SETUP SCREEN
//- -----------------------------------------------------
template(v-else-if='state.screen === `tfasetup`')
p TODO - TFA Setup not available yet.
</
template
>
<
script
setup
>
import
gql
from
'graphql-tag'
import
{
find
}
from
'lodash-es'
import
Cookies
from
'js-cookie'
import
zxcvbn
from
'zxcvbn'
import
{
useI18n
}
from
'vue-i18n'
import
{
useQuasar
}
from
'quasar'
import
{
computed
,
nextTick
,
onMounted
,
reactive
,
ref
,
watch
}
from
'vue'
import
{
useSiteStore
}
from
'src/stores/site'
import
{
useUserStore
}
from
'src/stores/user'
import
VOtpInput
from
'vue3-otp-input'
// QUASAR
const
$q
=
useQuasar
()
// STORES
const
siteStore
=
useSiteStore
()
const
userStore
=
useUserStore
()
// I18N
const
{
t
}
=
useI18n
()
// DATA
const
state
=
reactive
({
strategies
:
[],
selectedStrategyId
:
null
,
screen
:
'login'
,
username
:
''
,
password
:
''
,
securityCode
:
''
,
continuationToken
:
''
,
newName
:
''
,
newEmail
:
''
,
newPassword
:
''
,
newPasswordVerify
:
''
,
isTFAShown
:
false
,
isTFASetupShown
:
false
,
tfaQRImage
:
''
})
// REFS
const
loginEmailIpt
=
ref
(
null
)
const
forgotEmailIpt
=
ref
(
null
)
const
registerNameIpt
=
ref
(
null
)
const
changePwdCurrentIpt
=
ref
(
null
)
const
changePwdNewPwdIpt
=
ref
(
null
)
const
loginForm
=
ref
(
null
)
const
forgotForm
=
ref
(
null
)
const
registerForm
=
ref
(
null
)
const
changePwdForm
=
ref
(
null
)
// COMPUTED
const
selectedStrategy
=
computed
(()
=>
{
return
(
state
.
selectedStrategyId
&&
find
(
state
.
strategies
,
[
'id'
,
state
.
selectedStrategyId
]))
||
{}
})
const
passwordStrength
=
computed
(()
=>
{
if
(
state
.
newPassword
.
length
<
8
)
{
return
{
color
:
'negative'
,
label
:
t
(
'common.password.weak'
)
}
}
else
{
switch
(
zxcvbn
(
state
.
newPassword
).
score
)
{
case
1
:
return
{
color
:
'deep-orange-7'
,
label
:
t
(
'common.password.poor'
)
}
case
2
:
return
{
color
:
'purple-7'
,
label
:
t
(
'common.password.average'
)
}
case
3
:
return
{
color
:
'blue-7'
,
label
:
t
(
'common.password.good'
)
}
case
4
:
return
{
color
:
'green-7'
,
label
:
t
(
'common.password.strong'
)
}
default
:
return
{
color
:
'negative'
,
label
:
t
(
'common.password.weak'
)
}
}
}
})
// VALIDATION RULES
const
loginUsernameValidation
=
[
val
=>
val
.
length
>
0
||
t
(
'auth.errors.missingUsername'
)
]
const
loginPasswordValidation
=
[
val
=>
val
.
length
>
0
||
t
(
'auth.errors.missingPassword'
)
]
const
userNameValidation
=
[
val
=>
val
.
length
>
0
||
t
(
'auth.errors.missingName'
),
val
=>
/^
[^
<>"
]
+$/
.
test
(
val
)
||
t
(
'auth.errors.invalidName'
)
]
const
userEmailValidation
=
[
val
=>
val
.
length
>
0
||
t
(
'auth.errors.missingEmail'
),
val
=>
/^.+@.+
\.
.+$/
.
test
(
val
)
||
t
(
'auth.errors.invalidEmail'
)
]
const
userPasswordValidation
=
[
val
=>
val
.
length
>
0
||
t
(
'auth.errors.missingPassword'
),
val
=>
val
.
length
>=
8
||
t
(
'auth.errors.passwordTooShort'
)
]
const
userPasswordVerifyValidation
=
[
val
=>
val
.
length
>
0
||
t
(
'auth.errors.missingVerifyPassword'
),
val
=>
val
===
state
.
newPassword
||
t
(
'auth.errors.passwordsNotMatch'
)
]
// METHODS
function
switchTo
(
screen
)
{
switch
(
screen
)
{
case
'login'
:
{
state
.
screen
=
'login'
nextTick
(()
=>
{
loginEmailIpt
.
value
.
focus
()
})
break
}
case
'forgot'
:
{
state
.
screen
=
'forgot'
nextTick
(()
=>
{
forgotEmailIpt
.
value
.
focus
()
})
break
}
case
'register'
:
{
state
.
screen
=
'register'
nextTick
(()
=>
{
registerNameIpt
.
value
.
focus
()
})
break
}
default
:
{
throw
new
Error
(
'Invalid Screen'
)
}
}
}
async
function
fetchStrategies
(
showAll
=
false
)
{
const
resp
=
await
APOLLO_CLIENT
.
query
({
query
:
gql
`
query loginFetchSiteStrategies(
$siteId: UUID!
$visibleOnly: Boolean
) {
authSiteStrategies(
siteId: $siteId
visibleOnly: $visibleOnly
) {
id
activeStrategy {
id
displayName
strategy {
key
color
icon
useForm
usernameType
}
selfRegistration
}
order
}
}
`
,
variables
:
{
siteId
:
siteStore
.
id
,
visibleOnly
:
!
showAll
}
})
state
.
strategies
=
resp
.
data
?.
authSiteStrategies
??
[]
state
.
selectedStrategyId
=
state
.
strategies
[
0
].
id
}
async
function
handleLoginResponse
(
resp
)
{
state
.
continuationToken
=
resp
.
continuationToken
if
(
resp
.
mustChangePwd
===
true
)
{
state
.
screen
=
'changePwd'
nextTick
(()
=>
{
if
(
state
.
continuationToken
)
{
changePwdNewPwdIpt
.
value
.
focus
()
}
else
{
changePwdCurrentIpt
.
value
.
focus
()
}
})
$q
.
loading
.
hide
()
}
else
if
(
resp
.
mustProvideTFA
===
true
)
{
state
.
securityCode
=
''
state
.
screen
=
'tfa'
$q
.
loading
.
hide
()
}
else
if
(
resp
.
mustSetupTFA
===
true
)
{
state
.
securityCode
=
''
state
.
screen
=
'tfasetup'
state
.
tfaQRImage
=
resp
.
tfaQRImage
nextTick
(()
=>
{
this
.
$refs
.
iptTFASetup
.
focus
()
})
$q
.
loading
.
hide
()
}
else
{
$q
.
loading
.
show
({
message
:
t
(
'auth.loginSuccess'
),
backgroundColor
:
'green'
})
Cookies
.
set
(
'jwt'
,
resp
.
jwt
,
{
expires
:
365
})
setTimeout
(()
=>
{
const
loginRedirect
=
Cookies
.
get
(
'loginRedirect'
)
if
(
loginRedirect
===
'/'
&&
resp
.
redirect
)
{
Cookies
.
remove
(
'loginRedirect'
)
window
.
location
.
replace
(
resp
.
redirect
)
}
else
if
(
loginRedirect
)
{
Cookies
.
remove
(
'loginRedirect'
)
window
.
location
.
replace
(
loginRedirect
)
}
else
if
(
resp
.
redirect
)
{
window
.
location
.
replace
(
resp
.
redirect
)
}
else
{
window
.
location
.
replace
(
'/'
)
}
},
1000
)
}
}
/**
* LOGIN
*/
async
function
login
()
{
$q
.
loading
.
show
({
message
:
t
(
'auth.signingIn'
)
})
try
{
const
isFormValid
=
await
loginForm
.
value
.
validate
(
true
)
if
(
!
isFormValid
)
{
throw
new
Error
(
t
(
'auth.errors.login'
))
}
const
resp
=
await
APOLLO_CLIENT
.
mutate
({
mutation
:
gql
`
mutation(
$username: String!
$password: String!
$strategyId: UUID!
$siteId: UUID
) {
login(
username: $username
password: $password
strategyId: $strategyId
siteId: $siteId
) {
operation {
succeeded
message
}
jwt
mustChangePwd
mustProvideTFA
mustSetupTFA
continuationToken
redirect
tfaQRImage
}
}
`
,
variables
:
{
username
:
state
.
username
,
password
:
state
.
password
,
strategyId
:
state
.
selectedStrategyId
,
siteId
:
siteStore
.
id
}
})
if
(
resp
.
data
?.
login
?.
operation
?.
succeeded
)
{
state
.
password
=
''
await
handleLoginResponse
(
resp
.
data
.
login
)
}
else
{
throw
new
Error
(
resp
.
data
?.
login
?.
operation
?.
message
||
t
(
'auth.errors.loginError'
))
}
}
catch
(
err
)
{
$q
.
loading
.
hide
()
$q
.
notify
({
type
:
'negative'
,
message
:
err
.
message
})
}
}
/**
* FORGOT PASSWORD
*/
async
function
forgotPassword
()
{
try
{
const
isFormValid
=
await
forgotForm
.
value
.
validate
(
true
)
if
(
!
isFormValid
)
{
throw
new
Error
(
t
(
'auth.errors.forgotPassword'
))
}
}
catch
(
err
)
{
$q
.
notify
({
type
:
'negative'
,
message
:
err
.
message
})
}
}
/**
* REGISTER
*/
async
function
register
()
{
try
{
const
isFormValid
=
await
registerForm
.
value
.
validate
(
true
)
if
(
!
isFormValid
)
{
throw
new
Error
(
t
(
'auth.errors.register'
))
}
}
catch
(
err
)
{
$q
.
notify
({
type
:
'negative'
,
message
:
err
.
message
})
}
}
/**
* CHANGE PASSWORD
*/
async
function
changePwd
()
{
try
{
const
isFormValid
=
await
changePwdForm
.
value
.
validate
(
true
)
if
(
!
isFormValid
)
{
throw
new
Error
(
t
(
'auth.errors.register'
))
}
const
resp
=
await
APOLLO_CLIENT
.
mutate
({
mutation
:
gql
`
mutation (
$userId: UUID!
$continuationToken: String
$currentPassword: String
$newPassword: String!
$strategyId: UUID!
$siteId: UUID
) {
changePassword (
userId: $userId
continuationToken: $continuationToken
currentPassword: $currentPassword
newPassword: $newPassword
strategyId: $strategyId
siteId: $siteId
) {
operation {
succeeded
message
}
jwt
mustChangePwd
mustProvideTFA
mustSetupTFA
continuationToken
redirect
tfaQRImage
}
}
`
,
variables
:
{
userId
:
userStore
.
id
,
continuationToken
:
state
.
continuationToken
,
currentPassword
:
state
.
password
,
newPassword
:
state
.
newPassword
,
strategyId
:
state
.
selectedStrategyId
,
siteId
:
siteStore
.
id
}
})
if
(
resp
.
data
?.
login
?.
operation
?.
succeeded
)
{
state
.
password
=
''
await
handleLoginResponse
(
resp
.
data
.
login
)
}
else
{
throw
new
Error
(
resp
.
data
?.
login
?.
operation
?.
message
||
t
(
'auth.errors.loginError'
))
}
}
catch
(
err
)
{
$q
.
notify
({
type
:
'negative'
,
message
:
err
.
message
})
}
}
/**
* VERIFY TFA TOKEN
*/
async
function
verifyTFA
()
{
$q
.
loading
.
show
({
message
:
t
(
'auth.signingIn'
)
})
try
{
if
(
!
/^
[
0-9
]{6}
$/
.
test
(
state
.
securityCode
))
{
throw
new
Error
(
t
(
'auth.errors.tfaMissing'
))
}
const
resp
=
await
APOLLO_CLIENT
.
mutate
({
mutation
:
gql
`
mutation(
continuationToken: String!
securityCode: String!
) {
loginTFA(
continuationToken: $continuationToken
securityCode: $securityCode
) {
operation {
succeeded
message
}
jwt
mustChangePwd
mustProvideTFA
mustSetupTFA
continuationToken
redirect
tfaQRImage
}
}
`
,
variables
:
{
continuationToken
:
state
.
continuationToken
,
securityCode
:
state
.
securityCode
}
})
if
(
resp
.
data
?.
login
?.
operation
?.
succeeded
)
{
state
.
continuationToken
=
''
state
.
securityCode
=
''
await
handleLoginResponse
(
resp
.
data
.
loginTFA
)
}
else
{
throw
new
Error
(
resp
.
data
?.
loginTFA
?.
operation
?.
message
||
t
(
'auth.errors.loginError'
))
}
}
catch
(
err
)
{
$q
.
loading
.
hide
()
$q
.
notify
({
type
:
'negative'
,
message
:
err
.
message
})
}
}
// MOUNTED
onMounted
(
async
()
=>
{
await
fetchStrategies
()
})
</
script
>
<
style
lang=
"scss"
>
.auth-login-tfa
{
>
div
{
justify-content
:
center
;
}
.otp-input
{
width
:
100%
;
height
:
48px
;
padding
:
5px
;
margin
:
0
5px
;
font-size
:
20px
;
border-radius
:
6px
;
text-align
:
center
;
@at-root
.body--light
&
{
border
:
2px
solid
rgba
(
0
,
0
,
0
,
0
.2
);
}
@at-root
.body--dark
&
{
border
:
2px
solid
rgba
(
255
,
255
,
255
,
0
.3
);
}
&
:focus-visible
{
outline-color
:
$primary
;
}
/* Background colour of an input field with value */
&
.is-complete
{
border-color
:
$positive
;
border-width
:
2px
;
}
&
:
:-
webkit-inner-spin-button
,
&::-
webkit-outer-spin-button
{
-webkit-appearance
:
none
;
margin
:
0
;
}
}
}
</
style
>
ux/src/css/app.scss
View file @
097833d7
...
...
@@ -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
// ------------------------------------------------------------------
...
...
ux/src/i18n/locales/en.json
View file @
097833d7
...
...
@@ -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"
:
"S
elect Authentication Provider
"
,
"auth.selectAuthProvider"
:
"S
ign 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"
,
...
...
ux/src/pages/AdminLocale.vue
View file @
097833d7
...
...
@@ -20,7 +20,7 @@ q-page.admin-locale
icon='las la-question-circle'
flat
color='grey'
:href='siteStore.docsBase + `/admin/local
e
`'
:href='siteStore.docsBase + `/admin/local
isation
`'
target='_blank'
type='a'
)
...
...
ux/src/pages/Login.vue
View file @
097833d7
...
...
@@ -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
>
...
...
ux/yarn.lock
View file @
097833d7
This diff was suppressed by a .gitattributes entry.
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment