Compare commits
33 Commits
fix/organi
...
0.10.2
Author | SHA1 | Date | |
---|---|---|---|
![]() |
2f29bbcee4 | ||
![]() |
63a8c82535 | ||
![]() |
1f81e45e79 | ||
![]() |
40dcf67de9 | ||
![]() |
32d8b7c038 | ||
![]() |
2a91f3e7d8 | ||
![]() |
36d9861517 | ||
![]() |
d577a21a9a | ||
![]() |
115607cb6b | ||
![]() |
adb969ec04 | ||
![]() |
4e48320cf1 | ||
![]() |
cfe035e96b | ||
![]() |
34a91f3195 | ||
![]() |
ea14cc1743 | ||
![]() |
9d7f5fd9db | ||
![]() |
0520056e43 | ||
![]() |
1821e27692 | ||
![]() |
388530a69c | ||
![]() |
1e32d790b3 | ||
![]() |
681ffc65f1 | ||
![]() |
6331ec7b7a | ||
![]() |
25c9ce03bd | ||
![]() |
ac416bfc7b | ||
![]() |
0049e1380b | ||
![]() |
9bd185a9c6 | ||
![]() |
82bc38923c | ||
![]() |
8df8010b22 | ||
![]() |
b42cc1549a | ||
![]() |
4bc9059b0f | ||
![]() |
87b1cac979 | ||
![]() |
7f18a3f634 | ||
![]() |
0511e737ae | ||
![]() |
003d88fb6c |
16
.env.sample
@@ -1,16 +1,2 @@
|
|||||||
ENV=production
|
|
||||||
DATABASE_URL=data.db
|
DATABASE_URL=data.db
|
||||||
DATABASE_TYPE=sqlite
|
DATABASE_TYPE=sqlite
|
||||||
ADMIN_SECRET=admin
|
|
||||||
JWT_SECRET=random_string
|
|
||||||
SENDER_EMAIL=info@authorizer.dev
|
|
||||||
SMTP_USERNAME=username
|
|
||||||
SMTP_PASSWORD=password
|
|
||||||
SMTP_HOST=smtp.mailtrap.io
|
|
||||||
SMTP_PORT=2525
|
|
||||||
JWT_TYPE=HS256
|
|
||||||
ROLES=user
|
|
||||||
DEFAULT_ROLES=user
|
|
||||||
PROTECTED_ROLES=admin
|
|
||||||
JWT_ROLE_CLAIM=role
|
|
||||||
CUSTOM_ACCESS_TOKEN_SCRIPT=function(user,tokenPayload){var data = tokenPayload;data.extra = {'x-extra-id': user.id};return data;}
|
|
@@ -24,7 +24,9 @@ FROM alpine:latest
|
|||||||
WORKDIR /root/
|
WORKDIR /root/
|
||||||
RUN mkdir app dashboard
|
RUN mkdir app dashboard
|
||||||
COPY --from=node-builder /authorizer/app/build app/build
|
COPY --from=node-builder /authorizer/app/build app/build
|
||||||
|
COPY --from=node-builder /authorizer/app/favicon_io app/favicon_io
|
||||||
COPY --from=node-builder /authorizer/dashboard/build dashboard/build
|
COPY --from=node-builder /authorizer/dashboard/build dashboard/build
|
||||||
|
COPY --from=node-builder /authorizer/dashboard/favicon_io dashboard/favicon_io
|
||||||
COPY --from=go-builder /authorizer/build build
|
COPY --from=go-builder /authorizer/build build
|
||||||
COPY templates templates
|
COPY templates templates
|
||||||
EXPOSE 8080
|
EXPOSE 8080
|
||||||
|
19
README.md
@@ -1,6 +1,6 @@
|
|||||||
<p align="center">
|
<p align="center">
|
||||||
<a href="https://authorizer.dev">
|
<a href="https://authorizer.dev">
|
||||||
<img alt="Logo" src="https://github.com/authorizerdev/authorizer/blob/main/assets/logo.png" width="60" />
|
<img alt="Logo" src="https://authorizer.dev/images/logo.png" width="60" />
|
||||||
</a>
|
</a>
|
||||||
</p>
|
</p>
|
||||||
<h1 align="center">
|
<h1 align="center">
|
||||||
@@ -129,18 +129,13 @@ Required environment variables are pre-configured in `.env` file. But based on t
|
|||||||
|
|
||||||
> Note: For mac users, you might have to give binary the permission to execute. Here is the command you can use to grant permission `xattr -d com.apple.quarantine build/server`
|
> Note: For mac users, you might have to give binary the permission to execute. Here is the command you can use to grant permission `xattr -d com.apple.quarantine build/server`
|
||||||
|
|
||||||
## Install instance on Heroku
|
Deploy production ready Authorizer instance using one click deployment options available below
|
||||||
|
|
||||||
Deploy Authorizer using [heroku](https://github.com/authorizerdev/authorizer-heroku) and quickly play with it in 30seconds
|
| **Infra provider** | **One-click link** | **Additional information** |
|
||||||
<br/><br/>
|
| :----------------: | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :----------------------------------------------------: |
|
||||||
[](https://heroku.com/deploy?template=https://github.com/authorizerdev/authorizer-heroku)
|
| Railway.app | <a href="https://railway.app/new/template?template=https://github.com/authorizerdev/authorizer-railway&plugins=postgresql,redis"><img src="https://railway.app/button.svg" style="height: 44px" alt="Deploy on Railway"></a> | [docs](https://docs.authorizer.dev/deployment/railway) |
|
||||||
|
| Heroku | <a href="https://heroku.com/deploy?template=https://github.com/authorizerdev/authorizer-heroku"><img src="https://www.herokucdn.com/deploy/button.svg" alt="Deploy to Heroku" style="height: 44px;"></a> | [docs](https://docs.authorizer.dev/deployment/heroku) |
|
||||||
# Install instance on railway
|
| Render | [](https://render.com/deploy?repo=https://github.com/authorizerdev/authorizer-render) | [docs](https://docs.authorizer.dev/deployment/render) |
|
||||||
|
|
||||||
Deploy production ready Authorizer instance using [railway.app](https://github.com/authorizerdev/authorizer-railway) with postgres and redis for free and build with it in 30seconds
|
|
||||||
<br/>
|
|
||||||
|
|
||||||
[](https://railway.app/new/template?template=https%3A%2F%2Fgithub.com%2Fauthorizerdev%2Fauthorizer-railway&plugins=postgresql%2Credis&envs=ENV%2CDATABASE_TYPE%2CADMIN_SECRET%2CCOOKIE_NAME%2CJWT_ROLE_CLAIM%2CJWT_TYPE%2CJWT_SECRET%2CFACEBOOK_CLIENT_ID%2CFACEBOOK_CLIENT_SECRET%2CGOOGLE_CLIENT_ID%2CGOOGLE_CLIENT_SECRET%2CGITHUB_CLIENT_ID%2CGITHUB_CLIENT_SECRET%2CALLOWED_ORIGINS%2CROLES%2CPROTECTED_ROLES%2CDEFAULT_ROLES&optionalEnvs=FACEBOOK_CLIENT_ID%2CFACEBOOK_CLIENT_SECRET%2CGOOGLE_CLIENT_ID%2CGOOGLE_CLIENT_SECRET%2CGITHUB_CLIENT_ID%2CGITHUB_CLIENT_SECRET%2CALLOWED_ORIGINS%2CROLES%2CPROTECTED_ROLES%2CDEFAULT_ROLES&ENVDesc=Deployment+environment&DATABASE_TYPEDesc=With+railway+we+are+deploying+postgres+db&ADMIN_SECRETDesc=Secret+to+access+the+admin+apis&COOKIE_NAMEDesc=Name+of+http+only+cookie+that+will+be+used+as+session&FACEBOOK_CLIENT_IDDesc=Facebook+client+ID+for+facebook+login&FACEBOOK_CLIENT_SECRETDesc=Facebook+client+secret+for+facebook+login&GOOGLE_CLIENT_IDDesc=Google+client+ID+for+google+login&GOOGLE_CLIENT_SECRETDesc=Google+client+secret+for+google+login&GITHUB_CLIENT_IDDesc=Github+client+ID+for+github+login&GITHUB_CLIENT_SECRETDesc=Github+client+secret+for+github+login&ALLOWED_ORIGINSDesc=Whitelist+the+URL+for+which+this+instance+of+authorizer+is+allowed&ROLESDesc=Comma+separated+list+of+roles+that+platform+supports.+Default+role+is+user&PROTECTED_ROLESDesc=Comma+separated+list+of+protected+roles+for+which+sign-up+is+disabled&DEFAULT_ROLESDesc=Default+role+that+should+be+assigned+to+user.+It+should+be+one+from+the+list+of+%60ROLES%60+env.+Default+role+is+user&JWT_ROLE_CLAIMDesc=JWT+key+to+be+used+to+validate+the+role+field.&JWT_TYPEDesc=JWT+encryption+type&JWT_SECRETDesc=Random+string+that+will+be+used+for+encrypting+the+JWT+token&ENVDefault=PRODUCTION&DATABASE_TYPEDefault=postgres&COOKIE_NAMEDefault=authorizer&JWT_TYPEDefault=HS256&JWT_ROLE_CLAIMDefault=role)
|
|
||||||
|
|
||||||
### Things to consider
|
### Things to consider
|
||||||
|
|
||||||
|
11
TODO.md
@@ -1,5 +1,16 @@
|
|||||||
# Task List
|
# Task List
|
||||||
|
|
||||||
|
## Implement better way of handling jwt tokens
|
||||||
|
|
||||||
|
Check: https://hasura.io/blog/best-practices-of-using-jwt-with-graphql/#server-side-rendering-ssr
|
||||||
|
|
||||||
|
- [x] Set finger print in response cookie (https://github.com/hasura/jwt-guide/blob/60a7a86146d604fc48a799fffdee712be1c52cd0/lib/setFingerprintCookieAndSignJwt.ts#L8)
|
||||||
|
- [x] Save refresh token in session store
|
||||||
|
- [x] refresh token should be made more secure with the help of secure token rotation. Every time new token is requested new refresh token should be generated
|
||||||
|
- [x] Return jwt in response
|
||||||
|
- [x] To get session send finger print and refresh token [if they are valid -> a new access token is generated and sent to user]
|
||||||
|
- [x] Refresh token should be long living token (refresh token + finger print hash should be verified)
|
||||||
|
|
||||||
## Open ID compatible claims and schema
|
## Open ID compatible claims and schema
|
||||||
|
|
||||||
- [x] Rename `schema.graphqls` and re generate schema
|
- [x] Rename `schema.graphqls` and re generate schema
|
||||||
|
BIN
app/favicon_io/android-chrome-192x192.png
Normal file
After Width: | Height: | Size: 16 KiB |
BIN
app/favicon_io/android-chrome-512x512.png
Normal file
After Width: | Height: | Size: 77 KiB |
BIN
app/favicon_io/apple-touch-icon.png
Normal file
After Width: | Height: | Size: 14 KiB |
BIN
app/favicon_io/favicon-16x16.png
Normal file
After Width: | Height: | Size: 528 B |
BIN
app/favicon_io/favicon-32x32.png
Normal file
After Width: | Height: | Size: 1.1 KiB |
BIN
app/favicon_io/favicon.ico
Normal file
After Width: | Height: | Size: 15 KiB |
884
app/package-lock.json
generated
@@ -1,23 +1,847 @@
|
|||||||
{
|
{
|
||||||
"name": "app",
|
"name": "app",
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"lockfileVersion": 1,
|
"lockfileVersion": 2,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
|
"packages": {
|
||||||
|
"": {
|
||||||
|
"name": "app",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"license": "ISC",
|
||||||
|
"dependencies": {
|
||||||
|
"@authorizerdev/authorizer-react": "latest",
|
||||||
|
"@types/react": "^17.0.15",
|
||||||
|
"@types/react-dom": "^17.0.9",
|
||||||
|
"esbuild": "^0.12.17",
|
||||||
|
"react": "^17.0.2",
|
||||||
|
"react-dom": "^17.0.2",
|
||||||
|
"react-is": "^17.0.2",
|
||||||
|
"react-router-dom": "^5.2.0",
|
||||||
|
"typescript": "^4.3.5"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@types/react-router-dom": "^5.1.8"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@authorizerdev/authorizer-js": {
|
||||||
|
"version": "0.2.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@authorizerdev/authorizer-js/-/authorizer-js-0.2.1.tgz",
|
||||||
|
"integrity": "sha512-5lQlh+nc5xTsPongfTyCSX24A1WESu/BjhmZwUNuScEOGady0qPoDHE3RBf46dpi5v05wbHCDN1IFEalX5zssQ==",
|
||||||
|
"dependencies": {
|
||||||
|
"node-fetch": "^2.6.1"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=10"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@authorizerdev/authorizer-react": {
|
||||||
|
"version": "0.4.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/@authorizerdev/authorizer-react/-/authorizer-react-0.4.3.tgz",
|
||||||
|
"integrity": "sha512-o/wWe9zZ3ARYdjbDfhGfvOxe1YQrE1YQ+UN9pcq85YSDkbfBkOfcnJ4YxlxWdL0Obd/ErDIeQ3vskyrfvRf3sA==",
|
||||||
|
"dependencies": {
|
||||||
|
"@authorizerdev/authorizer-js": "^0.2.1",
|
||||||
|
"final-form": "^4.20.2",
|
||||||
|
"react-final-form": "^6.5.3",
|
||||||
|
"styled-components": "^5.3.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=10"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": ">=16"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@babel/code-frame": {
|
||||||
|
"version": "7.16.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.16.7.tgz",
|
||||||
|
"integrity": "sha512-iAXqUn8IIeBTNd72xsFlgaXHkMBMt6y4HJp1tIaK465CWLT/fG1aqB7ykr95gHHmlBdGbFeWWfyB4NJJ0nmeIg==",
|
||||||
|
"dependencies": {
|
||||||
|
"@babel/highlight": "^7.16.7"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=6.9.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@babel/generator": {
|
||||||
|
"version": "7.16.8",
|
||||||
|
"resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.16.8.tgz",
|
||||||
|
"integrity": "sha512-1ojZwE9+lOXzcWdWmO6TbUzDfqLD39CmEhN8+2cX9XkDo5yW1OpgfejfliysR2AWLpMamTiOiAp/mtroaymhpw==",
|
||||||
|
"dependencies": {
|
||||||
|
"@babel/types": "^7.16.8",
|
||||||
|
"jsesc": "^2.5.1",
|
||||||
|
"source-map": "^0.5.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=6.9.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@babel/helper-annotate-as-pure": {
|
||||||
|
"version": "7.16.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.16.7.tgz",
|
||||||
|
"integrity": "sha512-s6t2w/IPQVTAET1HitoowRGXooX8mCgtuP5195wD/QJPV6wYjpujCGF7JuMODVX2ZAJOf1GT6DT9MHEZvLOFSw==",
|
||||||
|
"dependencies": {
|
||||||
|
"@babel/types": "^7.16.7"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=6.9.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@babel/helper-environment-visitor": {
|
||||||
|
"version": "7.16.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.16.7.tgz",
|
||||||
|
"integrity": "sha512-SLLb0AAn6PkUeAfKJCCOl9e1R53pQlGAfc4y4XuMRZfqeMYLE0dM1LMhqbGAlGQY0lfw5/ohoYWAe9V1yibRag==",
|
||||||
|
"dependencies": {
|
||||||
|
"@babel/types": "^7.16.7"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=6.9.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@babel/helper-function-name": {
|
||||||
|
"version": "7.16.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.16.7.tgz",
|
||||||
|
"integrity": "sha512-QfDfEnIUyyBSR3HtrtGECuZ6DAyCkYFp7GHl75vFtTnn6pjKeK0T1DB5lLkFvBea8MdaiUABx3osbgLyInoejA==",
|
||||||
|
"dependencies": {
|
||||||
|
"@babel/helper-get-function-arity": "^7.16.7",
|
||||||
|
"@babel/template": "^7.16.7",
|
||||||
|
"@babel/types": "^7.16.7"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=6.9.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@babel/helper-get-function-arity": {
|
||||||
|
"version": "7.16.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.16.7.tgz",
|
||||||
|
"integrity": "sha512-flc+RLSOBXzNzVhcLu6ujeHUrD6tANAOU5ojrRx/as+tbzf8+stUCj7+IfRRoAbEZqj/ahXEMsjhOhgeZsrnTw==",
|
||||||
|
"dependencies": {
|
||||||
|
"@babel/types": "^7.16.7"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=6.9.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@babel/helper-hoist-variables": {
|
||||||
|
"version": "7.16.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.16.7.tgz",
|
||||||
|
"integrity": "sha512-m04d/0Op34H5v7pbZw6pSKP7weA6lsMvfiIAMeIvkY/R4xQtBSMFEigu9QTZ2qB/9l22vsxtM8a+Q8CzD255fg==",
|
||||||
|
"dependencies": {
|
||||||
|
"@babel/types": "^7.16.7"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=6.9.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@babel/helper-module-imports": {
|
||||||
|
"version": "7.16.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.16.7.tgz",
|
||||||
|
"integrity": "sha512-LVtS6TqjJHFc+nYeITRo6VLXve70xmq7wPhWTqDJusJEgGmkAACWwMiTNrvfoQo6hEhFwAIixNkvB0jPXDL8Wg==",
|
||||||
|
"dependencies": {
|
||||||
|
"@babel/types": "^7.16.7"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=6.9.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@babel/helper-split-export-declaration": {
|
||||||
|
"version": "7.16.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.16.7.tgz",
|
||||||
|
"integrity": "sha512-xbWoy/PFoxSWazIToT9Sif+jJTlrMcndIsaOKvTA6u7QEo7ilkRZpjew18/W3c7nm8fXdUDXh02VXTbZ0pGDNw==",
|
||||||
|
"dependencies": {
|
||||||
|
"@babel/types": "^7.16.7"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=6.9.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@babel/helper-validator-identifier": {
|
||||||
|
"version": "7.16.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.16.7.tgz",
|
||||||
|
"integrity": "sha512-hsEnFemeiW4D08A5gUAZxLBTXpZ39P+a+DGDsHw1yxqyQ/jzFEnxf5uTEGp+3bzAbNOxU1paTgYS4ECU/IgfDw==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=6.9.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@babel/highlight": {
|
||||||
|
"version": "7.16.10",
|
||||||
|
"resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.16.10.tgz",
|
||||||
|
"integrity": "sha512-5FnTQLSLswEj6IkgVw5KusNUUFY9ZGqe/TRFnP/BKYHYgfh7tc+C7mwiy95/yNP7Dh9x580Vv8r7u7ZfTBFxdw==",
|
||||||
|
"dependencies": {
|
||||||
|
"@babel/helper-validator-identifier": "^7.16.7",
|
||||||
|
"chalk": "^2.0.0",
|
||||||
|
"js-tokens": "^4.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=6.9.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@babel/parser": {
|
||||||
|
"version": "7.16.12",
|
||||||
|
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.16.12.tgz",
|
||||||
|
"integrity": "sha512-VfaV15po8RiZssrkPweyvbGVSe4x2y+aciFCgn0n0/SJMR22cwofRV1mtnJQYcSB1wUTaA/X1LnA3es66MCO5A==",
|
||||||
|
"bin": {
|
||||||
|
"parser": "bin/babel-parser.js"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=6.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@babel/runtime": {
|
||||||
|
"version": "7.14.8",
|
||||||
|
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.14.8.tgz",
|
||||||
|
"integrity": "sha512-twj3L8Og5SaCRCErB4x4ajbvBIVV77CGeFglHpeg5WC5FF8TZzBWXtTJ4MqaD9QszLYTtr+IsaAL2rEUevb+eg==",
|
||||||
|
"dependencies": {
|
||||||
|
"regenerator-runtime": "^0.13.4"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=6.9.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@babel/template": {
|
||||||
|
"version": "7.16.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/@babel/template/-/template-7.16.7.tgz",
|
||||||
|
"integrity": "sha512-I8j/x8kHUrbYRTUxXrrMbfCa7jxkE7tZre39x3kjr9hvI82cK1FfqLygotcWN5kdPGWcLdWMHpSBavse5tWw3w==",
|
||||||
|
"dependencies": {
|
||||||
|
"@babel/code-frame": "^7.16.7",
|
||||||
|
"@babel/parser": "^7.16.7",
|
||||||
|
"@babel/types": "^7.16.7"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=6.9.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@babel/traverse": {
|
||||||
|
"version": "7.16.10",
|
||||||
|
"resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.16.10.tgz",
|
||||||
|
"integrity": "sha512-yzuaYXoRJBGMlBhsMJoUW7G1UmSb/eXr/JHYM/MsOJgavJibLwASijW7oXBdw3NQ6T0bW7Ty5P/VarOs9cHmqw==",
|
||||||
|
"dependencies": {
|
||||||
|
"@babel/code-frame": "^7.16.7",
|
||||||
|
"@babel/generator": "^7.16.8",
|
||||||
|
"@babel/helper-environment-visitor": "^7.16.7",
|
||||||
|
"@babel/helper-function-name": "^7.16.7",
|
||||||
|
"@babel/helper-hoist-variables": "^7.16.7",
|
||||||
|
"@babel/helper-split-export-declaration": "^7.16.7",
|
||||||
|
"@babel/parser": "^7.16.10",
|
||||||
|
"@babel/types": "^7.16.8",
|
||||||
|
"debug": "^4.1.0",
|
||||||
|
"globals": "^11.1.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=6.9.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@babel/types": {
|
||||||
|
"version": "7.16.8",
|
||||||
|
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.16.8.tgz",
|
||||||
|
"integrity": "sha512-smN2DQc5s4M7fntyjGtyIPbRJv6wW4rU/94fmYJ7PKQuZkC0qGMHXJbg6sNGt12JmVr4k5YaptI/XtiLJBnmIg==",
|
||||||
|
"dependencies": {
|
||||||
|
"@babel/helper-validator-identifier": "^7.16.7",
|
||||||
|
"to-fast-properties": "^2.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=6.9.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@emotion/is-prop-valid": {
|
||||||
|
"version": "0.8.8",
|
||||||
|
"resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-0.8.8.tgz",
|
||||||
|
"integrity": "sha512-u5WtneEAr5IDG2Wv65yhunPSMLIpuKsbuOktRojfrEiEvRyC85LgPMZI63cr7NUqT8ZIGdSVg8ZKGxIug4lXcA==",
|
||||||
|
"dependencies": {
|
||||||
|
"@emotion/memoize": "0.7.4"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@emotion/memoize": {
|
||||||
|
"version": "0.7.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.7.4.tgz",
|
||||||
|
"integrity": "sha512-Ja/Vfqe3HpuzRsG1oBtWTHk2PGZ7GR+2Vz5iYGelAw8dx32K0y7PjVuxK6z1nMpZOqAFsRUPCkK1YjJ56qJlgw=="
|
||||||
|
},
|
||||||
|
"node_modules/@emotion/stylis": {
|
||||||
|
"version": "0.8.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/@emotion/stylis/-/stylis-0.8.5.tgz",
|
||||||
|
"integrity": "sha512-h6KtPihKFn3T9fuIrwvXXUOwlx3rfUvfZIcP5a6rh8Y7zjE3O06hT5Ss4S/YI1AYhuZ1kjaE/5EaOOI2NqSylQ=="
|
||||||
|
},
|
||||||
|
"node_modules/@emotion/unitless": {
|
||||||
|
"version": "0.7.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.7.5.tgz",
|
||||||
|
"integrity": "sha512-OWORNpfjMsSSUBVrRBVGECkhWcULOAJz9ZW8uK9qgxD+87M7jHRcvh/A96XXNhXTLmKcoYSQtBEX7lHMO7YRwg=="
|
||||||
|
},
|
||||||
|
"node_modules/@types/history": {
|
||||||
|
"version": "4.7.9",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/history/-/history-4.7.9.tgz",
|
||||||
|
"integrity": "sha512-MUc6zSmU3tEVnkQ78q0peeEjKWPUADMlC/t++2bI8WnAG2tvYRPIgHG8lWkXwqc8MsUF6Z2MOf+Mh5sazOmhiQ==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
|
"node_modules/@types/prop-types": {
|
||||||
|
"version": "15.7.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.4.tgz",
|
||||||
|
"integrity": "sha512-rZ5drC/jWjrArrS8BR6SIr4cWpW09RNTYt9AMZo3Jwwif+iacXAqgVjm0B0Bv/S1jhDXKHqRVNCbACkJ89RAnQ=="
|
||||||
|
},
|
||||||
|
"node_modules/@types/react": {
|
||||||
|
"version": "17.0.15",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/react/-/react-17.0.15.tgz",
|
||||||
|
"integrity": "sha512-uTKHDK9STXFHLaKv6IMnwp52fm0hwU+N89w/p9grdUqcFA6WuqDyPhaWopbNyE1k/VhgzmHl8pu1L4wITtmlLw==",
|
||||||
|
"dependencies": {
|
||||||
|
"@types/prop-types": "*",
|
||||||
|
"@types/scheduler": "*",
|
||||||
|
"csstype": "^3.0.2"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@types/react-dom": {
|
||||||
|
"version": "17.0.9",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-17.0.9.tgz",
|
||||||
|
"integrity": "sha512-wIvGxLfgpVDSAMH5utdL9Ngm5Owu0VsGmldro3ORLXV8CShrL8awVj06NuEXFQ5xyaYfdca7Sgbk/50Ri1GdPg==",
|
||||||
|
"dependencies": {
|
||||||
|
"@types/react": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@types/react-router": {
|
||||||
|
"version": "5.1.16",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/react-router/-/react-router-5.1.16.tgz",
|
||||||
|
"integrity": "sha512-8d7nR/fNSqlTFGHti0R3F9WwIertOaaA1UEB8/jr5l5mDMOs4CidEgvvYMw4ivqrBK+vtVLxyTj2P+Pr/dtgzg==",
|
||||||
|
"dev": true,
|
||||||
|
"dependencies": {
|
||||||
|
"@types/history": "*",
|
||||||
|
"@types/react": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@types/react-router-dom": {
|
||||||
|
"version": "5.1.8",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/react-router-dom/-/react-router-dom-5.1.8.tgz",
|
||||||
|
"integrity": "sha512-03xHyncBzG0PmDmf8pf3rehtjY0NpUj7TIN46FrT5n1ZWHPZvXz32gUyNboJ+xsL8cpg8bQVLcllptcQHvocrw==",
|
||||||
|
"dev": true,
|
||||||
|
"dependencies": {
|
||||||
|
"@types/history": "*",
|
||||||
|
"@types/react": "*",
|
||||||
|
"@types/react-router": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@types/scheduler": {
|
||||||
|
"version": "0.16.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.2.tgz",
|
||||||
|
"integrity": "sha512-hppQEBDmlwhFAXKJX2KnWLYu5yMfi91yazPb2l+lbJiwW+wdo1gNeRA+3RgNSO39WYX2euey41KEwnqesU2Jew=="
|
||||||
|
},
|
||||||
|
"node_modules/ansi-styles": {
|
||||||
|
"version": "3.2.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
|
||||||
|
"integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
|
||||||
|
"dependencies": {
|
||||||
|
"color-convert": "^1.9.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=4"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/babel-plugin-styled-components": {
|
||||||
|
"version": "2.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/babel-plugin-styled-components/-/babel-plugin-styled-components-2.0.2.tgz",
|
||||||
|
"integrity": "sha512-7eG5NE8rChnNTDxa6LQfynwgHTVOYYaHJbUYSlOhk8QBXIQiMBKq4gyfHBBKPrxUcVBXVJL61ihduCpCQbuNbw==",
|
||||||
|
"dependencies": {
|
||||||
|
"@babel/helper-annotate-as-pure": "^7.16.0",
|
||||||
|
"@babel/helper-module-imports": "^7.16.0",
|
||||||
|
"babel-plugin-syntax-jsx": "^6.18.0",
|
||||||
|
"lodash": "^4.17.11"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"styled-components": ">= 2"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/babel-plugin-syntax-jsx": {
|
||||||
|
"version": "6.18.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/babel-plugin-syntax-jsx/-/babel-plugin-syntax-jsx-6.18.0.tgz",
|
||||||
|
"integrity": "sha1-CvMqmm4Tyno/1QaeYtew9Y0NiUY="
|
||||||
|
},
|
||||||
|
"node_modules/camelize": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/camelize/-/camelize-1.0.0.tgz",
|
||||||
|
"integrity": "sha1-FkpUg+Yw+kMh5a8HAg5TGDGyYJs="
|
||||||
|
},
|
||||||
|
"node_modules/chalk": {
|
||||||
|
"version": "2.4.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz",
|
||||||
|
"integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==",
|
||||||
|
"dependencies": {
|
||||||
|
"ansi-styles": "^3.2.1",
|
||||||
|
"escape-string-regexp": "^1.0.5",
|
||||||
|
"supports-color": "^5.3.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=4"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/color-convert": {
|
||||||
|
"version": "1.9.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz",
|
||||||
|
"integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==",
|
||||||
|
"dependencies": {
|
||||||
|
"color-name": "1.1.3"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/color-name": {
|
||||||
|
"version": "1.1.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
|
||||||
|
"integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU="
|
||||||
|
},
|
||||||
|
"node_modules/css-color-keywords": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/css-color-keywords/-/css-color-keywords-1.0.0.tgz",
|
||||||
|
"integrity": "sha1-/qJhbcZ2spYmhrOvjb2+GAskTgU=",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=4"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/css-to-react-native": {
|
||||||
|
"version": "3.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/css-to-react-native/-/css-to-react-native-3.0.0.tgz",
|
||||||
|
"integrity": "sha512-Ro1yETZA813eoyUp2GDBhG2j+YggidUmzO1/v9eYBKR2EHVEniE2MI/NqpTQ954BMpTPZFsGNPm46qFB9dpaPQ==",
|
||||||
|
"dependencies": {
|
||||||
|
"camelize": "^1.0.0",
|
||||||
|
"css-color-keywords": "^1.0.0",
|
||||||
|
"postcss-value-parser": "^4.0.2"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/csstype": {
|
||||||
|
"version": "3.0.8",
|
||||||
|
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.0.8.tgz",
|
||||||
|
"integrity": "sha512-jXKhWqXPmlUeoQnF/EhTtTl4C9SnrxSH/jZUih3jmO6lBKr99rP3/+FmrMj4EFpOXzMtXHAZkd3x0E6h6Fgflw=="
|
||||||
|
},
|
||||||
|
"node_modules/debug": {
|
||||||
|
"version": "4.3.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.3.tgz",
|
||||||
|
"integrity": "sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q==",
|
||||||
|
"dependencies": {
|
||||||
|
"ms": "2.1.2"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=6.0"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"supports-color": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/esbuild": {
|
||||||
|
"version": "0.12.17",
|
||||||
|
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.12.17.tgz",
|
||||||
|
"integrity": "sha512-GshKJyVYUnlSXIZj/NheC2O0Kblh42CS7P1wJyTbbIHevTG4jYMS9NNw8EOd8dDWD0dzydYHS01MpZoUcQXB4g==",
|
||||||
|
"hasInstallScript": true,
|
||||||
|
"bin": {
|
||||||
|
"esbuild": "bin/esbuild"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/escape-string-regexp": {
|
||||||
|
"version": "1.0.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
|
||||||
|
"integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.8.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/final-form": {
|
||||||
|
"version": "4.20.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/final-form/-/final-form-4.20.6.tgz",
|
||||||
|
"integrity": "sha512-fCdwIj49KOaFfDRlXB57Eo+GghIMZQWrA9TakQI3C9uQxHwaFHXqZSNRlUdfnQmNNeySwGOaGPZCvjy58hyv4w==",
|
||||||
|
"dependencies": {
|
||||||
|
"@babel/runtime": "^7.10.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"type": "opencollective",
|
||||||
|
"url": "https://opencollective.com/final-form"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/globals": {
|
||||||
|
"version": "11.12.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz",
|
||||||
|
"integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=4"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/has-flag": {
|
||||||
|
"version": "3.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
|
||||||
|
"integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=4"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/history": {
|
||||||
|
"version": "4.10.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/history/-/history-4.10.1.tgz",
|
||||||
|
"integrity": "sha512-36nwAD620w12kuzPAsyINPWJqlNbij+hpK1k9XRloDtym8mxzGYl2c17LnV6IAGB2Dmg4tEa7G7DlawS0+qjew==",
|
||||||
|
"dependencies": {
|
||||||
|
"@babel/runtime": "^7.1.2",
|
||||||
|
"loose-envify": "^1.2.0",
|
||||||
|
"resolve-pathname": "^3.0.0",
|
||||||
|
"tiny-invariant": "^1.0.2",
|
||||||
|
"tiny-warning": "^1.0.0",
|
||||||
|
"value-equal": "^1.0.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/hoist-non-react-statics": {
|
||||||
|
"version": "3.3.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz",
|
||||||
|
"integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==",
|
||||||
|
"dependencies": {
|
||||||
|
"react-is": "^16.7.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/hoist-non-react-statics/node_modules/react-is": {
|
||||||
|
"version": "16.13.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
|
||||||
|
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="
|
||||||
|
},
|
||||||
|
"node_modules/isarray": {
|
||||||
|
"version": "0.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz",
|
||||||
|
"integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8="
|
||||||
|
},
|
||||||
|
"node_modules/js-tokens": {
|
||||||
|
"version": "4.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
|
||||||
|
"integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="
|
||||||
|
},
|
||||||
|
"node_modules/jsesc": {
|
||||||
|
"version": "2.5.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz",
|
||||||
|
"integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==",
|
||||||
|
"bin": {
|
||||||
|
"jsesc": "bin/jsesc"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=4"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/lodash": {
|
||||||
|
"version": "4.17.21",
|
||||||
|
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
|
||||||
|
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="
|
||||||
|
},
|
||||||
|
"node_modules/loose-envify": {
|
||||||
|
"version": "1.4.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
|
||||||
|
"integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==",
|
||||||
|
"dependencies": {
|
||||||
|
"js-tokens": "^3.0.0 || ^4.0.0"
|
||||||
|
},
|
||||||
|
"bin": {
|
||||||
|
"loose-envify": "cli.js"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/mini-create-react-context": {
|
||||||
|
"version": "0.4.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/mini-create-react-context/-/mini-create-react-context-0.4.1.tgz",
|
||||||
|
"integrity": "sha512-YWCYEmd5CQeHGSAKrYvXgmzzkrvssZcuuQDDeqkT+PziKGMgE+0MCCtcKbROzocGBG1meBLl2FotlRwf4gAzbQ==",
|
||||||
|
"dependencies": {
|
||||||
|
"@babel/runtime": "^7.12.1",
|
||||||
|
"tiny-warning": "^1.0.3"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"prop-types": "^15.0.0",
|
||||||
|
"react": "^0.14.0 || ^15.0.0 || ^16.0.0 || ^17.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/ms": {
|
||||||
|
"version": "2.1.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
|
||||||
|
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
|
||||||
|
},
|
||||||
|
"node_modules/node-fetch": {
|
||||||
|
"version": "2.6.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz",
|
||||||
|
"integrity": "sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==",
|
||||||
|
"dependencies": {
|
||||||
|
"whatwg-url": "^5.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": "4.x || >=6.0.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"encoding": "^0.1.0"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"encoding": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/object-assign": {
|
||||||
|
"version": "4.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
|
||||||
|
"integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.10.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/path-to-regexp": {
|
||||||
|
"version": "1.8.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-1.8.0.tgz",
|
||||||
|
"integrity": "sha512-n43JRhlUKUAlibEJhPeir1ncUID16QnEjNpwzNdO3Lm4ywrBpBZ5oLD0I6br9evr1Y9JTqwRtAh7JLoOzAQdVA==",
|
||||||
|
"dependencies": {
|
||||||
|
"isarray": "0.0.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/postcss-value-parser": {
|
||||||
|
"version": "4.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz",
|
||||||
|
"integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ=="
|
||||||
|
},
|
||||||
|
"node_modules/prop-types": {
|
||||||
|
"version": "15.7.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.7.2.tgz",
|
||||||
|
"integrity": "sha512-8QQikdH7//R2vurIJSutZ1smHYTcLpRWEOlHnzcWHmBYrOGUysKwSsrC89BCiFj3CbrfJ/nXFdJepOVrY1GCHQ==",
|
||||||
|
"dependencies": {
|
||||||
|
"loose-envify": "^1.4.0",
|
||||||
|
"object-assign": "^4.1.1",
|
||||||
|
"react-is": "^16.8.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/prop-types/node_modules/react-is": {
|
||||||
|
"version": "16.13.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
|
||||||
|
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="
|
||||||
|
},
|
||||||
|
"node_modules/react": {
|
||||||
|
"version": "17.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/react/-/react-17.0.2.tgz",
|
||||||
|
"integrity": "sha512-gnhPt75i/dq/z3/6q/0asP78D0u592D5L1pd7M8P+dck6Fu/jJeL6iVVK23fptSUZj8Vjf++7wXA8UNclGQcbA==",
|
||||||
|
"dependencies": {
|
||||||
|
"loose-envify": "^1.1.0",
|
||||||
|
"object-assign": "^4.1.1"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.10.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/react-dom": {
|
||||||
|
"version": "17.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-17.0.2.tgz",
|
||||||
|
"integrity": "sha512-s4h96KtLDUQlsENhMn1ar8t2bEa+q/YAtj8pPPdIjPDGBDIVNsrD9aXNWqspUe6AzKCIG0C1HZZLqLV7qpOBGA==",
|
||||||
|
"dependencies": {
|
||||||
|
"loose-envify": "^1.1.0",
|
||||||
|
"object-assign": "^4.1.1",
|
||||||
|
"scheduler": "^0.20.2"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": "17.0.2"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/react-final-form": {
|
||||||
|
"version": "6.5.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/react-final-form/-/react-final-form-6.5.7.tgz",
|
||||||
|
"integrity": "sha512-o7tvJXB+McGiXOILqIC8lnOcX4aLhIBiF/Xi9Qet35b7XOS8R7KL8HLRKTfnZWQJm6MCE15v1U0SFive0NcxyA==",
|
||||||
|
"dependencies": {
|
||||||
|
"@babel/runtime": "^7.15.4"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"type": "opencollective",
|
||||||
|
"url": "https://opencollective.com/final-form"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"final-form": "4.20.4",
|
||||||
|
"react": "^16.8.0 || ^17.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/react-final-form/node_modules/@babel/runtime": {
|
||||||
|
"version": "7.16.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.16.7.tgz",
|
||||||
|
"integrity": "sha512-9E9FJowqAsytyOY6LG+1KuueckRL+aQW+mKvXRXnuFGyRAyepJPmEo9vgMfXUA6O9u3IeEdv9MAkppFcaQwogQ==",
|
||||||
|
"dependencies": {
|
||||||
|
"regenerator-runtime": "^0.13.4"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=6.9.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/react-is": {
|
||||||
|
"version": "17.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz",
|
||||||
|
"integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w=="
|
||||||
|
},
|
||||||
|
"node_modules/react-router": {
|
||||||
|
"version": "5.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/react-router/-/react-router-5.2.0.tgz",
|
||||||
|
"integrity": "sha512-smz1DUuFHRKdcJC0jobGo8cVbhO3x50tCL4icacOlcwDOEQPq4TMqwx3sY1TP+DvtTgz4nm3thuo7A+BK2U0Dw==",
|
||||||
|
"dependencies": {
|
||||||
|
"@babel/runtime": "^7.1.2",
|
||||||
|
"history": "^4.9.0",
|
||||||
|
"hoist-non-react-statics": "^3.1.0",
|
||||||
|
"loose-envify": "^1.3.1",
|
||||||
|
"mini-create-react-context": "^0.4.0",
|
||||||
|
"path-to-regexp": "^1.7.0",
|
||||||
|
"prop-types": "^15.6.2",
|
||||||
|
"react-is": "^16.6.0",
|
||||||
|
"tiny-invariant": "^1.0.2",
|
||||||
|
"tiny-warning": "^1.0.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": ">=15"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/react-router-dom": {
|
||||||
|
"version": "5.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-5.2.0.tgz",
|
||||||
|
"integrity": "sha512-gxAmfylo2QUjcwxI63RhQ5G85Qqt4voZpUXSEqCwykV0baaOTQDR1f0PmY8AELqIyVc0NEZUj0Gov5lNGcXgsA==",
|
||||||
|
"dependencies": {
|
||||||
|
"@babel/runtime": "^7.1.2",
|
||||||
|
"history": "^4.9.0",
|
||||||
|
"loose-envify": "^1.3.1",
|
||||||
|
"prop-types": "^15.6.2",
|
||||||
|
"react-router": "5.2.0",
|
||||||
|
"tiny-invariant": "^1.0.2",
|
||||||
|
"tiny-warning": "^1.0.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": ">=15"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/react-router/node_modules/react-is": {
|
||||||
|
"version": "16.13.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
|
||||||
|
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="
|
||||||
|
},
|
||||||
|
"node_modules/regenerator-runtime": {
|
||||||
|
"version": "0.13.9",
|
||||||
|
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.9.tgz",
|
||||||
|
"integrity": "sha512-p3VT+cOEgxFsRRA9X4lkI1E+k2/CtnKtU4gcxyaCUreilL/vqI6CdZ3wxVUx3UOUg+gnUOQQcRI7BmSI656MYA=="
|
||||||
|
},
|
||||||
|
"node_modules/resolve-pathname": {
|
||||||
|
"version": "3.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/resolve-pathname/-/resolve-pathname-3.0.0.tgz",
|
||||||
|
"integrity": "sha512-C7rARubxI8bXFNB/hqcp/4iUeIXJhJZvFPFPiSPRnhU5UPxzMFIl+2E6yY6c4k9giDJAhtV+enfA+G89N6Csng=="
|
||||||
|
},
|
||||||
|
"node_modules/scheduler": {
|
||||||
|
"version": "0.20.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.20.2.tgz",
|
||||||
|
"integrity": "sha512-2eWfGgAqqWFGqtdMmcL5zCMK1U8KlXv8SQFGglL3CEtd0aDVDWgeF/YoCmvln55m5zSk3J/20hTaSBeSObsQDQ==",
|
||||||
|
"dependencies": {
|
||||||
|
"loose-envify": "^1.1.0",
|
||||||
|
"object-assign": "^4.1.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/shallowequal": {
|
||||||
|
"version": "1.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/shallowequal/-/shallowequal-1.1.0.tgz",
|
||||||
|
"integrity": "sha512-y0m1JoUZSlPAjXVtPPW70aZWfIL/dSP7AFkRnniLCrK/8MDKog3TySTBmckD+RObVxH0v4Tox67+F14PdED2oQ=="
|
||||||
|
},
|
||||||
|
"node_modules/source-map": {
|
||||||
|
"version": "0.5.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz",
|
||||||
|
"integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.10.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/styled-components": {
|
||||||
|
"version": "5.3.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/styled-components/-/styled-components-5.3.3.tgz",
|
||||||
|
"integrity": "sha512-++4iHwBM7ZN+x6DtPPWkCI4vdtwumQ+inA/DdAsqYd4SVgUKJie5vXyzotA00ttcFdQkCng7zc6grwlfIfw+lw==",
|
||||||
|
"dependencies": {
|
||||||
|
"@babel/helper-module-imports": "^7.0.0",
|
||||||
|
"@babel/traverse": "^7.4.5",
|
||||||
|
"@emotion/is-prop-valid": "^0.8.8",
|
||||||
|
"@emotion/stylis": "^0.8.4",
|
||||||
|
"@emotion/unitless": "^0.7.4",
|
||||||
|
"babel-plugin-styled-components": ">= 1.12.0",
|
||||||
|
"css-to-react-native": "^3.0.0",
|
||||||
|
"hoist-non-react-statics": "^3.0.0",
|
||||||
|
"shallowequal": "^1.1.0",
|
||||||
|
"supports-color": "^5.5.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=10"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"type": "opencollective",
|
||||||
|
"url": "https://opencollective.com/styled-components"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": ">= 16.8.0",
|
||||||
|
"react-dom": ">= 16.8.0",
|
||||||
|
"react-is": ">= 16.8.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/supports-color": {
|
||||||
|
"version": "5.5.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
|
||||||
|
"integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
|
||||||
|
"dependencies": {
|
||||||
|
"has-flag": "^3.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=4"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/tiny-invariant": {
|
||||||
|
"version": "1.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.1.0.tgz",
|
||||||
|
"integrity": "sha512-ytxQvrb1cPc9WBEI/HSeYYoGD0kWnGEOR8RY6KomWLBVhqz0RgTwVO9dLrGz7dC+nN9llyI7OKAgRq8Vq4ZBSw=="
|
||||||
|
},
|
||||||
|
"node_modules/tiny-warning": {
|
||||||
|
"version": "1.0.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/tiny-warning/-/tiny-warning-1.0.3.tgz",
|
||||||
|
"integrity": "sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA=="
|
||||||
|
},
|
||||||
|
"node_modules/to-fast-properties": {
|
||||||
|
"version": "2.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz",
|
||||||
|
"integrity": "sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4=",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=4"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/tr46": {
|
||||||
|
"version": "0.0.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz",
|
||||||
|
"integrity": "sha1-gYT9NH2snNwYWZLzpmIuFLnZq2o="
|
||||||
|
},
|
||||||
|
"node_modules/typescript": {
|
||||||
|
"version": "4.3.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/typescript/-/typescript-4.3.5.tgz",
|
||||||
|
"integrity": "sha512-DqQgihaQ9cUrskJo9kIyW/+g0Vxsk8cDtZ52a3NGh0YNTfpUSArXSohyUGnvbPazEPLu398C0UxmKSOrPumUzA==",
|
||||||
|
"bin": {
|
||||||
|
"tsc": "bin/tsc",
|
||||||
|
"tsserver": "bin/tsserver"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=4.2.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/value-equal": {
|
||||||
|
"version": "1.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/value-equal/-/value-equal-1.0.1.tgz",
|
||||||
|
"integrity": "sha512-NOJ6JZCAWr0zlxZt+xqCHNTEKOsrks2HQd4MqhP1qy4z1SkbEP467eNx6TgDKXMvUOb+OENfJCZwM+16n7fRfw=="
|
||||||
|
},
|
||||||
|
"node_modules/webidl-conversions": {
|
||||||
|
"version": "3.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz",
|
||||||
|
"integrity": "sha1-JFNCdeKnvGvnvIZhHMFq4KVlSHE="
|
||||||
|
},
|
||||||
|
"node_modules/whatwg-url": {
|
||||||
|
"version": "5.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz",
|
||||||
|
"integrity": "sha1-lmRU6HZUYuN2RNNib2dCzotwll0=",
|
||||||
|
"dependencies": {
|
||||||
|
"tr46": "~0.0.3",
|
||||||
|
"webidl-conversions": "^3.0.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@authorizerdev/authorizer-js": {
|
"@authorizerdev/authorizer-js": {
|
||||||
"version": "0.2.0",
|
"version": "0.2.1",
|
||||||
"resolved": "https://registry.npmjs.org/@authorizerdev/authorizer-js/-/authorizer-js-0.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/@authorizerdev/authorizer-js/-/authorizer-js-0.2.1.tgz",
|
||||||
"integrity": "sha512-T2gurEYEP1T56j9IwDIWP1PsELoWcU7TBl5G0UsMqFQhKo7T6p2hM634Z7PS1yLegU3aihuvHGI0C0n4xSo0VQ==",
|
"integrity": "sha512-5lQlh+nc5xTsPongfTyCSX24A1WESu/BjhmZwUNuScEOGady0qPoDHE3RBf46dpi5v05wbHCDN1IFEalX5zssQ==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"node-fetch": "^2.6.1"
|
"node-fetch": "^2.6.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@authorizerdev/authorizer-react": {
|
"@authorizerdev/authorizer-react": {
|
||||||
"version": "0.4.0",
|
"version": "0.4.3",
|
||||||
"resolved": "https://registry.npmjs.org/@authorizerdev/authorizer-react/-/authorizer-react-0.4.0.tgz",
|
"resolved": "https://registry.npmjs.org/@authorizerdev/authorizer-react/-/authorizer-react-0.4.3.tgz",
|
||||||
"integrity": "sha512-ydE7xrP3cqeTtU923bK0+OIB1fnL0VHnbThcNa41n89XUPV3VBhZ23gxMg90nqon8JFE5g2TNz7+/qBCOQ7aZw==",
|
"integrity": "sha512-o/wWe9zZ3ARYdjbDfhGfvOxe1YQrE1YQ+UN9pcq85YSDkbfBkOfcnJ4YxlxWdL0Obd/ErDIeQ3vskyrfvRf3sA==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"@authorizerdev/authorizer-js": "^0.2.0",
|
"@authorizerdev/authorizer-js": "^0.2.1",
|
||||||
"final-form": "^4.20.2",
|
"final-form": "^4.20.2",
|
||||||
"react-final-form": "^6.5.3",
|
"react-final-form": "^6.5.3",
|
||||||
"styled-components": "^5.3.0"
|
"styled-components": "^5.3.0"
|
||||||
@@ -32,11 +856,11 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@babel/generator": {
|
"@babel/generator": {
|
||||||
"version": "7.16.7",
|
"version": "7.16.8",
|
||||||
"resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.16.7.tgz",
|
"resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.16.8.tgz",
|
||||||
"integrity": "sha512-/ST3Sg8MLGY5HVYmrjOgL60ENux/HfO/CsUh7y4MalThufhE/Ff/6EibFDHi4jiDCaWfJKoqbE6oTh21c5hrRg==",
|
"integrity": "sha512-1ojZwE9+lOXzcWdWmO6TbUzDfqLD39CmEhN8+2cX9XkDo5yW1OpgfejfliysR2AWLpMamTiOiAp/mtroaymhpw==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"@babel/types": "^7.16.7",
|
"@babel/types": "^7.16.8",
|
||||||
"jsesc": "^2.5.1",
|
"jsesc": "^2.5.1",
|
||||||
"source-map": "^0.5.0"
|
"source-map": "^0.5.0"
|
||||||
}
|
}
|
||||||
@@ -105,9 +929,9 @@
|
|||||||
"integrity": "sha512-hsEnFemeiW4D08A5gUAZxLBTXpZ39P+a+DGDsHw1yxqyQ/jzFEnxf5uTEGp+3bzAbNOxU1paTgYS4ECU/IgfDw=="
|
"integrity": "sha512-hsEnFemeiW4D08A5gUAZxLBTXpZ39P+a+DGDsHw1yxqyQ/jzFEnxf5uTEGp+3bzAbNOxU1paTgYS4ECU/IgfDw=="
|
||||||
},
|
},
|
||||||
"@babel/highlight": {
|
"@babel/highlight": {
|
||||||
"version": "7.16.7",
|
"version": "7.16.10",
|
||||||
"resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.16.7.tgz",
|
"resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.16.10.tgz",
|
||||||
"integrity": "sha512-aKpPMfLvGO3Q97V0qhw/V2SWNWlwfJknuwAunU7wZLSfrM4xTBvg7E5opUVi1kJTBKihE38CPg4nBiqX83PWYw==",
|
"integrity": "sha512-5FnTQLSLswEj6IkgVw5KusNUUFY9ZGqe/TRFnP/BKYHYgfh7tc+C7mwiy95/yNP7Dh9x580Vv8r7u7ZfTBFxdw==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"@babel/helper-validator-identifier": "^7.16.7",
|
"@babel/helper-validator-identifier": "^7.16.7",
|
||||||
"chalk": "^2.0.0",
|
"chalk": "^2.0.0",
|
||||||
@@ -115,9 +939,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@babel/parser": {
|
"@babel/parser": {
|
||||||
"version": "7.16.7",
|
"version": "7.16.12",
|
||||||
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.16.7.tgz",
|
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.16.12.tgz",
|
||||||
"integrity": "sha512-sR4eaSrnM7BV7QPzGfEX5paG/6wrZM3I0HDzfIAK06ESvo9oy3xBuVBxE3MbQaKNhvg8g/ixjMWo2CGpzpHsDA=="
|
"integrity": "sha512-VfaV15po8RiZssrkPweyvbGVSe4x2y+aciFCgn0n0/SJMR22cwofRV1mtnJQYcSB1wUTaA/X1LnA3es66MCO5A=="
|
||||||
},
|
},
|
||||||
"@babel/runtime": {
|
"@babel/runtime": {
|
||||||
"version": "7.14.8",
|
"version": "7.14.8",
|
||||||
@@ -138,26 +962,26 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@babel/traverse": {
|
"@babel/traverse": {
|
||||||
"version": "7.16.7",
|
"version": "7.16.10",
|
||||||
"resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.16.7.tgz",
|
"resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.16.10.tgz",
|
||||||
"integrity": "sha512-8KWJPIb8c2VvY8AJrydh6+fVRo2ODx1wYBU2398xJVq0JomuLBZmVQzLPBblJgHIGYG4znCpUZUZ0Pt2vdmVYQ==",
|
"integrity": "sha512-yzuaYXoRJBGMlBhsMJoUW7G1UmSb/eXr/JHYM/MsOJgavJibLwASijW7oXBdw3NQ6T0bW7Ty5P/VarOs9cHmqw==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"@babel/code-frame": "^7.16.7",
|
"@babel/code-frame": "^7.16.7",
|
||||||
"@babel/generator": "^7.16.7",
|
"@babel/generator": "^7.16.8",
|
||||||
"@babel/helper-environment-visitor": "^7.16.7",
|
"@babel/helper-environment-visitor": "^7.16.7",
|
||||||
"@babel/helper-function-name": "^7.16.7",
|
"@babel/helper-function-name": "^7.16.7",
|
||||||
"@babel/helper-hoist-variables": "^7.16.7",
|
"@babel/helper-hoist-variables": "^7.16.7",
|
||||||
"@babel/helper-split-export-declaration": "^7.16.7",
|
"@babel/helper-split-export-declaration": "^7.16.7",
|
||||||
"@babel/parser": "^7.16.7",
|
"@babel/parser": "^7.16.10",
|
||||||
"@babel/types": "^7.16.7",
|
"@babel/types": "^7.16.8",
|
||||||
"debug": "^4.1.0",
|
"debug": "^4.1.0",
|
||||||
"globals": "^11.1.0"
|
"globals": "^11.1.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@babel/types": {
|
"@babel/types": {
|
||||||
"version": "7.16.7",
|
"version": "7.16.8",
|
||||||
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.16.7.tgz",
|
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.16.8.tgz",
|
||||||
"integrity": "sha512-E8HuV7FO9qLpx6OtoGfUQ2cjIYnbFwvZWYBS+87EwtdMvmUPJSwykpovFB+8insbpF0uJcpr8KMUi64XZntZcg==",
|
"integrity": "sha512-smN2DQc5s4M7fntyjGtyIPbRJv6wW4rU/94fmYJ7PKQuZkC0qGMHXJbg6sNGt12JmVr4k5YaptI/XtiLJBnmIg==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"@babel/helper-validator-identifier": "^7.16.7",
|
"@babel/helper-validator-identifier": "^7.16.7",
|
||||||
"to-fast-properties": "^2.0.0"
|
"to-fast-properties": "^2.0.0"
|
||||||
@@ -420,9 +1244,9 @@
|
|||||||
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
|
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
|
||||||
},
|
},
|
||||||
"node-fetch": {
|
"node-fetch": {
|
||||||
"version": "2.6.6",
|
"version": "2.6.7",
|
||||||
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.6.tgz",
|
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz",
|
||||||
"integrity": "sha512-Z8/6vRlTUChSdIgMa51jxQ4lrw/Jy5SOW10ObaA47/RElsAN2c5Pn8bTgFGWn/ibwzXTE8qwr1Yzx28vsecXEA==",
|
"integrity": "sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"whatwg-url": "^5.0.0"
|
"whatwg-url": "^5.0.0"
|
||||||
}
|
}
|
||||||
|
@@ -15,7 +15,7 @@ export default function Dashboard() {
|
|||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<h1>Hey 👋,</h1>
|
<h1>Hey 👋,</h1>
|
||||||
<p>Thank you for joining authorizer demo app.</p>
|
<p>Thank you for using authorizer.</p>
|
||||||
<p>
|
<p>
|
||||||
Your email address is{' '}
|
Your email address is{' '}
|
||||||
<a href={`mailto:${user?.email}`} style={{ color: '#3B82F6' }}>
|
<a href={`mailto:${user?.email}`} style={{ color: '#3B82F6' }}>
|
||||||
|
BIN
dashboard/favicon_io/android-chrome-192x192.png
Normal file
After Width: | Height: | Size: 16 KiB |
BIN
dashboard/favicon_io/android-chrome-512x512.png
Normal file
After Width: | Height: | Size: 77 KiB |
BIN
dashboard/favicon_io/apple-touch-icon.png
Normal file
After Width: | Height: | Size: 14 KiB |
BIN
dashboard/favicon_io/favicon-16x16.png
Normal file
After Width: | Height: | Size: 528 B |
BIN
dashboard/favicon_io/favicon-32x32.png
Normal file
After Width: | Height: | Size: 1.1 KiB |
BIN
dashboard/favicon_io/favicon.ico
Normal file
After Width: | Height: | Size: 15 KiB |
2320
dashboard/package-lock.json
generated
@@ -17,9 +17,11 @@
|
|||||||
"@types/react": "^17.0.38",
|
"@types/react": "^17.0.38",
|
||||||
"@types/react-dom": "^17.0.11",
|
"@types/react-dom": "^17.0.11",
|
||||||
"@types/react-router-dom": "^5.3.2",
|
"@types/react-router-dom": "^5.3.2",
|
||||||
|
"dayjs": "^1.10.7",
|
||||||
"esbuild": "^0.14.9",
|
"esbuild": "^0.14.9",
|
||||||
"framer-motion": "^5.5.5",
|
"framer-motion": "^5.5.5",
|
||||||
"graphql": "^16.2.0",
|
"graphql": "^16.2.0",
|
||||||
|
"lodash": "^4.17.21",
|
||||||
"react": "^17.0.2",
|
"react": "^17.0.2",
|
||||||
"react-dom": "^17.0.2",
|
"react-dom": "^17.0.2",
|
||||||
"react-icons": "^4.3.1",
|
"react-icons": "^4.3.1",
|
||||||
|
@@ -12,6 +12,7 @@ const queryClient = createClient({
|
|||||||
credentials: 'include',
|
credentials: 'include',
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
requestPolicy: 'network-only',
|
||||||
});
|
});
|
||||||
|
|
||||||
const theme = extendTheme({
|
const theme = extendTheme({
|
||||||
|
112
dashboard/src/components/DeleteUserModal.tsx
Normal file
@@ -0,0 +1,112 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import {
|
||||||
|
Button,
|
||||||
|
Center,
|
||||||
|
Flex,
|
||||||
|
MenuItem,
|
||||||
|
Modal,
|
||||||
|
ModalBody,
|
||||||
|
ModalCloseButton,
|
||||||
|
ModalContent,
|
||||||
|
ModalFooter,
|
||||||
|
ModalHeader,
|
||||||
|
ModalOverlay,
|
||||||
|
useDisclosure,
|
||||||
|
Text,
|
||||||
|
useToast,
|
||||||
|
} from '@chakra-ui/react';
|
||||||
|
import { useClient } from 'urql';
|
||||||
|
import { FaRegTrashAlt } from 'react-icons/fa';
|
||||||
|
import { DeleteUser } from '../graphql/mutation';
|
||||||
|
import { capitalizeFirstLetter } from '../utils';
|
||||||
|
|
||||||
|
interface userDataTypes {
|
||||||
|
id: string;
|
||||||
|
email: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const DeleteUserModal = ({
|
||||||
|
user,
|
||||||
|
updateUserList,
|
||||||
|
}: {
|
||||||
|
user: userDataTypes;
|
||||||
|
updateUserList: Function;
|
||||||
|
}) => {
|
||||||
|
const client = useClient();
|
||||||
|
const toast = useToast();
|
||||||
|
const { isOpen, onOpen, onClose } = useDisclosure();
|
||||||
|
const [userData, setUserData] = React.useState<userDataTypes>({
|
||||||
|
id: '',
|
||||||
|
email: '',
|
||||||
|
});
|
||||||
|
React.useEffect(() => {
|
||||||
|
setUserData(user);
|
||||||
|
}, []);
|
||||||
|
const deleteHandler = async () => {
|
||||||
|
const res = await client
|
||||||
|
.mutation(DeleteUser, { params: { email: userData.email } })
|
||||||
|
.toPromise();
|
||||||
|
if (res.error) {
|
||||||
|
toast({
|
||||||
|
title: capitalizeFirstLetter(res.error.message),
|
||||||
|
isClosable: true,
|
||||||
|
status: 'error',
|
||||||
|
position: 'bottom-right',
|
||||||
|
});
|
||||||
|
|
||||||
|
return;
|
||||||
|
} else if (res.data?._delete_user) {
|
||||||
|
toast({
|
||||||
|
title: capitalizeFirstLetter(res.data?._delete_user.message),
|
||||||
|
isClosable: true,
|
||||||
|
status: 'success',
|
||||||
|
position: 'bottom-right',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
onClose();
|
||||||
|
updateUserList();
|
||||||
|
};
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<MenuItem onClick={onOpen}>Delete User</MenuItem>
|
||||||
|
<Modal isOpen={isOpen} onClose={onClose}>
|
||||||
|
<ModalOverlay />
|
||||||
|
<ModalContent>
|
||||||
|
<ModalHeader>Delete User</ModalHeader>
|
||||||
|
<ModalCloseButton />
|
||||||
|
<ModalBody>
|
||||||
|
<Text fontSize="md">Are you sure?</Text>
|
||||||
|
<Flex
|
||||||
|
padding="5%"
|
||||||
|
marginTop="5%"
|
||||||
|
marginBottom="2%"
|
||||||
|
border="1px solid #ff7875"
|
||||||
|
borderRadius="5px"
|
||||||
|
flexDirection="column"
|
||||||
|
>
|
||||||
|
<Text fontSize="sm">
|
||||||
|
User <b>{user.email}</b> will be deleted permanently!
|
||||||
|
</Text>
|
||||||
|
</Flex>
|
||||||
|
</ModalBody>
|
||||||
|
|
||||||
|
<ModalFooter>
|
||||||
|
<Button
|
||||||
|
leftIcon={<FaRegTrashAlt />}
|
||||||
|
colorScheme="red"
|
||||||
|
variant="solid"
|
||||||
|
onClick={deleteHandler}
|
||||||
|
isDisabled={false}
|
||||||
|
>
|
||||||
|
<Center h="100%" pt="5%">
|
||||||
|
Delete
|
||||||
|
</Center>
|
||||||
|
</Button>
|
||||||
|
</ModalFooter>
|
||||||
|
</ModalContent>
|
||||||
|
</Modal>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default DeleteUserModal;
|
250
dashboard/src/components/EditUserModal.tsx
Normal file
@@ -0,0 +1,250 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import {
|
||||||
|
Button,
|
||||||
|
Center,
|
||||||
|
Flex,
|
||||||
|
MenuItem,
|
||||||
|
Modal,
|
||||||
|
ModalBody,
|
||||||
|
ModalCloseButton,
|
||||||
|
ModalContent,
|
||||||
|
ModalFooter,
|
||||||
|
ModalHeader,
|
||||||
|
ModalOverlay,
|
||||||
|
Stack,
|
||||||
|
useDisclosure,
|
||||||
|
Text,
|
||||||
|
useToast,
|
||||||
|
} from '@chakra-ui/react';
|
||||||
|
import { useClient } from 'urql';
|
||||||
|
import { FaSave } from 'react-icons/fa';
|
||||||
|
import InputField from './InputField';
|
||||||
|
import {
|
||||||
|
ArrayInputType,
|
||||||
|
DateInputType,
|
||||||
|
SelectInputType,
|
||||||
|
TextInputType,
|
||||||
|
} from '../constants';
|
||||||
|
import { getObjectDiff } from '../utils';
|
||||||
|
import { UpdateUser } from '../graphql/mutation';
|
||||||
|
|
||||||
|
const GenderTypes = {
|
||||||
|
Undisclosed: null,
|
||||||
|
Male: 'Male',
|
||||||
|
Female: 'Female',
|
||||||
|
};
|
||||||
|
|
||||||
|
interface userDataTypes {
|
||||||
|
id: string;
|
||||||
|
email: string;
|
||||||
|
given_name: string;
|
||||||
|
family_name: string;
|
||||||
|
middle_name: string;
|
||||||
|
nickname: string;
|
||||||
|
gender: string;
|
||||||
|
birthdate: string;
|
||||||
|
phone_number: string;
|
||||||
|
picture: string;
|
||||||
|
roles: [string] | [];
|
||||||
|
}
|
||||||
|
|
||||||
|
const EditUserModal = ({
|
||||||
|
user,
|
||||||
|
updateUserList,
|
||||||
|
}: {
|
||||||
|
user: userDataTypes;
|
||||||
|
updateUserList: Function;
|
||||||
|
}) => {
|
||||||
|
const client = useClient();
|
||||||
|
const toast = useToast();
|
||||||
|
const { isOpen, onOpen, onClose } = useDisclosure();
|
||||||
|
const [userData, setUserData] = React.useState<userDataTypes>({
|
||||||
|
id: '',
|
||||||
|
email: '',
|
||||||
|
given_name: '',
|
||||||
|
family_name: '',
|
||||||
|
middle_name: '',
|
||||||
|
nickname: '',
|
||||||
|
gender: '',
|
||||||
|
birthdate: '',
|
||||||
|
phone_number: '',
|
||||||
|
picture: '',
|
||||||
|
roles: [],
|
||||||
|
});
|
||||||
|
React.useEffect(() => {
|
||||||
|
setUserData(user);
|
||||||
|
}, []);
|
||||||
|
const saveHandler = async () => {
|
||||||
|
const diff = getObjectDiff(user, userData);
|
||||||
|
const updatedUserData = diff.reduce(
|
||||||
|
(acc: any, property: string) => ({
|
||||||
|
...acc,
|
||||||
|
// @ts-ignore
|
||||||
|
[property]: userData[property],
|
||||||
|
}),
|
||||||
|
{}
|
||||||
|
);
|
||||||
|
const res = await client
|
||||||
|
.mutation(UpdateUser, { params: { ...updatedUserData, id: userData.id } })
|
||||||
|
.toPromise();
|
||||||
|
if (res.error) {
|
||||||
|
toast({
|
||||||
|
title: 'User data update failed',
|
||||||
|
isClosable: true,
|
||||||
|
status: 'error',
|
||||||
|
position: 'bottom-right',
|
||||||
|
});
|
||||||
|
} else if (res.data?._update_user?.id) {
|
||||||
|
toast({
|
||||||
|
title: 'User data update successful',
|
||||||
|
isClosable: true,
|
||||||
|
status: 'success',
|
||||||
|
position: 'bottom-right',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
onClose();
|
||||||
|
updateUserList();
|
||||||
|
};
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<MenuItem onClick={onOpen}>Edit User Details</MenuItem>
|
||||||
|
<Modal isOpen={isOpen} onClose={onClose}>
|
||||||
|
<ModalOverlay />
|
||||||
|
<ModalContent>
|
||||||
|
<ModalHeader>Edit User Details</ModalHeader>
|
||||||
|
<ModalCloseButton />
|
||||||
|
<ModalBody>
|
||||||
|
<Stack>
|
||||||
|
<Flex>
|
||||||
|
<Flex w="30%" justifyContent="start" alignItems="center">
|
||||||
|
<Text fontSize="sm">Given Name:</Text>
|
||||||
|
</Flex>
|
||||||
|
<Center w="70%">
|
||||||
|
<InputField
|
||||||
|
variables={userData}
|
||||||
|
setVariables={setUserData}
|
||||||
|
inputType={TextInputType.GIVEN_NAME}
|
||||||
|
/>
|
||||||
|
</Center>
|
||||||
|
</Flex>
|
||||||
|
<Flex>
|
||||||
|
<Flex w="30%" justifyContent="start" alignItems="center">
|
||||||
|
<Text fontSize="sm">Middle Name:</Text>
|
||||||
|
</Flex>
|
||||||
|
<Center w="70%">
|
||||||
|
<InputField
|
||||||
|
variables={userData}
|
||||||
|
setVariables={setUserData}
|
||||||
|
inputType={TextInputType.MIDDLE_NAME}
|
||||||
|
/>
|
||||||
|
</Center>
|
||||||
|
</Flex>
|
||||||
|
<Flex>
|
||||||
|
<Flex w="30%" justifyContent="start" alignItems="center">
|
||||||
|
<Text fontSize="sm">Family Name:</Text>
|
||||||
|
</Flex>
|
||||||
|
<Center w="70%">
|
||||||
|
<InputField
|
||||||
|
variables={userData}
|
||||||
|
setVariables={setUserData}
|
||||||
|
inputType={TextInputType.FAMILY_NAME}
|
||||||
|
/>
|
||||||
|
</Center>
|
||||||
|
</Flex>
|
||||||
|
<Flex>
|
||||||
|
<Flex w="30%" justifyContent="start" alignItems="center">
|
||||||
|
<Text fontSize="sm">Birth Date:</Text>
|
||||||
|
</Flex>
|
||||||
|
<Center w="70%">
|
||||||
|
<InputField
|
||||||
|
variables={userData}
|
||||||
|
setVariables={setUserData}
|
||||||
|
inputType={DateInputType.BIRTHDATE}
|
||||||
|
/>
|
||||||
|
</Center>
|
||||||
|
</Flex>
|
||||||
|
<Flex>
|
||||||
|
<Flex w="30%" justifyContent="start" alignItems="center">
|
||||||
|
<Text fontSize="sm">Nickname:</Text>
|
||||||
|
</Flex>
|
||||||
|
<Center w="70%">
|
||||||
|
<InputField
|
||||||
|
variables={userData}
|
||||||
|
setVariables={setUserData}
|
||||||
|
inputType={TextInputType.NICKNAME}
|
||||||
|
/>
|
||||||
|
</Center>
|
||||||
|
</Flex>
|
||||||
|
<Flex>
|
||||||
|
<Flex w="30%" justifyContent="start" alignItems="center">
|
||||||
|
<Text fontSize="sm">Gender:</Text>
|
||||||
|
</Flex>
|
||||||
|
<Center w="70%">
|
||||||
|
<InputField
|
||||||
|
variables={userData}
|
||||||
|
setVariables={setUserData}
|
||||||
|
inputType={SelectInputType.GENDER}
|
||||||
|
value={userData.gender}
|
||||||
|
options={GenderTypes}
|
||||||
|
/>
|
||||||
|
</Center>
|
||||||
|
</Flex>
|
||||||
|
<Flex>
|
||||||
|
<Flex w="30%" justifyContent="start" alignItems="center">
|
||||||
|
<Text fontSize="sm">Phone Number:</Text>
|
||||||
|
</Flex>
|
||||||
|
<Center w="70%">
|
||||||
|
<InputField
|
||||||
|
variables={userData}
|
||||||
|
setVariables={setUserData}
|
||||||
|
inputType={TextInputType.PHONE_NUMBER}
|
||||||
|
/>
|
||||||
|
</Center>
|
||||||
|
</Flex>
|
||||||
|
<Flex>
|
||||||
|
<Flex w="30%" justifyContent="start" alignItems="center">
|
||||||
|
<Text fontSize="sm">Picture:</Text>
|
||||||
|
</Flex>
|
||||||
|
<Center w="70%">
|
||||||
|
<InputField
|
||||||
|
variables={userData}
|
||||||
|
setVariables={setUserData}
|
||||||
|
inputType={TextInputType.PICTURE}
|
||||||
|
/>
|
||||||
|
</Center>
|
||||||
|
</Flex>
|
||||||
|
<Flex>
|
||||||
|
<Flex w="30%" justifyContent="start" alignItems="center">
|
||||||
|
<Text fontSize="sm">Roles:</Text>
|
||||||
|
</Flex>
|
||||||
|
<Center w="70%">
|
||||||
|
<InputField
|
||||||
|
variables={userData}
|
||||||
|
setVariables={setUserData}
|
||||||
|
inputType={ArrayInputType.USER_ROLES}
|
||||||
|
/>
|
||||||
|
</Center>
|
||||||
|
</Flex>
|
||||||
|
</Stack>
|
||||||
|
</ModalBody>
|
||||||
|
|
||||||
|
<ModalFooter>
|
||||||
|
<Button
|
||||||
|
leftIcon={<FaSave />}
|
||||||
|
colorScheme="blue"
|
||||||
|
variant="solid"
|
||||||
|
onClick={saveHandler}
|
||||||
|
isDisabled={false}
|
||||||
|
>
|
||||||
|
<Center h="100%" pt="5%">
|
||||||
|
Save
|
||||||
|
</Center>
|
||||||
|
</Button>
|
||||||
|
</ModalFooter>
|
||||||
|
</ModalContent>
|
||||||
|
</Modal>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default EditUserModal;
|
338
dashboard/src/components/InputField.tsx
Normal file
@@ -0,0 +1,338 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import {
|
||||||
|
Box,
|
||||||
|
Flex,
|
||||||
|
Input,
|
||||||
|
Center,
|
||||||
|
InputGroup,
|
||||||
|
InputRightElement,
|
||||||
|
Tag,
|
||||||
|
TagLabel,
|
||||||
|
TagRightIcon,
|
||||||
|
Select,
|
||||||
|
Textarea,
|
||||||
|
Switch,
|
||||||
|
Code,
|
||||||
|
} from '@chakra-ui/react';
|
||||||
|
import {
|
||||||
|
FaRegClone,
|
||||||
|
FaRegEye,
|
||||||
|
FaRegEyeSlash,
|
||||||
|
FaPlus,
|
||||||
|
FaTimes,
|
||||||
|
} from 'react-icons/fa';
|
||||||
|
import {
|
||||||
|
ArrayInputOperations,
|
||||||
|
ArrayInputType,
|
||||||
|
SelectInputType,
|
||||||
|
HiddenInputType,
|
||||||
|
TextInputType,
|
||||||
|
TextAreaInputType,
|
||||||
|
SwitchInputType,
|
||||||
|
DateInputType,
|
||||||
|
} from '../constants';
|
||||||
|
import { copyTextToClipboard } from '../utils';
|
||||||
|
|
||||||
|
const InputField = ({
|
||||||
|
inputType,
|
||||||
|
variables,
|
||||||
|
setVariables,
|
||||||
|
fieldVisibility,
|
||||||
|
setFieldVisibility,
|
||||||
|
...downshiftProps
|
||||||
|
}: any) => {
|
||||||
|
const props = {
|
||||||
|
size: 'sm',
|
||||||
|
...downshiftProps,
|
||||||
|
};
|
||||||
|
const [inputFieldVisibility, setInputFieldVisibility] = React.useState<
|
||||||
|
Record<string, boolean>
|
||||||
|
>({
|
||||||
|
ROLES: false,
|
||||||
|
DEFAULT_ROLES: false,
|
||||||
|
PROTECTED_ROLES: false,
|
||||||
|
ALLOWED_ORIGINS: false,
|
||||||
|
roles: false,
|
||||||
|
});
|
||||||
|
const [inputData, setInputData] = React.useState<Record<string, string>>({
|
||||||
|
ROLES: '',
|
||||||
|
DEFAULT_ROLES: '',
|
||||||
|
PROTECTED_ROLES: '',
|
||||||
|
ALLOWED_ORIGINS: '',
|
||||||
|
roles: '',
|
||||||
|
});
|
||||||
|
const updateInputHandler = (
|
||||||
|
type: string,
|
||||||
|
operation: any,
|
||||||
|
role: string = ''
|
||||||
|
) => {
|
||||||
|
if (operation === ArrayInputOperations.APPEND) {
|
||||||
|
if (inputData[type] !== '') {
|
||||||
|
setVariables({
|
||||||
|
...variables,
|
||||||
|
[type]: [...variables[type], inputData[type]],
|
||||||
|
});
|
||||||
|
setInputData({ ...inputData, [type]: '' });
|
||||||
|
}
|
||||||
|
setInputFieldVisibility({ ...inputFieldVisibility, [type]: false });
|
||||||
|
}
|
||||||
|
if (operation === ArrayInputOperations.REMOVE) {
|
||||||
|
let updatedEnvVars = variables[type].filter(
|
||||||
|
(item: string) => item !== role
|
||||||
|
);
|
||||||
|
setVariables({
|
||||||
|
...variables,
|
||||||
|
[type]: updatedEnvVars,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
if (Object.values(TextInputType).includes(inputType)) {
|
||||||
|
return (
|
||||||
|
<InputGroup size="sm">
|
||||||
|
<Input
|
||||||
|
{...props}
|
||||||
|
value={variables[inputType] ? variables[inputType] : ''}
|
||||||
|
onChange={(
|
||||||
|
event: Event & {
|
||||||
|
target: HTMLInputElement;
|
||||||
|
}
|
||||||
|
) =>
|
||||||
|
setVariables({
|
||||||
|
...variables,
|
||||||
|
[inputType]: event.target.value,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<InputRightElement
|
||||||
|
children={<FaRegClone color="#bfbfbf" />}
|
||||||
|
cursor="pointer"
|
||||||
|
onClick={() => copyTextToClipboard(variables[inputType])}
|
||||||
|
/>
|
||||||
|
</InputGroup>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (Object.values(HiddenInputType).includes(inputType)) {
|
||||||
|
return (
|
||||||
|
<InputGroup size="sm">
|
||||||
|
<Input
|
||||||
|
{...props}
|
||||||
|
value={variables[inputType]}
|
||||||
|
onChange={(
|
||||||
|
event: Event & {
|
||||||
|
target: HTMLInputElement;
|
||||||
|
}
|
||||||
|
) =>
|
||||||
|
setVariables({
|
||||||
|
...variables,
|
||||||
|
[inputType]: event.target.value,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
type={!fieldVisibility[inputType] ? 'password' : 'text'}
|
||||||
|
/>
|
||||||
|
<InputRightElement
|
||||||
|
right="15px"
|
||||||
|
children={
|
||||||
|
<Flex>
|
||||||
|
{fieldVisibility[inputType] ? (
|
||||||
|
<Center
|
||||||
|
w="25px"
|
||||||
|
margin="0 1.5%"
|
||||||
|
cursor="pointer"
|
||||||
|
onClick={() =>
|
||||||
|
setFieldVisibility({
|
||||||
|
...fieldVisibility,
|
||||||
|
[inputType]: false,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<FaRegEyeSlash color="#bfbfbf" />
|
||||||
|
</Center>
|
||||||
|
) : (
|
||||||
|
<Center
|
||||||
|
w="25px"
|
||||||
|
margin="0 1.5%"
|
||||||
|
cursor="pointer"
|
||||||
|
onClick={() =>
|
||||||
|
setFieldVisibility({
|
||||||
|
...fieldVisibility,
|
||||||
|
[inputType]: true,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<FaRegEye color="#bfbfbf" />
|
||||||
|
</Center>
|
||||||
|
)}
|
||||||
|
<Center
|
||||||
|
w="25px"
|
||||||
|
margin="0 1.5%"
|
||||||
|
cursor="pointer"
|
||||||
|
onClick={() => copyTextToClipboard(variables[inputType])}
|
||||||
|
>
|
||||||
|
<FaRegClone color="#bfbfbf" />
|
||||||
|
</Center>
|
||||||
|
</Flex>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</InputGroup>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (Object.values(ArrayInputType).includes(inputType)) {
|
||||||
|
return (
|
||||||
|
<Flex
|
||||||
|
border="1px solid #e2e8f0"
|
||||||
|
w="100%"
|
||||||
|
paddingTop="0.5%"
|
||||||
|
overflowX="scroll"
|
||||||
|
overflowY="hidden"
|
||||||
|
justifyContent="start"
|
||||||
|
alignItems="center"
|
||||||
|
>
|
||||||
|
{variables[inputType].map((role: string, index: number) => (
|
||||||
|
<Box key={index} margin="0.5%" role="group">
|
||||||
|
<Tag
|
||||||
|
size="sm"
|
||||||
|
variant="outline"
|
||||||
|
colorScheme="gray"
|
||||||
|
minW="fit-content"
|
||||||
|
>
|
||||||
|
<TagLabel cursor="default">{role}</TagLabel>
|
||||||
|
<TagRightIcon
|
||||||
|
boxSize="12px"
|
||||||
|
as={FaTimes}
|
||||||
|
display="none"
|
||||||
|
cursor="pointer"
|
||||||
|
_groupHover={{ display: 'block' }}
|
||||||
|
onClick={() =>
|
||||||
|
updateInputHandler(
|
||||||
|
inputType,
|
||||||
|
ArrayInputOperations.REMOVE,
|
||||||
|
role
|
||||||
|
)
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</Tag>
|
||||||
|
</Box>
|
||||||
|
))}
|
||||||
|
{inputFieldVisibility[inputType] ? (
|
||||||
|
<Box ml="1%" mb="0.75%">
|
||||||
|
<Input
|
||||||
|
type="text"
|
||||||
|
size="xs"
|
||||||
|
minW="150px"
|
||||||
|
placeholder="add a new value"
|
||||||
|
value={inputData[inputType]}
|
||||||
|
onChange={(e: any) => {
|
||||||
|
setInputData({ ...inputData, [inputType]: e.target.value });
|
||||||
|
}}
|
||||||
|
onBlur={() =>
|
||||||
|
updateInputHandler(inputType, ArrayInputOperations.APPEND)
|
||||||
|
}
|
||||||
|
onKeyPress={(event) => {
|
||||||
|
if (event.key === 'Enter') {
|
||||||
|
updateInputHandler(inputType, ArrayInputOperations.APPEND);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
) : (
|
||||||
|
<Box
|
||||||
|
marginLeft="0.5%"
|
||||||
|
cursor="pointer"
|
||||||
|
onClick={() =>
|
||||||
|
setInputFieldVisibility({
|
||||||
|
...inputFieldVisibility,
|
||||||
|
[inputType]: true,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<Tag
|
||||||
|
size="sm"
|
||||||
|
variant="outline"
|
||||||
|
colorScheme="gray"
|
||||||
|
minW="fit-content"
|
||||||
|
>
|
||||||
|
<FaPlus />
|
||||||
|
</Tag>
|
||||||
|
</Box>
|
||||||
|
)}
|
||||||
|
</Flex>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (Object.values(SelectInputType).includes(inputType)) {
|
||||||
|
if (inputType === SelectInputType.JWT_TYPE) {
|
||||||
|
return (
|
||||||
|
<Select size="sm" {...props}>
|
||||||
|
{[variables[inputType]].map((value: string) => (
|
||||||
|
<option value="value" key={value}>
|
||||||
|
{value}
|
||||||
|
</option>
|
||||||
|
))}
|
||||||
|
</Select>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
const { options, ...rest } = props;
|
||||||
|
return (
|
||||||
|
<Select
|
||||||
|
size="sm"
|
||||||
|
{...rest}
|
||||||
|
value={variables[inputType] ? variables[inputType] : ''}
|
||||||
|
onChange={(e) =>
|
||||||
|
setVariables({ ...variables, [inputType]: e.target.value })
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{Object.entries(options).map(([key, value]: any) => (
|
||||||
|
<option value={value} key={key}>
|
||||||
|
{key}
|
||||||
|
</option>
|
||||||
|
))}
|
||||||
|
</Select>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (Object.values(TextAreaInputType).includes(inputType)) {
|
||||||
|
return (
|
||||||
|
<Textarea
|
||||||
|
{...props}
|
||||||
|
size="lg"
|
||||||
|
value={inputData[inputType]}
|
||||||
|
onChange={(e: any) => {
|
||||||
|
setInputData({ ...inputData, [inputType]: e.target.value });
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (Object.values(SwitchInputType).includes(inputType)) {
|
||||||
|
return (
|
||||||
|
<Flex w="25%" justifyContent="space-between">
|
||||||
|
<Code h="75%">Off</Code>
|
||||||
|
<Switch
|
||||||
|
size="md"
|
||||||
|
isChecked={variables[inputType]}
|
||||||
|
onChange={() => {
|
||||||
|
setVariables({
|
||||||
|
...variables,
|
||||||
|
[inputType]: !variables[inputType],
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Code h="75%">On</Code>
|
||||||
|
</Flex>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (Object.values(DateInputType).includes(inputType)) {
|
||||||
|
return (
|
||||||
|
<Flex border="1px solid #e2e8f0" w="100%" h="33px" padding="1%">
|
||||||
|
<input
|
||||||
|
type="date"
|
||||||
|
style={{ width: '100%', paddingLeft: '2.5%' }}
|
||||||
|
value={variables[inputType] ? variables[inputType] : ''}
|
||||||
|
onChange={(e) =>
|
||||||
|
setVariables({ ...variables, [inputType]: e.target.value })
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</Flex>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default InputField;
|
@@ -1,7 +1,6 @@
|
|||||||
import React, { ReactNode } from 'react';
|
import React, { ReactNode } from 'react';
|
||||||
import {
|
import {
|
||||||
IconButton,
|
IconButton,
|
||||||
Avatar,
|
|
||||||
Box,
|
Box,
|
||||||
CloseButton,
|
CloseButton,
|
||||||
Flex,
|
Flex,
|
||||||
@@ -21,9 +20,7 @@ import {
|
|||||||
} from '@chakra-ui/react';
|
} from '@chakra-ui/react';
|
||||||
import {
|
import {
|
||||||
FiHome,
|
FiHome,
|
||||||
FiTrendingUp,
|
FiCode,
|
||||||
FiCompass,
|
|
||||||
FiStar,
|
|
||||||
FiSettings,
|
FiSettings,
|
||||||
FiMenu,
|
FiMenu,
|
||||||
FiUser,
|
FiUser,
|
||||||
@@ -90,6 +87,17 @@ export const Sidebar = ({ onClose, ...rest }: SidebarProps) => {
|
|||||||
</NavItem>
|
</NavItem>
|
||||||
</NavLink>
|
</NavLink>
|
||||||
))}
|
))}
|
||||||
|
|
||||||
|
<Link
|
||||||
|
href="/playground"
|
||||||
|
target="_blank"
|
||||||
|
style={{
|
||||||
|
textDecoration: 'none',
|
||||||
|
}}
|
||||||
|
_focus={{ _boxShadow: 'none' }}
|
||||||
|
>
|
||||||
|
<NavItem icon={FiCode}>API Playground</NavItem>
|
||||||
|
</Link>
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
@@ -100,37 +108,31 @@ interface NavItemProps extends FlexProps {
|
|||||||
}
|
}
|
||||||
export const NavItem = ({ icon, children, ...rest }: NavItemProps) => {
|
export const NavItem = ({ icon, children, ...rest }: NavItemProps) => {
|
||||||
return (
|
return (
|
||||||
<Link
|
<Flex
|
||||||
href="#"
|
align="center"
|
||||||
style={{ textDecoration: 'none' }}
|
p="3"
|
||||||
_focus={{ boxShadow: 'none' }}
|
mx="3"
|
||||||
|
borderRadius="md"
|
||||||
|
role="group"
|
||||||
|
cursor="pointer"
|
||||||
|
_hover={{
|
||||||
|
bg: 'blue.500',
|
||||||
|
color: 'white',
|
||||||
|
}}
|
||||||
|
{...rest}
|
||||||
>
|
>
|
||||||
<Flex
|
{icon && (
|
||||||
align="center"
|
<Icon
|
||||||
p="3"
|
mr="4"
|
||||||
mx="3"
|
fontSize="16"
|
||||||
borderRadius="md"
|
_groupHover={{
|
||||||
role="group"
|
color: 'white',
|
||||||
cursor="pointer"
|
}}
|
||||||
_hover={{
|
as={icon}
|
||||||
bg: 'blue.500',
|
/>
|
||||||
color: 'white',
|
)}
|
||||||
}}
|
{children}
|
||||||
{...rest}
|
</Flex>
|
||||||
>
|
|
||||||
{icon && (
|
|
||||||
<Icon
|
|
||||||
mr="4"
|
|
||||||
fontSize="16"
|
|
||||||
_groupHover={{
|
|
||||||
color: 'white',
|
|
||||||
}}
|
|
||||||
as={icon}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
{children}
|
|
||||||
</Flex>
|
|
||||||
</Link>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -161,6 +163,7 @@ export const MobileNav = ({ onOpen, ...rest }: MobileProps) => {
|
|||||||
borderBottomWidth="1px"
|
borderBottomWidth="1px"
|
||||||
borderBottomColor={useColorModeValue('gray.200', 'gray.700')}
|
borderBottomColor={useColorModeValue('gray.200', 'gray.700')}
|
||||||
justifyContent={{ base: 'space-between', md: 'flex-end' }}
|
justifyContent={{ base: 'space-between', md: 'flex-end' }}
|
||||||
|
zIndex={99}
|
||||||
{...rest}
|
{...rest}
|
||||||
>
|
>
|
||||||
<IconButton
|
<IconButton
|
||||||
|
@@ -1 +1,68 @@
|
|||||||
export const LOGO_URL = "https://user-images.githubusercontent.com/6964334/147834043-fc384cab-e7ca-40f8-9663-38fc25fd5f3a.png"
|
export const LOGO_URL =
|
||||||
|
'https://user-images.githubusercontent.com/6964334/147834043-fc384cab-e7ca-40f8-9663-38fc25fd5f3a.png';
|
||||||
|
|
||||||
|
export const TextInputType = {
|
||||||
|
GOOGLE_CLIENT_ID: 'GOOGLE_CLIENT_ID',
|
||||||
|
GITHUB_CLIENT_ID: 'GITHUB_CLIENT_ID',
|
||||||
|
FACEBOOK_CLIENT_ID: 'FACEBOOK_CLIENT_ID',
|
||||||
|
JWT_ROLE_CLAIM: 'JWT_ROLE_CLAIM',
|
||||||
|
REDIS_URL: 'REDIS_URL',
|
||||||
|
SMTP_HOST: 'SMTP_HOST',
|
||||||
|
SMTP_PORT: 'SMTP_PORT',
|
||||||
|
SMTP_USERNAME: 'SMTP_USERNAME',
|
||||||
|
SENDER_EMAIL: 'SENDER_EMAIL',
|
||||||
|
ORGANIZATION_NAME: 'ORGANIZATION_NAME',
|
||||||
|
ORGANIZATION_LOGO: 'ORGANIZATION_LOGO',
|
||||||
|
DATABASE_NAME: 'DATABASE_NAME',
|
||||||
|
DATABASE_TYPE: 'DATABASE_TYPE',
|
||||||
|
DATABASE_URL: 'DATABASE_URL',
|
||||||
|
GIVEN_NAME: 'given_name',
|
||||||
|
MIDDLE_NAME: 'middle_name',
|
||||||
|
FAMILY_NAME: 'family_name',
|
||||||
|
NICKNAME: 'nickname',
|
||||||
|
PHONE_NUMBER: 'phone_number',
|
||||||
|
PICTURE: 'picture',
|
||||||
|
};
|
||||||
|
|
||||||
|
export const HiddenInputType = {
|
||||||
|
GOOGLE_CLIENT_SECRET: 'GOOGLE_CLIENT_SECRET',
|
||||||
|
GITHUB_CLIENT_SECRET: 'GITHUB_CLIENT_SECRET',
|
||||||
|
FACEBOOK_CLIENT_SECRET: 'FACEBOOK_CLIENT_SECRET',
|
||||||
|
JWT_SECRET: 'JWT_SECRET',
|
||||||
|
SMTP_PASSWORD: 'SMTP_PASSWORD',
|
||||||
|
ADMIN_SECRET: 'ADMIN_SECRET',
|
||||||
|
OLD_ADMIN_SECRET: 'OLD_ADMIN_SECRET',
|
||||||
|
};
|
||||||
|
|
||||||
|
export const ArrayInputType = {
|
||||||
|
ROLES: 'ROLES',
|
||||||
|
DEFAULT_ROLES: 'DEFAULT_ROLES',
|
||||||
|
PROTECTED_ROLES: 'PROTECTED_ROLES',
|
||||||
|
ALLOWED_ORIGINS: 'ALLOWED_ORIGINS',
|
||||||
|
USER_ROLES: 'roles',
|
||||||
|
};
|
||||||
|
|
||||||
|
export const SelectInputType = {
|
||||||
|
JWT_TYPE: 'JWT_TYPE',
|
||||||
|
GENDER: 'gender',
|
||||||
|
};
|
||||||
|
|
||||||
|
export const TextAreaInputType = {
|
||||||
|
CUSTOM_ACCESS_TOKEN_SCRIPT: 'CUSTOM_ACCESS_TOKEN_SCRIPT',
|
||||||
|
};
|
||||||
|
|
||||||
|
export const SwitchInputType = {
|
||||||
|
DISABLE_LOGIN_PAGE: 'DISABLE_LOGIN_PAGE',
|
||||||
|
DISABLE_MAGIC_LINK_LOGIN: 'DISABLE_MAGIC_LINK_LOGIN',
|
||||||
|
DISABLE_EMAIL_VERIFICATION: 'DISABLE_EMAIL_VERIFICATION',
|
||||||
|
DISABLE_BASIC_AUTHENTICATION: 'DISABLE_BASIC_AUTHENTICATION',
|
||||||
|
};
|
||||||
|
|
||||||
|
export const DateInputType = {
|
||||||
|
BIRTHDATE: 'birthdate',
|
||||||
|
};
|
||||||
|
|
||||||
|
export const ArrayInputOperations = {
|
||||||
|
APPEND: 'APPEND',
|
||||||
|
REMOVE: 'REMOVE',
|
||||||
|
};
|
||||||
|
@@ -32,7 +32,7 @@ export const AuthContextProvider = ({ children }: { children: any }) => {
|
|||||||
|
|
||||||
if (fetching) {
|
if (fetching) {
|
||||||
return (
|
return (
|
||||||
<Center>
|
<Center h="100%">
|
||||||
<Spinner />
|
<Spinner />
|
||||||
</Center>
|
</Center>
|
||||||
);
|
);
|
||||||
|
@@ -21,3 +21,27 @@ export const AdminLogout = `
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
export const UpdateEnvVariables = `
|
||||||
|
mutation updateEnvVariables($params: UpdateEnvInput!) {
|
||||||
|
_update_env(params: $params) {
|
||||||
|
message
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
export const UpdateUser = `
|
||||||
|
mutation updateUser($params: UpdateUserInput!) {
|
||||||
|
_update_user(params: $params) {
|
||||||
|
id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
export const DeleteUser = `
|
||||||
|
mutation deleteUser($params: DeleteUserInput!) {
|
||||||
|
_delete_user(params: $params) {
|
||||||
|
message
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
@@ -5,3 +5,69 @@ export const AdminSessionQuery = `
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
export const EnvVariablesQuery = `
|
||||||
|
query {
|
||||||
|
_env{
|
||||||
|
GOOGLE_CLIENT_ID,
|
||||||
|
GOOGLE_CLIENT_SECRET,
|
||||||
|
GITHUB_CLIENT_ID,
|
||||||
|
GITHUB_CLIENT_SECRET,
|
||||||
|
FACEBOOK_CLIENT_ID,
|
||||||
|
FACEBOOK_CLIENT_SECRET,
|
||||||
|
ROLES,
|
||||||
|
DEFAULT_ROLES,
|
||||||
|
PROTECTED_ROLES,
|
||||||
|
JWT_TYPE,
|
||||||
|
JWT_SECRET,
|
||||||
|
JWT_ROLE_CLAIM,
|
||||||
|
REDIS_URL,
|
||||||
|
SMTP_HOST,
|
||||||
|
SMTP_PORT,
|
||||||
|
SMTP_USERNAME,
|
||||||
|
SMTP_PASSWORD,
|
||||||
|
SENDER_EMAIL,
|
||||||
|
ALLOWED_ORIGINS,
|
||||||
|
ORGANIZATION_NAME,
|
||||||
|
ORGANIZATION_LOGO,
|
||||||
|
ADMIN_SECRET,
|
||||||
|
DISABLE_LOGIN_PAGE,
|
||||||
|
DISABLE_MAGIC_LINK_LOGIN,
|
||||||
|
DISABLE_EMAIL_VERIFICATION,
|
||||||
|
DISABLE_BASIC_AUTHENTICATION,
|
||||||
|
CUSTOM_ACCESS_TOKEN_SCRIPT,
|
||||||
|
DATABASE_NAME,
|
||||||
|
DATABASE_TYPE,
|
||||||
|
DATABASE_URL,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
export const UserDetailsQuery = `
|
||||||
|
query($params: PaginatedInput) {
|
||||||
|
_users(params: $params) {
|
||||||
|
pagination {
|
||||||
|
limit
|
||||||
|
page
|
||||||
|
offset
|
||||||
|
total
|
||||||
|
}
|
||||||
|
users {
|
||||||
|
id
|
||||||
|
email
|
||||||
|
email_verified
|
||||||
|
given_name
|
||||||
|
family_name
|
||||||
|
middle_name
|
||||||
|
nickname
|
||||||
|
gender
|
||||||
|
birthdate
|
||||||
|
phone_number
|
||||||
|
picture
|
||||||
|
signup_methods
|
||||||
|
roles
|
||||||
|
created_at
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
@@ -1,35 +1,773 @@
|
|||||||
import { Box, Divider, Flex } from '@chakra-ui/react';
|
import React, { useEffect } from 'react';
|
||||||
import React from 'react';
|
import {
|
||||||
|
Box,
|
||||||
|
Divider,
|
||||||
|
Flex,
|
||||||
|
Stack,
|
||||||
|
Center,
|
||||||
|
Text,
|
||||||
|
Button,
|
||||||
|
Input,
|
||||||
|
InputGroup,
|
||||||
|
InputRightElement,
|
||||||
|
useToast,
|
||||||
|
} from '@chakra-ui/react';
|
||||||
|
import { useClient } from 'urql';
|
||||||
|
import {
|
||||||
|
FaGoogle,
|
||||||
|
FaGithub,
|
||||||
|
FaFacebookF,
|
||||||
|
FaSave,
|
||||||
|
FaRegEyeSlash,
|
||||||
|
FaRegEye,
|
||||||
|
} from 'react-icons/fa';
|
||||||
|
import _ from 'lodash';
|
||||||
|
import InputField from '../components/InputField';
|
||||||
|
import { EnvVariablesQuery } from '../graphql/queries';
|
||||||
|
import {
|
||||||
|
ArrayInputType,
|
||||||
|
SelectInputType,
|
||||||
|
HiddenInputType,
|
||||||
|
TextInputType,
|
||||||
|
TextAreaInputType,
|
||||||
|
SwitchInputType,
|
||||||
|
} from '../constants';
|
||||||
|
import { UpdateEnvVariables } from '../graphql/mutation';
|
||||||
|
import { getObjectDiff, capitalizeFirstLetter } from '../utils';
|
||||||
|
|
||||||
|
interface envVarTypes {
|
||||||
|
GOOGLE_CLIENT_ID: string;
|
||||||
|
GOOGLE_CLIENT_SECRET: string;
|
||||||
|
GITHUB_CLIENT_ID: string;
|
||||||
|
GITHUB_CLIENT_SECRET: string;
|
||||||
|
FACEBOOK_CLIENT_ID: string;
|
||||||
|
FACEBOOK_CLIENT_SECRET: string;
|
||||||
|
ROLES: [string] | [];
|
||||||
|
DEFAULT_ROLES: [string] | [];
|
||||||
|
PROTECTED_ROLES: [string] | [];
|
||||||
|
JWT_TYPE: string;
|
||||||
|
JWT_SECRET: string;
|
||||||
|
JWT_ROLE_CLAIM: string;
|
||||||
|
REDIS_URL: string;
|
||||||
|
SMTP_HOST: string;
|
||||||
|
SMTP_PORT: string;
|
||||||
|
SMTP_USERNAME: string;
|
||||||
|
SMTP_PASSWORD: string;
|
||||||
|
SENDER_EMAIL: string;
|
||||||
|
ALLOWED_ORIGINS: [string] | [];
|
||||||
|
ORGANIZATION_NAME: string;
|
||||||
|
ORGANIZATION_LOGO: string;
|
||||||
|
CUSTOM_ACCESS_TOKEN_SCRIPT: string;
|
||||||
|
ADMIN_SECRET: string;
|
||||||
|
DISABLE_LOGIN_PAGE: boolean;
|
||||||
|
DISABLE_MAGIC_LINK_LOGIN: boolean;
|
||||||
|
DISABLE_EMAIL_VERIFICATION: boolean;
|
||||||
|
DISABLE_BASIC_AUTHENTICATION: boolean;
|
||||||
|
OLD_ADMIN_SECRET: string;
|
||||||
|
DATABASE_NAME: string;
|
||||||
|
DATABASE_TYPE: string;
|
||||||
|
DATABASE_URL: string;
|
||||||
|
}
|
||||||
|
|
||||||
// Don't allow changing database from here as it can cause persistence issues
|
|
||||||
export default function Environment() {
|
export default function Environment() {
|
||||||
|
const client = useClient();
|
||||||
|
const toast = useToast();
|
||||||
|
const [adminSecret, setAdminSecret] = React.useState<
|
||||||
|
Record<string, string | boolean>
|
||||||
|
>({
|
||||||
|
value: '',
|
||||||
|
disableInputField: true,
|
||||||
|
});
|
||||||
|
const [loading, setLoading] = React.useState<boolean>(true);
|
||||||
|
const [envVariables, setEnvVariables] = React.useState<envVarTypes>({
|
||||||
|
GOOGLE_CLIENT_ID: '',
|
||||||
|
GOOGLE_CLIENT_SECRET: '',
|
||||||
|
GITHUB_CLIENT_ID: '',
|
||||||
|
GITHUB_CLIENT_SECRET: '',
|
||||||
|
FACEBOOK_CLIENT_ID: '',
|
||||||
|
FACEBOOK_CLIENT_SECRET: '',
|
||||||
|
ROLES: [],
|
||||||
|
DEFAULT_ROLES: [],
|
||||||
|
PROTECTED_ROLES: [],
|
||||||
|
JWT_TYPE: '',
|
||||||
|
JWT_SECRET: '',
|
||||||
|
JWT_ROLE_CLAIM: '',
|
||||||
|
REDIS_URL: '',
|
||||||
|
SMTP_HOST: '',
|
||||||
|
SMTP_PORT: '',
|
||||||
|
SMTP_USERNAME: '',
|
||||||
|
SMTP_PASSWORD: '',
|
||||||
|
SENDER_EMAIL: '',
|
||||||
|
ALLOWED_ORIGINS: [],
|
||||||
|
ORGANIZATION_NAME: '',
|
||||||
|
ORGANIZATION_LOGO: '',
|
||||||
|
CUSTOM_ACCESS_TOKEN_SCRIPT: '',
|
||||||
|
ADMIN_SECRET: '',
|
||||||
|
DISABLE_LOGIN_PAGE: false,
|
||||||
|
DISABLE_MAGIC_LINK_LOGIN: false,
|
||||||
|
DISABLE_EMAIL_VERIFICATION: false,
|
||||||
|
DISABLE_BASIC_AUTHENTICATION: false,
|
||||||
|
OLD_ADMIN_SECRET: '',
|
||||||
|
DATABASE_NAME: '',
|
||||||
|
DATABASE_TYPE: '',
|
||||||
|
DATABASE_URL: '',
|
||||||
|
});
|
||||||
|
|
||||||
|
const [fieldVisibility, setFieldVisibility] = React.useState<
|
||||||
|
Record<string, boolean>
|
||||||
|
>({
|
||||||
|
GOOGLE_CLIENT_SECRET: false,
|
||||||
|
GITHUB_CLIENT_SECRET: false,
|
||||||
|
FACEBOOK_CLIENT_SECRET: false,
|
||||||
|
JWT_SECRET: false,
|
||||||
|
SMTP_PASSWORD: false,
|
||||||
|
ADMIN_SECRET: false,
|
||||||
|
OLD_ADMIN_SECRET: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
let isMounted = true;
|
||||||
|
async function getData() {
|
||||||
|
const {
|
||||||
|
data: { _env: envData },
|
||||||
|
} = await client.query(EnvVariablesQuery).toPromise();
|
||||||
|
|
||||||
|
if (isMounted) {
|
||||||
|
setLoading(false);
|
||||||
|
setEnvVariables({
|
||||||
|
...envData,
|
||||||
|
OLD_ADMIN_SECRET: envData.ADMIN_SECRET,
|
||||||
|
ADMIN_SECRET: '',
|
||||||
|
});
|
||||||
|
setAdminSecret({
|
||||||
|
value: '',
|
||||||
|
disableInputField: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
getData();
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
isMounted = false;
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const validateAdminSecretHandler = (event: any) => {
|
||||||
|
if (envVariables.OLD_ADMIN_SECRET === event.target.value) {
|
||||||
|
setAdminSecret({
|
||||||
|
...adminSecret,
|
||||||
|
value: event.target.value,
|
||||||
|
disableInputField: false,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
setAdminSecret({
|
||||||
|
...adminSecret,
|
||||||
|
value: event.target.value,
|
||||||
|
disableInputField: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (envVariables.ADMIN_SECRET !== '') {
|
||||||
|
setEnvVariables({ ...envVariables, ADMIN_SECRET: '' });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const saveHandler = async () => {
|
||||||
|
setLoading(true);
|
||||||
|
const {
|
||||||
|
data: { _env: envData },
|
||||||
|
} = await client.query(EnvVariablesQuery).toPromise();
|
||||||
|
|
||||||
|
const diff = getObjectDiff(envVariables, envData);
|
||||||
|
const updatedEnvVariables = diff.reduce(
|
||||||
|
(acc: any, property: string) => ({
|
||||||
|
...acc,
|
||||||
|
// @ts-ignore
|
||||||
|
[property]: envVariables[property],
|
||||||
|
}),
|
||||||
|
{}
|
||||||
|
);
|
||||||
|
if (
|
||||||
|
updatedEnvVariables[HiddenInputType.ADMIN_SECRET] === '' ||
|
||||||
|
updatedEnvVariables[HiddenInputType.OLD_ADMIN_SECRET] !==
|
||||||
|
envData.ADMIN_SECRET
|
||||||
|
) {
|
||||||
|
delete updatedEnvVariables.OLD_ADMIN_SECRET;
|
||||||
|
delete updatedEnvVariables.ADMIN_SECRET;
|
||||||
|
}
|
||||||
|
|
||||||
|
delete updatedEnvVariables.DATABASE_URL;
|
||||||
|
delete updatedEnvVariables.DATABASE_TYPE;
|
||||||
|
delete updatedEnvVariables.DATABASE_NAME;
|
||||||
|
|
||||||
|
const res = await client
|
||||||
|
.mutation(UpdateEnvVariables, { params: updatedEnvVariables })
|
||||||
|
.toPromise();
|
||||||
|
|
||||||
|
setLoading(false);
|
||||||
|
|
||||||
|
if (res.error) {
|
||||||
|
toast({
|
||||||
|
title: capitalizeFirstLetter(res.error.message),
|
||||||
|
isClosable: true,
|
||||||
|
status: 'error',
|
||||||
|
position: 'bottom-right',
|
||||||
|
});
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setAdminSecret({
|
||||||
|
value: '',
|
||||||
|
disableInputField: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
toast({
|
||||||
|
title: `Successfully updated ${
|
||||||
|
Object.keys(updatedEnvVariables).length
|
||||||
|
} variables`,
|
||||||
|
isClosable: true,
|
||||||
|
status: 'success',
|
||||||
|
position: 'bottom-right',
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box m="5" p="5" bg="white" rounded="md">
|
<Box m="5" py="5" px="10" bg="white" rounded="md">
|
||||||
<h1>Social Media Logins</h1>
|
<Text fontSize="md" paddingTop="2%" fontWeight="bold">
|
||||||
<Divider />- Add horizontal input for clientID and secret for - Google -
|
Social Media Logins
|
||||||
Github - Facebook
|
</Text>
|
||||||
<h1>Roles</h1>
|
<Stack spacing={6} padding="2% 0%">
|
||||||
<Divider />- Add tagged input for roles, default roles, and protected
|
<Flex>
|
||||||
roles
|
<Center
|
||||||
<h1>JWT Configurations</h1>
|
w="50px"
|
||||||
<Divider />- Add input for JWT Type (keep this disabled for now with
|
marginRight="1.5%"
|
||||||
notice saying, "More JWT types will be enabled in upcoming releases"),JWT
|
border="1px solid #e2e8f0"
|
||||||
secret, JWT role claim
|
borderRadius="5px"
|
||||||
<h1>Session Storage</h1>
|
>
|
||||||
<Divider />- Add input for redis url
|
<FaGoogle style={{ color: '#8c8c8c' }} />
|
||||||
<h1>Email Configurations</h1>
|
</Center>
|
||||||
<Divider />- Add input for SMTP Host, PORT, Username, Password, From
|
<Center w="45%" marginRight="1.5%">
|
||||||
Email,
|
<InputField
|
||||||
<h1>White Listing</h1>
|
variables={envVariables}
|
||||||
<Divider />- Add input for allowed origins
|
setVariables={setEnvVariables}
|
||||||
<h1>Organization Information</h1>
|
inputType={TextInputType.GOOGLE_CLIENT_ID}
|
||||||
<Divider />- Add input for organization name, and logo
|
placeholder="Google Client ID"
|
||||||
<h1>Custom Scripts</h1>
|
/>
|
||||||
<Divider />- For now add text area input for CUSTOM_ACCESS_TOKEN_SCRIPT
|
</Center>
|
||||||
<h1>Disable Features</h1>
|
<Center w="45%">
|
||||||
<Divider />
|
<InputField
|
||||||
<h1>Danger</h1>
|
variables={envVariables}
|
||||||
<Divider />- Include changing admin secret
|
setVariables={setEnvVariables}
|
||||||
|
fieldVisibility={fieldVisibility}
|
||||||
|
setFieldVisibility={setFieldVisibility}
|
||||||
|
inputType={HiddenInputType.GOOGLE_CLIENT_SECRET}
|
||||||
|
placeholder="Google Secret"
|
||||||
|
/>
|
||||||
|
</Center>
|
||||||
|
</Flex>
|
||||||
|
<Flex>
|
||||||
|
<Center
|
||||||
|
w="50px"
|
||||||
|
marginRight="1.5%"
|
||||||
|
border="1px solid #e2e8f0"
|
||||||
|
borderRadius="5px"
|
||||||
|
>
|
||||||
|
<FaGithub style={{ color: '#8c8c8c' }} />
|
||||||
|
</Center>
|
||||||
|
<Center w="45%" marginRight="1.5%">
|
||||||
|
<InputField
|
||||||
|
variables={envVariables}
|
||||||
|
setVariables={setEnvVariables}
|
||||||
|
inputType={TextInputType.GITHUB_CLIENT_ID}
|
||||||
|
placeholder="Github Client ID"
|
||||||
|
/>
|
||||||
|
</Center>
|
||||||
|
<Center w="45%">
|
||||||
|
<InputField
|
||||||
|
variables={envVariables}
|
||||||
|
setVariables={setEnvVariables}
|
||||||
|
fieldVisibility={fieldVisibility}
|
||||||
|
setFieldVisibility={setFieldVisibility}
|
||||||
|
inputType={HiddenInputType.GITHUB_CLIENT_SECRET}
|
||||||
|
placeholder="Github Secret"
|
||||||
|
/>
|
||||||
|
</Center>
|
||||||
|
</Flex>
|
||||||
|
<Flex>
|
||||||
|
<Center
|
||||||
|
w="50px"
|
||||||
|
marginRight="1.5%"
|
||||||
|
border="1px solid #e2e8f0"
|
||||||
|
borderRadius="5px"
|
||||||
|
>
|
||||||
|
<FaFacebookF style={{ color: '#8c8c8c' }} />
|
||||||
|
</Center>
|
||||||
|
<Center w="45%" marginRight="1.5%">
|
||||||
|
<InputField
|
||||||
|
variables={envVariables}
|
||||||
|
setVariables={setEnvVariables}
|
||||||
|
inputType={TextInputType.FACEBOOK_CLIENT_ID}
|
||||||
|
placeholder="Facebook Client ID"
|
||||||
|
/>
|
||||||
|
</Center>
|
||||||
|
<Center w="45%">
|
||||||
|
<InputField
|
||||||
|
variables={envVariables}
|
||||||
|
setVariables={setEnvVariables}
|
||||||
|
fieldVisibility={fieldVisibility}
|
||||||
|
setFieldVisibility={setFieldVisibility}
|
||||||
|
inputType={HiddenInputType.FACEBOOK_CLIENT_SECRET}
|
||||||
|
placeholder="Facebook Secret"
|
||||||
|
/>
|
||||||
|
</Center>
|
||||||
|
</Flex>
|
||||||
|
</Stack>
|
||||||
|
<Divider marginTop="2%" marginBottom="2%" />
|
||||||
|
<Text fontSize="md" paddingTop="2%" fontWeight="bold">
|
||||||
|
Roles
|
||||||
|
</Text>
|
||||||
|
<Stack spacing={6} padding="2% 0%">
|
||||||
|
<Flex>
|
||||||
|
<Flex w="30%" justifyContent="start" alignItems="center">
|
||||||
|
<Text fontSize="sm">Roles:</Text>
|
||||||
|
</Flex>
|
||||||
|
<Center w="70%">
|
||||||
|
<InputField
|
||||||
|
variables={envVariables}
|
||||||
|
setVariables={setEnvVariables}
|
||||||
|
inputType={ArrayInputType.ROLES}
|
||||||
|
/>
|
||||||
|
</Center>
|
||||||
|
</Flex>
|
||||||
|
<Flex>
|
||||||
|
<Flex w="30%" justifyContent="start" alignItems="center">
|
||||||
|
<Text fontSize="sm">Default Roles:</Text>
|
||||||
|
</Flex>
|
||||||
|
<Center w="70%">
|
||||||
|
<InputField
|
||||||
|
variables={envVariables}
|
||||||
|
setVariables={setEnvVariables}
|
||||||
|
inputType={ArrayInputType.DEFAULT_ROLES}
|
||||||
|
/>
|
||||||
|
</Center>
|
||||||
|
</Flex>
|
||||||
|
<Flex>
|
||||||
|
<Flex w="30%" justifyContent="start" alignItems="center">
|
||||||
|
<Text fontSize="sm">Protected Roles:</Text>
|
||||||
|
</Flex>
|
||||||
|
<Center w="70%">
|
||||||
|
<InputField
|
||||||
|
variables={envVariables}
|
||||||
|
setVariables={setEnvVariables}
|
||||||
|
inputType={ArrayInputType.PROTECTED_ROLES}
|
||||||
|
/>
|
||||||
|
</Center>
|
||||||
|
</Flex>
|
||||||
|
</Stack>
|
||||||
|
<Divider marginTop="2%" marginBottom="2%" />
|
||||||
|
<Text fontSize="md" paddingTop="2%" fontWeight="bold">
|
||||||
|
JWT (JSON Web Tokens) Configurations
|
||||||
|
</Text>
|
||||||
|
<Stack spacing={6} padding="2% 0%">
|
||||||
|
<Flex>
|
||||||
|
<Flex w="30%" justifyContent="start" alignItems="center">
|
||||||
|
<Text fontSize="sm">JWT Type:</Text>
|
||||||
|
</Flex>
|
||||||
|
<Center w="70%">
|
||||||
|
<Flex w="100%" justifyContent="space-between">
|
||||||
|
<Flex flex="2">
|
||||||
|
<InputField
|
||||||
|
variables={envVariables}
|
||||||
|
setVariables={setEnvVariables}
|
||||||
|
inputType={SelectInputType.JWT_TYPE}
|
||||||
|
isDisabled={true}
|
||||||
|
defaultValue={SelectInputType.JWT_TYPE}
|
||||||
|
/>
|
||||||
|
</Flex>
|
||||||
|
<Flex flex="3" justifyContent="center" alignItems="center">
|
||||||
|
<Text fontSize="sm">
|
||||||
|
More JWT types will be enabled in upcoming releases.
|
||||||
|
</Text>
|
||||||
|
</Flex>
|
||||||
|
</Flex>
|
||||||
|
</Center>
|
||||||
|
</Flex>
|
||||||
|
<Flex>
|
||||||
|
<Flex w="30%" justifyContent="start" alignItems="center">
|
||||||
|
<Text fontSize="sm">JWT Secret</Text>
|
||||||
|
</Flex>
|
||||||
|
<Center w="70%">
|
||||||
|
<InputField
|
||||||
|
variables={envVariables}
|
||||||
|
setVariables={setEnvVariables}
|
||||||
|
fieldVisibility={fieldVisibility}
|
||||||
|
setFieldVisibility={setFieldVisibility}
|
||||||
|
inputType={HiddenInputType.JWT_SECRET}
|
||||||
|
/>
|
||||||
|
</Center>
|
||||||
|
</Flex>
|
||||||
|
<Flex>
|
||||||
|
<Flex w="30%" justifyContent="start" alignItems="center">
|
||||||
|
<Text fontSize="sm">JWT Role Claim:</Text>
|
||||||
|
</Flex>
|
||||||
|
<Center w="70%">
|
||||||
|
<InputField
|
||||||
|
variables={envVariables}
|
||||||
|
setVariables={setEnvVariables}
|
||||||
|
inputType={TextInputType.JWT_ROLE_CLAIM}
|
||||||
|
/>
|
||||||
|
</Center>
|
||||||
|
</Flex>
|
||||||
|
</Stack>
|
||||||
|
<Divider marginTop="2%" marginBottom="2%" />
|
||||||
|
<Text fontSize="md" paddingTop="2%" fontWeight="bold">
|
||||||
|
Session Storage
|
||||||
|
</Text>
|
||||||
|
<Stack spacing={6} padding="2% 0%">
|
||||||
|
<Flex>
|
||||||
|
<Flex w="30%" justifyContent="start" alignItems="center">
|
||||||
|
<Text fontSize="sm">Redis URL:</Text>
|
||||||
|
</Flex>
|
||||||
|
<Center w="70%">
|
||||||
|
<InputField
|
||||||
|
variables={envVariables}
|
||||||
|
setVariables={setEnvVariables}
|
||||||
|
inputType={TextInputType.REDIS_URL}
|
||||||
|
/>
|
||||||
|
</Center>
|
||||||
|
</Flex>
|
||||||
|
</Stack>
|
||||||
|
<Divider marginTop="2%" marginBottom="2%" />
|
||||||
|
<Text fontSize="md" paddingTop="2%" fontWeight="bold">
|
||||||
|
Email Configurations
|
||||||
|
</Text>
|
||||||
|
<Stack spacing={6} padding="2% 0%">
|
||||||
|
<Flex>
|
||||||
|
<Flex w="30%" justifyContent="start" alignItems="center">
|
||||||
|
<Text fontSize="sm">SMTP Host:</Text>
|
||||||
|
</Flex>
|
||||||
|
<Center w="70%">
|
||||||
|
<InputField
|
||||||
|
variables={envVariables}
|
||||||
|
setVariables={setEnvVariables}
|
||||||
|
inputType={TextInputType.SMTP_HOST}
|
||||||
|
/>
|
||||||
|
</Center>
|
||||||
|
</Flex>
|
||||||
|
<Flex>
|
||||||
|
<Flex w="30%" justifyContent="start" alignItems="center">
|
||||||
|
<Text fontSize="sm">Port:</Text>
|
||||||
|
</Flex>
|
||||||
|
<Center w="70%">
|
||||||
|
<InputField
|
||||||
|
variables={envVariables}
|
||||||
|
setVariables={setEnvVariables}
|
||||||
|
inputType={TextInputType.SMTP_PORT}
|
||||||
|
/>
|
||||||
|
</Center>
|
||||||
|
</Flex>
|
||||||
|
<Flex>
|
||||||
|
<Flex w="30%" justifyContent="start" alignItems="center">
|
||||||
|
<Text fontSize="sm">Username:</Text>
|
||||||
|
</Flex>
|
||||||
|
<Center w="70%">
|
||||||
|
<InputField
|
||||||
|
variables={envVariables}
|
||||||
|
setVariables={setEnvVariables}
|
||||||
|
inputType={TextInputType.SMTP_USERNAME}
|
||||||
|
/>
|
||||||
|
</Center>
|
||||||
|
</Flex>
|
||||||
|
<Flex>
|
||||||
|
<Flex w="30%" justifyContent="start" alignItems="center">
|
||||||
|
<Text fontSize="sm">Password:</Text>
|
||||||
|
</Flex>
|
||||||
|
<Center w="70%">
|
||||||
|
<InputField
|
||||||
|
variables={envVariables}
|
||||||
|
setVariables={setEnvVariables}
|
||||||
|
fieldVisibility={fieldVisibility}
|
||||||
|
setFieldVisibility={setFieldVisibility}
|
||||||
|
inputType={HiddenInputType.SMTP_PASSWORD}
|
||||||
|
/>
|
||||||
|
</Center>
|
||||||
|
</Flex>
|
||||||
|
<Flex>
|
||||||
|
<Flex w="30%" justifyContent="start" alignItems="center">
|
||||||
|
<Text fontSize="sm">From Email:</Text>
|
||||||
|
</Flex>
|
||||||
|
<Center w="70%">
|
||||||
|
<InputField
|
||||||
|
variables={envVariables}
|
||||||
|
setVariables={setEnvVariables}
|
||||||
|
inputType={TextInputType.SENDER_EMAIL}
|
||||||
|
/>
|
||||||
|
</Center>
|
||||||
|
</Flex>
|
||||||
|
</Stack>
|
||||||
|
<Divider marginTop="2%" marginBottom="2%" />
|
||||||
|
<Text fontSize="md" paddingTop="2%" fontWeight="bold">
|
||||||
|
White Listing
|
||||||
|
</Text>
|
||||||
|
<Stack spacing={6} padding="2% 0%">
|
||||||
|
<Flex>
|
||||||
|
<Flex w="30%" justifyContent="start" alignItems="center">
|
||||||
|
<Text fontSize="sm">Allowed Origins:</Text>
|
||||||
|
</Flex>
|
||||||
|
<Center w="70%">
|
||||||
|
<InputField
|
||||||
|
variables={envVariables}
|
||||||
|
setVariables={setEnvVariables}
|
||||||
|
inputType={ArrayInputType.ALLOWED_ORIGINS}
|
||||||
|
/>
|
||||||
|
</Center>
|
||||||
|
</Flex>
|
||||||
|
</Stack>
|
||||||
|
<Divider marginTop="2%" marginBottom="2%" />
|
||||||
|
<Text fontSize="md" paddingTop="2%" fontWeight="bold">
|
||||||
|
Organization Information
|
||||||
|
</Text>
|
||||||
|
<Stack spacing={6} padding="2% 0%">
|
||||||
|
<Flex>
|
||||||
|
<Flex w="30%" justifyContent="start" alignItems="center">
|
||||||
|
<Text fontSize="sm">Organization Name:</Text>
|
||||||
|
</Flex>
|
||||||
|
<Center w="70%">
|
||||||
|
<InputField
|
||||||
|
variables={envVariables}
|
||||||
|
setVariables={setEnvVariables}
|
||||||
|
inputType={TextInputType.ORGANIZATION_NAME}
|
||||||
|
/>
|
||||||
|
</Center>
|
||||||
|
</Flex>
|
||||||
|
<Flex>
|
||||||
|
<Flex w="30%" justifyContent="start" alignItems="center">
|
||||||
|
<Text fontSize="sm">Organization Logo:</Text>
|
||||||
|
</Flex>
|
||||||
|
<Center w="70%">
|
||||||
|
<InputField
|
||||||
|
variables={envVariables}
|
||||||
|
setVariables={setEnvVariables}
|
||||||
|
inputType={TextInputType.ORGANIZATION_LOGO}
|
||||||
|
/>
|
||||||
|
</Center>
|
||||||
|
</Flex>
|
||||||
|
</Stack>
|
||||||
|
<Divider marginTop="2%" marginBottom="2%" />
|
||||||
|
<Text fontSize="md" paddingTop="2%" fontWeight="bold">
|
||||||
|
Custom Scripts
|
||||||
|
</Text>
|
||||||
|
<Stack spacing={6} padding="2% 0%">
|
||||||
|
<Flex>
|
||||||
|
<Center w="100%">
|
||||||
|
<InputField
|
||||||
|
variables={envVariables}
|
||||||
|
setVariables={setEnvVariables}
|
||||||
|
inputType={TextAreaInputType.CUSTOM_ACCESS_TOKEN_SCRIPT}
|
||||||
|
placeholder="Add script here"
|
||||||
|
minH="25vh"
|
||||||
|
/>
|
||||||
|
</Center>
|
||||||
|
</Flex>
|
||||||
|
</Stack>
|
||||||
|
<Divider marginTop="2%" marginBottom="2%" />
|
||||||
|
<Text fontSize="md" paddingTop="2%" fontWeight="bold">
|
||||||
|
Disable Features
|
||||||
|
</Text>
|
||||||
|
<Stack spacing={6} padding="2% 0%">
|
||||||
|
<Flex>
|
||||||
|
<Flex w="30%" justifyContent="start" alignItems="center">
|
||||||
|
<Text fontSize="sm">Disable Login Page:</Text>
|
||||||
|
</Flex>
|
||||||
|
<Flex justifyContent="start" w="70%">
|
||||||
|
<InputField
|
||||||
|
variables={envVariables}
|
||||||
|
setVariables={setEnvVariables}
|
||||||
|
inputType={SwitchInputType.DISABLE_LOGIN_PAGE}
|
||||||
|
/>
|
||||||
|
</Flex>
|
||||||
|
</Flex>
|
||||||
|
<Flex>
|
||||||
|
<Flex w="30%" justifyContent="start" alignItems="center">
|
||||||
|
<Text fontSize="sm">Disable Email Verification:</Text>
|
||||||
|
</Flex>
|
||||||
|
<Flex justifyContent="start" w="70%">
|
||||||
|
<InputField
|
||||||
|
variables={envVariables}
|
||||||
|
setVariables={setEnvVariables}
|
||||||
|
inputType={SwitchInputType.DISABLE_EMAIL_VERIFICATION}
|
||||||
|
/>
|
||||||
|
</Flex>
|
||||||
|
</Flex>
|
||||||
|
<Flex>
|
||||||
|
<Flex w="30%" justifyContent="start" alignItems="center">
|
||||||
|
<Text fontSize="sm">Disable Magic Login Link:</Text>
|
||||||
|
</Flex>
|
||||||
|
<Flex justifyContent="start" w="70%">
|
||||||
|
<InputField
|
||||||
|
variables={envVariables}
|
||||||
|
setVariables={setEnvVariables}
|
||||||
|
inputType={SwitchInputType.DISABLE_MAGIC_LINK_LOGIN}
|
||||||
|
/>
|
||||||
|
</Flex>
|
||||||
|
</Flex>
|
||||||
|
<Flex>
|
||||||
|
<Flex w="30%" justifyContent="start" alignItems="center">
|
||||||
|
<Text fontSize="sm">Disable Basic Authentication:</Text>
|
||||||
|
</Flex>
|
||||||
|
<Flex justifyContent="start" w="70%">
|
||||||
|
<InputField
|
||||||
|
variables={envVariables}
|
||||||
|
setVariables={setEnvVariables}
|
||||||
|
inputType={SwitchInputType.DISABLE_BASIC_AUTHENTICATION}
|
||||||
|
/>
|
||||||
|
</Flex>
|
||||||
|
</Flex>
|
||||||
|
</Stack>
|
||||||
|
<Divider marginTop="2%" marginBottom="2%" />
|
||||||
|
<Text fontSize="md" paddingTop="2%" fontWeight="bold">
|
||||||
|
Danger
|
||||||
|
</Text>
|
||||||
|
<Stack
|
||||||
|
spacing={6}
|
||||||
|
padding="0 5%"
|
||||||
|
marginTop="3%"
|
||||||
|
border="1px solid #ff7875"
|
||||||
|
borderRadius="5px"
|
||||||
|
>
|
||||||
|
<Stack spacing={6} padding="3% 0">
|
||||||
|
<Text fontStyle="italic" fontSize="sm" color="gray.600">
|
||||||
|
Note: Database related environment variables cannot be updated from
|
||||||
|
dashboard :(
|
||||||
|
</Text>
|
||||||
|
<Flex>
|
||||||
|
<Flex w="30%" justifyContent="start" alignItems="center">
|
||||||
|
<Text fontSize="sm">DataBase Name:</Text>
|
||||||
|
</Flex>
|
||||||
|
<Center w="70%">
|
||||||
|
<InputField
|
||||||
|
variables={envVariables}
|
||||||
|
setVariables={setEnvVariables}
|
||||||
|
inputType={TextInputType.DATABASE_NAME}
|
||||||
|
isDisabled={true}
|
||||||
|
/>
|
||||||
|
</Center>
|
||||||
|
</Flex>
|
||||||
|
<Flex>
|
||||||
|
<Flex w="30%" justifyContent="start" alignItems="center">
|
||||||
|
<Text fontSize="sm">DataBase Type:</Text>
|
||||||
|
</Flex>
|
||||||
|
<Center w="70%">
|
||||||
|
<InputField
|
||||||
|
variables={envVariables}
|
||||||
|
setVariables={setEnvVariables}
|
||||||
|
inputType={TextInputType.DATABASE_TYPE}
|
||||||
|
isDisabled={true}
|
||||||
|
/>
|
||||||
|
</Center>
|
||||||
|
</Flex>
|
||||||
|
<Flex>
|
||||||
|
<Flex w="30%" justifyContent="start" alignItems="center">
|
||||||
|
<Text fontSize="sm">DataBase URL:</Text>
|
||||||
|
</Flex>
|
||||||
|
<Center w="70%">
|
||||||
|
<InputField
|
||||||
|
variables={envVariables}
|
||||||
|
setVariables={setEnvVariables}
|
||||||
|
inputType={TextInputType.DATABASE_URL}
|
||||||
|
isDisabled={true}
|
||||||
|
/>
|
||||||
|
</Center>
|
||||||
|
</Flex>
|
||||||
|
</Stack>
|
||||||
|
<Flex marginTop="3%">
|
||||||
|
<Flex w="30%" justifyContent="start" alignItems="center">
|
||||||
|
<Text fontSize="sm">Old Admin Secret:</Text>
|
||||||
|
</Flex>
|
||||||
|
<Center w="70%">
|
||||||
|
<InputGroup size="sm">
|
||||||
|
<Input
|
||||||
|
size="sm"
|
||||||
|
placeholder="Enter Old Admin Secret"
|
||||||
|
value={adminSecret.value as string}
|
||||||
|
onChange={(event: any) => validateAdminSecretHandler(event)}
|
||||||
|
type={
|
||||||
|
!fieldVisibility[HiddenInputType.OLD_ADMIN_SECRET]
|
||||||
|
? 'password'
|
||||||
|
: 'text'
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<InputRightElement
|
||||||
|
right="5px"
|
||||||
|
children={
|
||||||
|
<Flex>
|
||||||
|
{fieldVisibility[HiddenInputType.OLD_ADMIN_SECRET] ? (
|
||||||
|
<Center
|
||||||
|
w="25px"
|
||||||
|
margin="0 1.5%"
|
||||||
|
cursor="pointer"
|
||||||
|
onClick={() =>
|
||||||
|
setFieldVisibility({
|
||||||
|
...fieldVisibility,
|
||||||
|
[HiddenInputType.OLD_ADMIN_SECRET]: false,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<FaRegEyeSlash color="#bfbfbf" />
|
||||||
|
</Center>
|
||||||
|
) : (
|
||||||
|
<Center
|
||||||
|
w="25px"
|
||||||
|
margin="0 1.5%"
|
||||||
|
cursor="pointer"
|
||||||
|
onClick={() =>
|
||||||
|
setFieldVisibility({
|
||||||
|
...fieldVisibility,
|
||||||
|
[HiddenInputType.OLD_ADMIN_SECRET]: true,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<FaRegEye color="#bfbfbf" />
|
||||||
|
</Center>
|
||||||
|
)}
|
||||||
|
</Flex>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</InputGroup>
|
||||||
|
</Center>
|
||||||
|
</Flex>
|
||||||
|
<Flex paddingBottom="3%">
|
||||||
|
<Flex w="30%" justifyContent="start" alignItems="center">
|
||||||
|
<Text fontSize="sm">New Admin Secret:</Text>
|
||||||
|
</Flex>
|
||||||
|
<Center w="70%">
|
||||||
|
<InputField
|
||||||
|
variables={envVariables}
|
||||||
|
setVariables={setEnvVariables}
|
||||||
|
inputType={HiddenInputType.ADMIN_SECRET}
|
||||||
|
fieldVisibility={fieldVisibility}
|
||||||
|
setFieldVisibility={setFieldVisibility}
|
||||||
|
isDisabled={adminSecret.disableInputField}
|
||||||
|
placeholder="Enter New Admin Secret"
|
||||||
|
/>
|
||||||
|
</Center>
|
||||||
|
</Flex>
|
||||||
|
</Stack>
|
||||||
|
<Divider marginTop="5%" marginBottom="2%" />
|
||||||
|
<Stack spacing={6} padding="1% 0">
|
||||||
|
<Flex justifyContent="end" alignItems="center">
|
||||||
|
<Button
|
||||||
|
leftIcon={<FaSave />}
|
||||||
|
colorScheme="blue"
|
||||||
|
variant="solid"
|
||||||
|
onClick={saveHandler}
|
||||||
|
isDisabled={loading}
|
||||||
|
>
|
||||||
|
Save
|
||||||
|
</Button>
|
||||||
|
</Flex>
|
||||||
|
</Stack>
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@@ -1,6 +1,400 @@
|
|||||||
import { Box } from '@chakra-ui/react';
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
import { useClient } from 'urql';
|
||||||
|
import dayjs from 'dayjs';
|
||||||
|
import {
|
||||||
|
Box,
|
||||||
|
Flex,
|
||||||
|
IconButton,
|
||||||
|
NumberDecrementStepper,
|
||||||
|
NumberIncrementStepper,
|
||||||
|
NumberInput,
|
||||||
|
NumberInputField,
|
||||||
|
NumberInputStepper,
|
||||||
|
Select,
|
||||||
|
Table,
|
||||||
|
Tag,
|
||||||
|
Tbody,
|
||||||
|
Td,
|
||||||
|
Text,
|
||||||
|
TableCaption,
|
||||||
|
Th,
|
||||||
|
Thead,
|
||||||
|
Tooltip,
|
||||||
|
Tr,
|
||||||
|
Button,
|
||||||
|
Center,
|
||||||
|
Menu,
|
||||||
|
MenuButton,
|
||||||
|
MenuList,
|
||||||
|
MenuItem,
|
||||||
|
useToast,
|
||||||
|
Spinner,
|
||||||
|
} from '@chakra-ui/react';
|
||||||
|
import {
|
||||||
|
FaAngleLeft,
|
||||||
|
FaAngleRight,
|
||||||
|
FaAngleDoubleLeft,
|
||||||
|
FaAngleDoubleRight,
|
||||||
|
FaExclamationCircle,
|
||||||
|
FaAngleDown,
|
||||||
|
} from 'react-icons/fa';
|
||||||
|
import { UserDetailsQuery } from '../graphql/queries';
|
||||||
|
import { UpdateUser } from '../graphql/mutation';
|
||||||
|
import EditUserModal from '../components/EditUserModal';
|
||||||
|
import DeleteUserModal from '../components/DeleteUserModal';
|
||||||
|
|
||||||
|
interface paginationPropTypes {
|
||||||
|
limit: number;
|
||||||
|
page: number;
|
||||||
|
offset: number;
|
||||||
|
total: number;
|
||||||
|
maxPages: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface userDataTypes {
|
||||||
|
id: string;
|
||||||
|
email: string;
|
||||||
|
email_verified: boolean;
|
||||||
|
given_name: string;
|
||||||
|
family_name: string;
|
||||||
|
middle_name: string;
|
||||||
|
nickname: string;
|
||||||
|
gender: string;
|
||||||
|
birthdate: string;
|
||||||
|
phone_number: string;
|
||||||
|
picture: string;
|
||||||
|
signup_methods: string;
|
||||||
|
roles: [string];
|
||||||
|
created_at: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
const getMaxPages = (pagination: paginationPropTypes) => {
|
||||||
|
const { limit, total } = pagination;
|
||||||
|
if (total > 1) {
|
||||||
|
return total % limit === 0
|
||||||
|
? total / limit
|
||||||
|
: parseInt(`${total / limit}`) + 1;
|
||||||
|
} else return 1;
|
||||||
|
};
|
||||||
|
|
||||||
|
const getLimits = (pagination: paginationPropTypes) => {
|
||||||
|
const { total } = pagination;
|
||||||
|
const limits = [5];
|
||||||
|
if (total > 10) {
|
||||||
|
for (let i = 10; i <= total && limits.length <= 10; i += 5) {
|
||||||
|
limits.push(i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return limits;
|
||||||
|
};
|
||||||
|
|
||||||
export default function Users() {
|
export default function Users() {
|
||||||
return <Box>Welcome to Users Page</Box>;
|
const client = useClient();
|
||||||
|
const toast = useToast();
|
||||||
|
const [paginationProps, setPaginationProps] =
|
||||||
|
React.useState<paginationPropTypes>({
|
||||||
|
limit: 5,
|
||||||
|
page: 1,
|
||||||
|
offset: 0,
|
||||||
|
total: 0,
|
||||||
|
maxPages: 1,
|
||||||
|
});
|
||||||
|
const [userList, setUserList] = React.useState<userDataTypes[]>([]);
|
||||||
|
const [loading, setLoading] = React.useState<boolean>(false);
|
||||||
|
const updateUserList = async () => {
|
||||||
|
setLoading(true);
|
||||||
|
const { data } = await client
|
||||||
|
.query(UserDetailsQuery, {
|
||||||
|
params: {
|
||||||
|
pagination: {
|
||||||
|
limit: paginationProps.limit,
|
||||||
|
page: paginationProps.page,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.toPromise();
|
||||||
|
if (data?._users) {
|
||||||
|
const { pagination, users } = data._users;
|
||||||
|
const maxPages = getMaxPages(pagination);
|
||||||
|
if (users && users.length > 0) {
|
||||||
|
setPaginationProps({ ...paginationProps, ...pagination, maxPages });
|
||||||
|
setUserList(users);
|
||||||
|
} else {
|
||||||
|
if (paginationProps.page !== 1) {
|
||||||
|
setPaginationProps({
|
||||||
|
...paginationProps,
|
||||||
|
...pagination,
|
||||||
|
maxPages,
|
||||||
|
page: 1,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
setLoading(false);
|
||||||
|
};
|
||||||
|
React.useEffect(() => {
|
||||||
|
updateUserList();
|
||||||
|
}, []);
|
||||||
|
React.useEffect(() => {
|
||||||
|
updateUserList();
|
||||||
|
}, [paginationProps.page, paginationProps.limit]);
|
||||||
|
|
||||||
|
const paginationHandler = (value: Record<string, number>) => {
|
||||||
|
setPaginationProps({ ...paginationProps, ...value });
|
||||||
|
};
|
||||||
|
|
||||||
|
const userVerificationHandler = async (user: userDataTypes) => {
|
||||||
|
const { id, email } = user;
|
||||||
|
const res = await client
|
||||||
|
.mutation(UpdateUser, {
|
||||||
|
params: {
|
||||||
|
id,
|
||||||
|
email,
|
||||||
|
email_verified: true,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.toPromise();
|
||||||
|
if (res.error) {
|
||||||
|
toast({
|
||||||
|
title: 'User verification failed',
|
||||||
|
isClosable: true,
|
||||||
|
status: 'error',
|
||||||
|
position: 'bottom-right',
|
||||||
|
});
|
||||||
|
} else if (res.data?._update_user?.id) {
|
||||||
|
toast({
|
||||||
|
title: 'User verification successful',
|
||||||
|
isClosable: true,
|
||||||
|
status: 'success',
|
||||||
|
position: 'bottom-right',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
updateUserList();
|
||||||
|
};
|
||||||
|
return (
|
||||||
|
<Box m="5" py="5" px="10" bg="white" rounded="md">
|
||||||
|
<Flex margin="2% 0" justifyContent="space-between" alignItems="center">
|
||||||
|
<Text fontSize="md" fontWeight="bold">
|
||||||
|
Users
|
||||||
|
</Text>
|
||||||
|
</Flex>
|
||||||
|
{!loading ? (
|
||||||
|
userList.length > 0 ? (
|
||||||
|
<Table variant="simple">
|
||||||
|
<Thead>
|
||||||
|
<Tr>
|
||||||
|
<Th>Email</Th>
|
||||||
|
<Th>Created At</Th>
|
||||||
|
<Th>Signup Methods</Th>
|
||||||
|
<Th>Roles</Th>
|
||||||
|
<Th>Verified</Th>
|
||||||
|
<Th>Actions</Th>
|
||||||
|
</Tr>
|
||||||
|
</Thead>
|
||||||
|
<Tbody>
|
||||||
|
{userList.map((user: userDataTypes) => {
|
||||||
|
const { email_verified, created_at, ...rest }: any = user;
|
||||||
|
return (
|
||||||
|
<Tr key={user.id} style={{ fontSize: 14 }}>
|
||||||
|
<Td>{user.email}</Td>
|
||||||
|
<Td>
|
||||||
|
{dayjs(user.created_at * 1000).format('MMM DD, YYYY')}
|
||||||
|
</Td>
|
||||||
|
<Td>{user.signup_methods}</Td>
|
||||||
|
<Td>{user.roles.join(', ')}</Td>
|
||||||
|
<Td>
|
||||||
|
<Tag
|
||||||
|
size="sm"
|
||||||
|
variant="outline"
|
||||||
|
colorScheme={user.email_verified ? 'green' : 'yellow'}
|
||||||
|
>
|
||||||
|
{user.email_verified.toString()}
|
||||||
|
</Tag>
|
||||||
|
</Td>
|
||||||
|
<Td>
|
||||||
|
<Menu>
|
||||||
|
<MenuButton as={Button} variant="unstyled" size="sm">
|
||||||
|
<Flex
|
||||||
|
justifyContent="space-between"
|
||||||
|
alignItems="center"
|
||||||
|
>
|
||||||
|
<Text fontSize="sm" fontWeight="light">
|
||||||
|
Menu
|
||||||
|
</Text>
|
||||||
|
<FaAngleDown style={{ marginLeft: 10 }} />
|
||||||
|
</Flex>
|
||||||
|
</MenuButton>
|
||||||
|
<MenuList>
|
||||||
|
{!user.email_verified && (
|
||||||
|
<MenuItem
|
||||||
|
onClick={() => userVerificationHandler(user)}
|
||||||
|
>
|
||||||
|
Verify User
|
||||||
|
</MenuItem>
|
||||||
|
)}
|
||||||
|
<EditUserModal
|
||||||
|
user={rest}
|
||||||
|
updateUserList={updateUserList}
|
||||||
|
/>
|
||||||
|
<DeleteUserModal
|
||||||
|
user={rest}
|
||||||
|
updateUserList={updateUserList}
|
||||||
|
/>
|
||||||
|
</MenuList>
|
||||||
|
</Menu>
|
||||||
|
</Td>
|
||||||
|
</Tr>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</Tbody>
|
||||||
|
{(paginationProps.maxPages > 1 || paginationProps.total >= 5) && (
|
||||||
|
<TableCaption>
|
||||||
|
<Flex
|
||||||
|
justifyContent="space-between"
|
||||||
|
alignItems="center"
|
||||||
|
m="2% 0"
|
||||||
|
>
|
||||||
|
<Flex flex="1">
|
||||||
|
<Tooltip label="First Page">
|
||||||
|
<IconButton
|
||||||
|
aria-label="icon button"
|
||||||
|
onClick={() =>
|
||||||
|
paginationHandler({
|
||||||
|
page: 1,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
isDisabled={paginationProps.page <= 1}
|
||||||
|
mr={4}
|
||||||
|
icon={<FaAngleDoubleLeft />}
|
||||||
|
/>
|
||||||
|
</Tooltip>
|
||||||
|
<Tooltip label="Previous Page">
|
||||||
|
<IconButton
|
||||||
|
aria-label="icon button"
|
||||||
|
onClick={() =>
|
||||||
|
paginationHandler({
|
||||||
|
page: paginationProps.page - 1,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
isDisabled={paginationProps.page <= 1}
|
||||||
|
icon={<FaAngleLeft />}
|
||||||
|
/>
|
||||||
|
</Tooltip>
|
||||||
|
</Flex>
|
||||||
|
<Flex
|
||||||
|
flex="8"
|
||||||
|
justifyContent="space-evenly"
|
||||||
|
alignItems="center"
|
||||||
|
>
|
||||||
|
<Text mr={8}>
|
||||||
|
Page{' '}
|
||||||
|
<Text fontWeight="bold" as="span">
|
||||||
|
{paginationProps.page}
|
||||||
|
</Text>{' '}
|
||||||
|
of{' '}
|
||||||
|
<Text fontWeight="bold" as="span">
|
||||||
|
{paginationProps.maxPages}
|
||||||
|
</Text>
|
||||||
|
</Text>
|
||||||
|
<Flex alignItems="center">
|
||||||
|
<Text flexShrink="0">Go to page:</Text>{' '}
|
||||||
|
<NumberInput
|
||||||
|
ml={2}
|
||||||
|
mr={8}
|
||||||
|
w={28}
|
||||||
|
min={1}
|
||||||
|
max={paginationProps.maxPages}
|
||||||
|
onChange={(value) =>
|
||||||
|
paginationHandler({
|
||||||
|
page: parseInt(value),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
value={paginationProps.page}
|
||||||
|
>
|
||||||
|
<NumberInputField />
|
||||||
|
<NumberInputStepper>
|
||||||
|
<NumberIncrementStepper />
|
||||||
|
<NumberDecrementStepper />
|
||||||
|
</NumberInputStepper>
|
||||||
|
</NumberInput>
|
||||||
|
</Flex>
|
||||||
|
<Select
|
||||||
|
w={32}
|
||||||
|
value={paginationProps.limit}
|
||||||
|
onChange={(e) =>
|
||||||
|
paginationHandler({
|
||||||
|
page: 1,
|
||||||
|
limit: parseInt(e.target.value),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{getLimits(paginationProps).map((pageSize) => (
|
||||||
|
<option key={pageSize} value={pageSize}>
|
||||||
|
Show {pageSize}
|
||||||
|
</option>
|
||||||
|
))}
|
||||||
|
</Select>
|
||||||
|
</Flex>
|
||||||
|
<Flex flex="1">
|
||||||
|
<Tooltip label="Next Page">
|
||||||
|
<IconButton
|
||||||
|
aria-label="icon button"
|
||||||
|
onClick={() =>
|
||||||
|
paginationHandler({
|
||||||
|
page: paginationProps.page + 1,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
isDisabled={
|
||||||
|
paginationProps.page >= paginationProps.maxPages
|
||||||
|
}
|
||||||
|
icon={<FaAngleRight />}
|
||||||
|
/>
|
||||||
|
</Tooltip>
|
||||||
|
<Tooltip label="Last Page">
|
||||||
|
<IconButton
|
||||||
|
aria-label="icon button"
|
||||||
|
onClick={() =>
|
||||||
|
paginationHandler({
|
||||||
|
page: paginationProps.maxPages,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
isDisabled={
|
||||||
|
paginationProps.page >= paginationProps.maxPages
|
||||||
|
}
|
||||||
|
ml={4}
|
||||||
|
icon={<FaAngleDoubleRight />}
|
||||||
|
/>
|
||||||
|
</Tooltip>
|
||||||
|
</Flex>
|
||||||
|
</Flex>
|
||||||
|
</TableCaption>
|
||||||
|
)}
|
||||||
|
</Table>
|
||||||
|
) : (
|
||||||
|
<Flex
|
||||||
|
flexDirection="column"
|
||||||
|
minH="25vh"
|
||||||
|
justifyContent="center"
|
||||||
|
alignItems="center"
|
||||||
|
>
|
||||||
|
<Center w="50px" marginRight="1.5%">
|
||||||
|
<FaExclamationCircle style={{ color: '#f0f0f0', fontSize: 70 }} />
|
||||||
|
</Center>
|
||||||
|
<Text
|
||||||
|
fontSize="2xl"
|
||||||
|
paddingRight="1%"
|
||||||
|
fontWeight="bold"
|
||||||
|
color="#d9d9d9"
|
||||||
|
>
|
||||||
|
No Data
|
||||||
|
</Text>
|
||||||
|
</Flex>
|
||||||
|
)
|
||||||
|
) : (
|
||||||
|
<Center minH="25vh">
|
||||||
|
<Spinner />
|
||||||
|
</Center>
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
@@ -26,6 +26,7 @@ export const AppRoutes = () => {
|
|||||||
<Route path="/" element={<Home />} />
|
<Route path="/" element={<Home />} />
|
||||||
<Route path="users" element={<Users />} />
|
<Route path="users" element={<Users />} />
|
||||||
<Route path="environment" element={<Environment />} />
|
<Route path="environment" element={<Environment />} />
|
||||||
|
<Route path="*" element={<Home />} />
|
||||||
</Route>
|
</Route>
|
||||||
</Routes>
|
</Routes>
|
||||||
</Suspense>
|
</Suspense>
|
||||||
@@ -35,6 +36,7 @@ export const AppRoutes = () => {
|
|||||||
<Suspense fallback={<></>}>
|
<Suspense fallback={<></>}>
|
||||||
<Routes>
|
<Routes>
|
||||||
<Route path="/" element={<Auth />} />
|
<Route path="/" element={<Auth />} />
|
||||||
|
<Route path="*" element={<Auth />} />
|
||||||
</Routes>
|
</Routes>
|
||||||
</Suspense>
|
</Suspense>
|
||||||
);
|
);
|
||||||
|
@@ -1,6 +1,66 @@
|
|||||||
|
import _ from 'lodash';
|
||||||
|
|
||||||
export const hasAdminSecret = () => {
|
export const hasAdminSecret = () => {
|
||||||
return (<any>window)['__authorizer__'].isOnboardingCompleted === true;
|
return (<any>window)['__authorizer__'].isOnboardingCompleted === true;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const capitalizeFirstLetter = (data: string): string =>
|
export const capitalizeFirstLetter = (data: string): string =>
|
||||||
data.charAt(0).toUpperCase() + data.slice(1);
|
data.charAt(0).toUpperCase() + data.slice(1);
|
||||||
|
|
||||||
|
const fallbackCopyTextToClipboard = (text: string) => {
|
||||||
|
const textArea = document.createElement('textarea');
|
||||||
|
|
||||||
|
textArea.value = text;
|
||||||
|
textArea.style.top = '0';
|
||||||
|
textArea.style.left = '0';
|
||||||
|
textArea.style.position = 'fixed';
|
||||||
|
|
||||||
|
document.body.appendChild(textArea);
|
||||||
|
textArea.focus();
|
||||||
|
textArea.select();
|
||||||
|
|
||||||
|
try {
|
||||||
|
const successful = document.execCommand('copy');
|
||||||
|
const msg = successful ? 'successful' : 'unsuccessful';
|
||||||
|
console.log('Fallback: Copying text command was ' + msg);
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Fallback: Oops, unable to copy', err);
|
||||||
|
}
|
||||||
|
document.body.removeChild(textArea);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const copyTextToClipboard = (text: string) => {
|
||||||
|
if (!navigator.clipboard) {
|
||||||
|
fallbackCopyTextToClipboard(text);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
navigator.clipboard.writeText(text).then(
|
||||||
|
() => {
|
||||||
|
console.log('Async: Copying to clipboard was successful!');
|
||||||
|
},
|
||||||
|
(err) => {
|
||||||
|
console.error('Async: Could not copy text: ', err);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getObjectDiff = (obj1: any, obj2: any) => {
|
||||||
|
const diff = Object.keys(obj1).reduce((result, key) => {
|
||||||
|
if (!obj2.hasOwnProperty(key)) {
|
||||||
|
result.push(key);
|
||||||
|
} else if (
|
||||||
|
_.isEqual(obj1[key], obj2[key]) ||
|
||||||
|
(obj1[key] === null && obj2[key] === '') ||
|
||||||
|
(obj1[key] &&
|
||||||
|
Array.isArray(obj1[key]) &&
|
||||||
|
obj1[key].length === 0 &&
|
||||||
|
obj2[key] === null)
|
||||||
|
) {
|
||||||
|
const resultKeyIndex = result.indexOf(key);
|
||||||
|
result.splice(resultKeyIndex, 1);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}, Object.keys(obj2));
|
||||||
|
|
||||||
|
return diff;
|
||||||
|
};
|
||||||
|
@@ -67,6 +67,7 @@
|
|||||||
|
|
||||||
/* Advanced Options */
|
/* Advanced Options */
|
||||||
"skipLibCheck": true /* Skip type checking of declaration files. */,
|
"skipLibCheck": true /* Skip type checking of declaration files. */,
|
||||||
"forceConsistentCasingInFileNames": true /* Disallow inconsistently-cased references to the same file. */
|
"forceConsistentCasingInFileNames": true /* Disallow inconsistently-cased references to the same file. */,
|
||||||
|
"lib": ["esnext", "dom"]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -16,6 +16,7 @@ const (
|
|||||||
// EnvKeyVersion key for build arg version
|
// EnvKeyVersion key for build arg version
|
||||||
EnvKeyVersion = "VERSION"
|
EnvKeyVersion = "VERSION"
|
||||||
// EnvKeyAuthorizerURL key for env variable AUTHORIZER_URL
|
// EnvKeyAuthorizerURL key for env variable AUTHORIZER_URL
|
||||||
|
// TODO: remove support AUTHORIZER_URL env
|
||||||
EnvKeyAuthorizerURL = "AUTHORIZER_URL"
|
EnvKeyAuthorizerURL = "AUTHORIZER_URL"
|
||||||
// EnvKeyPort key for env variable PORT
|
// EnvKeyPort key for env variable PORT
|
||||||
EnvKeyPort = "PORT"
|
EnvKeyPort = "PORT"
|
||||||
|
4
server/constants/pagination.go
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
package constants
|
||||||
|
|
||||||
|
// DefaultLimit is the default limit for pagination
|
||||||
|
var DefaultLimit = 10
|
46
server/cookie/admin_cookie.go
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
package cookie
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/url"
|
||||||
|
|
||||||
|
"github.com/authorizerdev/authorizer/server/constants"
|
||||||
|
"github.com/authorizerdev/authorizer/server/envstore"
|
||||||
|
"github.com/authorizerdev/authorizer/server/utils"
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
)
|
||||||
|
|
||||||
|
// SetAdminCookie sets the admin cookie in the response
|
||||||
|
func SetAdminCookie(gc *gin.Context, token string) {
|
||||||
|
secure := true
|
||||||
|
httpOnly := true
|
||||||
|
hostname := utils.GetHost(gc)
|
||||||
|
host, _ := utils.GetHostParts(hostname)
|
||||||
|
|
||||||
|
gc.SetCookie(envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyAdminCookieName), token, 3600, "/", host, secure, httpOnly)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetAdminCookie gets the admin cookie from the request
|
||||||
|
func GetAdminCookie(gc *gin.Context) (string, error) {
|
||||||
|
cookie, err := gc.Request.Cookie(envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyAdminCookieName))
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
// cookie escapes special characters like $
|
||||||
|
// hence we need to unescape before comparing
|
||||||
|
decodedValue, err := url.QueryUnescape(cookie.Value)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return decodedValue, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteAdminCookie sets the response cookie to empty
|
||||||
|
func DeleteAdminCookie(gc *gin.Context) {
|
||||||
|
secure := true
|
||||||
|
httpOnly := true
|
||||||
|
hostname := utils.GetHost(gc)
|
||||||
|
host, _ := utils.GetHostParts(hostname)
|
||||||
|
|
||||||
|
gc.SetCookie(envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyAdminCookieName), "", -1, "/", host, secure, httpOnly)
|
||||||
|
}
|
102
server/cookie/cookie.go
Normal file
@@ -0,0 +1,102 @@
|
|||||||
|
package cookie
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
|
||||||
|
"github.com/authorizerdev/authorizer/server/constants"
|
||||||
|
"github.com/authorizerdev/authorizer/server/envstore"
|
||||||
|
"github.com/authorizerdev/authorizer/server/utils"
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
)
|
||||||
|
|
||||||
|
// SetCookie sets the cookie in the response. It sets 4 cookies
|
||||||
|
// 1 COOKIE_NAME.access_token jwt token for the host (temp.abc.com)
|
||||||
|
// 2 COOKIE_NAME.access_token.domain jwt token for the domain (abc.com).
|
||||||
|
// 3 COOKIE_NAME.fingerprint fingerprint hash for the refresh token verification.
|
||||||
|
// 4 COOKIE_NAME.refresh_token refresh token
|
||||||
|
// Note all sites don't allow 2nd type of cookie
|
||||||
|
func SetCookie(gc *gin.Context, accessToken, refreshToken, fingerprintHash string) {
|
||||||
|
secure := true
|
||||||
|
httpOnly := true
|
||||||
|
hostname := utils.GetHost(gc)
|
||||||
|
host, _ := utils.GetHostParts(hostname)
|
||||||
|
domain := utils.GetDomainName(hostname)
|
||||||
|
if domain != "localhost" {
|
||||||
|
domain = "." + domain
|
||||||
|
}
|
||||||
|
|
||||||
|
year := 60 * 60 * 24 * 365
|
||||||
|
thirtyMin := 60 * 30
|
||||||
|
|
||||||
|
gc.SetSameSite(http.SameSiteNoneMode)
|
||||||
|
// set cookie for host
|
||||||
|
gc.SetCookie(envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyCookieName)+".access_token", accessToken, thirtyMin, "/", host, secure, httpOnly)
|
||||||
|
|
||||||
|
// in case of subdomain, set cookie for domain
|
||||||
|
gc.SetCookie(envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyCookieName)+".access_token.domain", accessToken, thirtyMin, "/", domain, secure, httpOnly)
|
||||||
|
|
||||||
|
// set finger print cookie (this should be accessed via cookie only)
|
||||||
|
gc.SetCookie(envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyCookieName)+".fingerprint", fingerprintHash, year, "/", host, secure, httpOnly)
|
||||||
|
|
||||||
|
// set refresh token cookie (this should be accessed via cookie only)
|
||||||
|
gc.SetCookie(envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyCookieName)+".refresh_token", refreshToken, year, "/", host, secure, httpOnly)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetAccessTokenCookie to get access token cookie from the request
|
||||||
|
func GetAccessTokenCookie(gc *gin.Context) (string, error) {
|
||||||
|
cookie, err := gc.Request.Cookie(envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyCookieName) + ".access_token")
|
||||||
|
if err != nil {
|
||||||
|
cookie, err = gc.Request.Cookie(envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyCookieName) + ".access_token.domain")
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return cookie.Value, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetRefreshTokenCookie to get refresh token cookie
|
||||||
|
func GetRefreshTokenCookie(gc *gin.Context) (string, error) {
|
||||||
|
cookie, err := gc.Request.Cookie(envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyCookieName) + ".refresh_token")
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
return cookie.Value, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetFingerPrintCookie to get fingerprint cookie
|
||||||
|
func GetFingerPrintCookie(gc *gin.Context) (string, error) {
|
||||||
|
cookie, err := gc.Request.Cookie(envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyCookieName) + ".fingerprint")
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
// cookie escapes special characters like $
|
||||||
|
// hence we need to unescape before comparing
|
||||||
|
decodedValue, err := url.QueryUnescape(cookie.Value)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
return decodedValue, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteCookie sets response cookies to expire
|
||||||
|
func DeleteCookie(gc *gin.Context) {
|
||||||
|
secure := true
|
||||||
|
httpOnly := true
|
||||||
|
hostname := utils.GetHost(gc)
|
||||||
|
host, _ := utils.GetHostParts(hostname)
|
||||||
|
domain := utils.GetDomainName(hostname)
|
||||||
|
if domain != "localhost" {
|
||||||
|
domain = "." + domain
|
||||||
|
}
|
||||||
|
|
||||||
|
gc.SetSameSite(http.SameSiteNoneMode)
|
||||||
|
gc.SetCookie(envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyCookieName)+".access_token", "", -1, "/", host, secure, httpOnly)
|
||||||
|
gc.SetCookie(envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyCookieName)+".access_token.domain", "", -1, "/", domain, secure, httpOnly)
|
||||||
|
gc.SetCookie(envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyCookieName)+".fingerprint", "", -1, "/", host, secure, httpOnly)
|
||||||
|
gc.SetCookie(envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyCookieName)+".refresh_token", "", -1, "/", host, secure, httpOnly)
|
||||||
|
}
|
@@ -4,8 +4,8 @@ package models
|
|||||||
type Env struct {
|
type Env struct {
|
||||||
Key string `json:"_key,omitempty" bson:"_key"` // for arangodb
|
Key string `json:"_key,omitempty" bson:"_key"` // for arangodb
|
||||||
ID string `gorm:"primaryKey;type:char(36)" json:"_id" bson:"_id"`
|
ID string `gorm:"primaryKey;type:char(36)" json:"_id" bson:"_id"`
|
||||||
EnvData []byte `gorm:"type:text" json:"env" bson:"env"`
|
EnvData string `gorm:"type:text" json:"env" bson:"env"`
|
||||||
Hash string `gorm:"type:hash" json:"hash" bson:"hash"`
|
Hash string `gorm:"type:text" json:"hash" bson:"hash"`
|
||||||
UpdatedAt int64 `gorm:"autoUpdateTime" json:"updated_at" bson:"updated_at"`
|
UpdatedAt int64 `json:"updated_at" bson:"updated_at"`
|
||||||
CreatedAt int64 `gorm:"autoCreateTime" json:"created_at" bson:"created_at"`
|
CreatedAt int64 `json:"created_at" bson:"created_at"`
|
||||||
}
|
}
|
||||||
|
@@ -5,9 +5,9 @@ type Session struct {
|
|||||||
Key string `json:"_key,omitempty" bson:"_key,omitempty"` // for arangodb
|
Key string `json:"_key,omitempty" bson:"_key,omitempty"` // for arangodb
|
||||||
ID string `gorm:"primaryKey;type:char(36)" json:"_id" bson:"_id"`
|
ID string `gorm:"primaryKey;type:char(36)" json:"_id" bson:"_id"`
|
||||||
UserID string `gorm:"type:char(36),index:" json:"user_id" bson:"user_id"`
|
UserID string `gorm:"type:char(36),index:" json:"user_id" bson:"user_id"`
|
||||||
User User `json:"-" bson:"-"`
|
User User `gorm:"constraint:OnUpdate:CASCADE,OnDelete:CASCADE;" json:"-" bson:"-"`
|
||||||
UserAgent string `json:"user_agent" bson:"user_agent"`
|
UserAgent string `json:"user_agent" bson:"user_agent"`
|
||||||
IP string `json:"ip" bson:"ip"`
|
IP string `json:"ip" bson:"ip"`
|
||||||
CreatedAt int64 `gorm:"autoCreateTime" json:"created_at" bson:"created_at"`
|
CreatedAt int64 `json:"created_at" bson:"created_at"`
|
||||||
UpdatedAt int64 `gorm:"autoUpdateTime" json:"updated_at" bson:"updated_at"`
|
UpdatedAt int64 `json:"updated_at" bson:"updated_at"`
|
||||||
}
|
}
|
||||||
|
@@ -1,5 +1,11 @@
|
|||||||
package models
|
package models
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/authorizerdev/authorizer/server/graph/model"
|
||||||
|
)
|
||||||
|
|
||||||
// User model for db
|
// User model for db
|
||||||
type User struct {
|
type User struct {
|
||||||
Key string `json:"_key,omitempty" bson:"_key"` // for arangodb
|
Key string `json:"_key,omitempty" bson:"_key"` // for arangodb
|
||||||
@@ -19,6 +25,30 @@ type User struct {
|
|||||||
PhoneNumberVerifiedAt *int64 `json:"phone_number_verified_at" bson:"phone_number_verified_at"`
|
PhoneNumberVerifiedAt *int64 `json:"phone_number_verified_at" bson:"phone_number_verified_at"`
|
||||||
Picture *string `gorm:"type:text" json:"picture" bson:"picture"`
|
Picture *string `gorm:"type:text" json:"picture" bson:"picture"`
|
||||||
Roles string `json:"roles" bson:"roles"`
|
Roles string `json:"roles" bson:"roles"`
|
||||||
UpdatedAt int64 `gorm:"autoUpdateTime" json:"updated_at" bson:"updated_at"`
|
UpdatedAt int64 `json:"updated_at" bson:"updated_at"`
|
||||||
CreatedAt int64 `gorm:"autoCreateTime" json:"created_at" bson:"created_at"`
|
CreatedAt int64 `json:"created_at" bson:"created_at"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (user *User) AsAPIUser() *model.User {
|
||||||
|
isEmailVerified := user.EmailVerifiedAt != nil
|
||||||
|
isPhoneVerified := user.PhoneNumberVerifiedAt != nil
|
||||||
|
return &model.User{
|
||||||
|
ID: user.ID,
|
||||||
|
Email: user.Email,
|
||||||
|
EmailVerified: isEmailVerified,
|
||||||
|
SignupMethods: user.SignupMethods,
|
||||||
|
GivenName: user.GivenName,
|
||||||
|
FamilyName: user.FamilyName,
|
||||||
|
MiddleName: user.MiddleName,
|
||||||
|
Nickname: user.Nickname,
|
||||||
|
PreferredUsername: &user.Email,
|
||||||
|
Gender: user.Gender,
|
||||||
|
Birthdate: user.Birthdate,
|
||||||
|
PhoneNumber: user.PhoneNumber,
|
||||||
|
PhoneNumberVerified: &isPhoneVerified,
|
||||||
|
Picture: user.Picture,
|
||||||
|
Roles: strings.Split(user.Roles, ","),
|
||||||
|
CreatedAt: &user.CreatedAt,
|
||||||
|
UpdatedAt: &user.UpdatedAt,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -1,5 +1,7 @@
|
|||||||
package models
|
package models
|
||||||
|
|
||||||
|
import "github.com/authorizerdev/authorizer/server/graph/model"
|
||||||
|
|
||||||
// VerificationRequest model for db
|
// VerificationRequest model for db
|
||||||
type VerificationRequest struct {
|
type VerificationRequest struct {
|
||||||
Key string `json:"_key,omitempty" bson:"_key"` // for arangodb
|
Key string `json:"_key,omitempty" bson:"_key"` // for arangodb
|
||||||
@@ -7,7 +9,19 @@ type VerificationRequest struct {
|
|||||||
Token string `gorm:"type:text" json:"token" bson:"token"`
|
Token string `gorm:"type:text" json:"token" bson:"token"`
|
||||||
Identifier string `gorm:"uniqueIndex:idx_email_identifier" json:"identifier" bson:"identifier"`
|
Identifier string `gorm:"uniqueIndex:idx_email_identifier" json:"identifier" bson:"identifier"`
|
||||||
ExpiresAt int64 `json:"expires_at" bson:"expires_at"`
|
ExpiresAt int64 `json:"expires_at" bson:"expires_at"`
|
||||||
CreatedAt int64 `gorm:"autoCreateTime" json:"created_at" bson:"created_at"`
|
CreatedAt int64 `json:"created_at" bson:"created_at"`
|
||||||
UpdatedAt int64 `gorm:"autoUpdateTime" json:"updated_at" bson:"updated_at"`
|
UpdatedAt int64 `json:"updated_at" bson:"updated_at"`
|
||||||
Email string `gorm:"uniqueIndex:idx_email_identifier" json:"email" bson:"email"`
|
Email string `gorm:"uniqueIndex:idx_email_identifier" json:"email" bson:"email"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (v *VerificationRequest) AsAPIVerificationRequest() *model.VerificationRequest {
|
||||||
|
return &model.VerificationRequest{
|
||||||
|
ID: v.ID,
|
||||||
|
Token: &v.Token,
|
||||||
|
Identifier: &v.Identifier,
|
||||||
|
Expires: &v.ExpiresAt,
|
||||||
|
CreatedAt: &v.CreatedAt,
|
||||||
|
UpdatedAt: &v.UpdatedAt,
|
||||||
|
Email: &v.Email,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@@ -1,6 +1,7 @@
|
|||||||
package arangodb
|
package arangodb
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
"strings"
|
"strings"
|
||||||
@@ -11,6 +12,7 @@ import (
|
|||||||
"github.com/authorizerdev/authorizer/server/constants"
|
"github.com/authorizerdev/authorizer/server/constants"
|
||||||
"github.com/authorizerdev/authorizer/server/db/models"
|
"github.com/authorizerdev/authorizer/server/db/models"
|
||||||
"github.com/authorizerdev/authorizer/server/envstore"
|
"github.com/authorizerdev/authorizer/server/envstore"
|
||||||
|
"github.com/authorizerdev/authorizer/server/graph/model"
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -66,32 +68,40 @@ func (p *provider) DeleteUser(user models.User) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ListUsers to get list of users from database
|
// ListUsers to get list of users from database
|
||||||
func (p *provider) ListUsers() ([]models.User, error) {
|
func (p *provider) ListUsers(pagination model.Pagination) (*model.Users, error) {
|
||||||
var users []models.User
|
var users []*model.User
|
||||||
query := fmt.Sprintf("FOR d in %s RETURN d", models.Collections.User)
|
ctx := driver.WithQueryFullCount(context.Background())
|
||||||
|
|
||||||
cursor, err := p.db.Query(nil, query, nil)
|
query := fmt.Sprintf("FOR d in %s SORT d.created_at DESC LIMIT %d, %d RETURN d", models.Collections.User, pagination.Offset, pagination.Limit)
|
||||||
|
|
||||||
|
cursor, err := p.db.Query(ctx, query, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return users, err
|
return nil, err
|
||||||
}
|
}
|
||||||
defer cursor.Close()
|
defer cursor.Close()
|
||||||
|
|
||||||
|
paginationClone := pagination
|
||||||
|
paginationClone.Total = cursor.Statistics().FullCount()
|
||||||
|
|
||||||
for {
|
for {
|
||||||
var user models.User
|
var user models.User
|
||||||
meta, err := cursor.ReadDocument(nil, &user)
|
meta, err := cursor.ReadDocument(nil, &user)
|
||||||
|
|
||||||
if driver.IsNoMoreDocuments(err) {
|
if arangoDriver.IsNoMoreDocuments(err) {
|
||||||
break
|
break
|
||||||
} else if err != nil {
|
} else if err != nil {
|
||||||
return users, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if meta.Key != "" {
|
if meta.Key != "" {
|
||||||
users = append(users, user)
|
users = append(users, user.AsAPIUser())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return users, nil
|
return &model.Users{
|
||||||
|
Pagination: &paginationClone,
|
||||||
|
Users: users,
|
||||||
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetUserByEmail to get user information from database using email address
|
// GetUserByEmail to get user information from database using email address
|
||||||
|
@@ -1,12 +1,14 @@
|
|||||||
package arangodb
|
package arangodb
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/arangodb/go-driver"
|
"github.com/arangodb/go-driver"
|
||||||
"github.com/authorizerdev/authorizer/server/db/models"
|
"github.com/authorizerdev/authorizer/server/db/models"
|
||||||
|
"github.com/authorizerdev/authorizer/server/graph/model"
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -93,17 +95,20 @@ func (p *provider) GetVerificationRequestByEmail(email string, identifier string
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ListVerificationRequests to get list of verification requests from database
|
// ListVerificationRequests to get list of verification requests from database
|
||||||
func (p *provider) ListVerificationRequests() ([]models.VerificationRequest, error) {
|
func (p *provider) ListVerificationRequests(pagination model.Pagination) (*model.VerificationRequests, error) {
|
||||||
var verificationRequests []models.VerificationRequest
|
var verificationRequests []*model.VerificationRequest
|
||||||
|
ctx := driver.WithQueryFullCount(context.Background())
|
||||||
|
query := fmt.Sprintf("FOR d in %s SORT d.created_at DESC LIMIT %d, %d RETURN d", models.Collections.VerificationRequest, pagination.Offset, pagination.Limit)
|
||||||
|
|
||||||
query := fmt.Sprintf("FOR d in %s RETURN d", models.Collections.VerificationRequest)
|
cursor, err := p.db.Query(ctx, query, nil)
|
||||||
|
|
||||||
cursor, err := p.db.Query(nil, query, nil)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return verificationRequests, err
|
return nil, err
|
||||||
}
|
}
|
||||||
defer cursor.Close()
|
defer cursor.Close()
|
||||||
|
|
||||||
|
paginationClone := pagination
|
||||||
|
paginationClone.Total = cursor.Statistics().FullCount()
|
||||||
|
|
||||||
for {
|
for {
|
||||||
var verificationRequest models.VerificationRequest
|
var verificationRequest models.VerificationRequest
|
||||||
meta, err := cursor.ReadDocument(nil, &verificationRequest)
|
meta, err := cursor.ReadDocument(nil, &verificationRequest)
|
||||||
@@ -111,16 +116,19 @@ func (p *provider) ListVerificationRequests() ([]models.VerificationRequest, err
|
|||||||
if driver.IsNoMoreDocuments(err) {
|
if driver.IsNoMoreDocuments(err) {
|
||||||
break
|
break
|
||||||
} else if err != nil {
|
} else if err != nil {
|
||||||
return verificationRequests, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if meta.Key != "" {
|
if meta.Key != "" {
|
||||||
verificationRequests = append(verificationRequests, verificationRequest)
|
verificationRequests = append(verificationRequests, verificationRequest.AsAPIVerificationRequest())
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return verificationRequests, nil
|
return &model.VerificationRequests{
|
||||||
|
VerificationRequests: verificationRequests,
|
||||||
|
Pagination: &paginationClone,
|
||||||
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// DeleteVerificationRequest to delete verification request from database
|
// DeleteVerificationRequest to delete verification request from database
|
||||||
|
@@ -8,6 +8,7 @@ import (
|
|||||||
"github.com/authorizerdev/authorizer/server/constants"
|
"github.com/authorizerdev/authorizer/server/constants"
|
||||||
"github.com/authorizerdev/authorizer/server/db/models"
|
"github.com/authorizerdev/authorizer/server/db/models"
|
||||||
"github.com/authorizerdev/authorizer/server/envstore"
|
"github.com/authorizerdev/authorizer/server/envstore"
|
||||||
|
"github.com/authorizerdev/authorizer/server/graph/model"
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
"go.mongodb.org/mongo-driver/bson"
|
"go.mongodb.org/mongo-driver/bson"
|
||||||
"go.mongodb.org/mongo-driver/mongo/options"
|
"go.mongodb.org/mongo-driver/mongo/options"
|
||||||
@@ -60,13 +61,29 @@ func (p *provider) DeleteUser(user models.User) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ListUsers to get list of users from database
|
// ListUsers to get list of users from database
|
||||||
func (p *provider) ListUsers() ([]models.User, error) {
|
func (p *provider) ListUsers(pagination model.Pagination) (*model.Users, error) {
|
||||||
var users []models.User
|
var users []*model.User
|
||||||
|
opts := options.Find()
|
||||||
|
opts.SetLimit(pagination.Limit)
|
||||||
|
opts.SetSkip(pagination.Offset)
|
||||||
|
opts.SetSort(bson.M{"created_at": -1})
|
||||||
|
|
||||||
|
paginationClone := pagination
|
||||||
|
// TODO add pagination total
|
||||||
|
|
||||||
userCollection := p.db.Collection(models.Collections.User, options.Collection())
|
userCollection := p.db.Collection(models.Collections.User, options.Collection())
|
||||||
cursor, err := userCollection.Find(nil, bson.M{}, options.Find())
|
count, err := userCollection.CountDocuments(nil, bson.M{}, options.Count())
|
||||||
|
if err != nil {
|
||||||
|
log.Println("error getting total users:", err)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
paginationClone.Total = count
|
||||||
|
|
||||||
|
cursor, err := userCollection.Find(nil, bson.M{}, opts)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Println("error getting users:", err)
|
log.Println("error getting users:", err)
|
||||||
return users, err
|
return nil, err
|
||||||
}
|
}
|
||||||
defer cursor.Close(nil)
|
defer cursor.Close(nil)
|
||||||
|
|
||||||
@@ -74,12 +91,15 @@ func (p *provider) ListUsers() ([]models.User, error) {
|
|||||||
var user models.User
|
var user models.User
|
||||||
err := cursor.Decode(&user)
|
err := cursor.Decode(&user)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return users, err
|
return nil, err
|
||||||
}
|
}
|
||||||
users = append(users, user)
|
users = append(users, user.AsAPIUser())
|
||||||
}
|
}
|
||||||
|
|
||||||
return users, nil
|
return &model.Users{
|
||||||
|
Pagination: &paginationClone,
|
||||||
|
Users: users,
|
||||||
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetUserByEmail to get user information from database using email address
|
// GetUserByEmail to get user information from database using email address
|
||||||
|
@@ -5,6 +5,7 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/authorizerdev/authorizer/server/db/models"
|
"github.com/authorizerdev/authorizer/server/db/models"
|
||||||
|
"github.com/authorizerdev/authorizer/server/graph/model"
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
"go.mongodb.org/mongo-driver/bson"
|
"go.mongodb.org/mongo-driver/bson"
|
||||||
"go.mongodb.org/mongo-driver/mongo/options"
|
"go.mongodb.org/mongo-driver/mongo/options"
|
||||||
@@ -56,13 +57,24 @@ func (p *provider) GetVerificationRequestByEmail(email string, identifier string
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ListVerificationRequests to get list of verification requests from database
|
// ListVerificationRequests to get list of verification requests from database
|
||||||
func (p *provider) ListVerificationRequests() ([]models.VerificationRequest, error) {
|
func (p *provider) ListVerificationRequests(pagination model.Pagination) (*model.VerificationRequests, error) {
|
||||||
var verificationRequests []models.VerificationRequest
|
var verificationRequests []*model.VerificationRequest
|
||||||
|
|
||||||
|
opts := options.Find()
|
||||||
|
opts.SetLimit(pagination.Limit)
|
||||||
|
opts.SetSkip(pagination.Offset)
|
||||||
|
opts.SetSort(bson.M{"created_at": -1})
|
||||||
|
|
||||||
verificationRequestCollection := p.db.Collection(models.Collections.VerificationRequest, options.Collection())
|
verificationRequestCollection := p.db.Collection(models.Collections.VerificationRequest, options.Collection())
|
||||||
cursor, err := verificationRequestCollection.Find(nil, bson.M{}, options.Find())
|
|
||||||
|
verificationRequestCollectionCount, err := verificationRequestCollection.CountDocuments(nil, bson.M{})
|
||||||
|
paginationClone := pagination
|
||||||
|
paginationClone.Total = verificationRequestCollectionCount
|
||||||
|
|
||||||
|
cursor, err := verificationRequestCollection.Find(nil, bson.M{}, opts)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Println("error getting verification requests:", err)
|
log.Println("error getting verification requests:", err)
|
||||||
return verificationRequests, err
|
return nil, err
|
||||||
}
|
}
|
||||||
defer cursor.Close(nil)
|
defer cursor.Close(nil)
|
||||||
|
|
||||||
@@ -70,12 +82,15 @@ func (p *provider) ListVerificationRequests() ([]models.VerificationRequest, err
|
|||||||
var verificationRequest models.VerificationRequest
|
var verificationRequest models.VerificationRequest
|
||||||
err := cursor.Decode(&verificationRequest)
|
err := cursor.Decode(&verificationRequest)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return verificationRequests, err
|
return nil, err
|
||||||
}
|
}
|
||||||
verificationRequests = append(verificationRequests, verificationRequest)
|
verificationRequests = append(verificationRequests, verificationRequest.AsAPIVerificationRequest())
|
||||||
}
|
}
|
||||||
|
|
||||||
return verificationRequests, nil
|
return &model.VerificationRequests{
|
||||||
|
VerificationRequests: verificationRequests,
|
||||||
|
Pagination: &paginationClone,
|
||||||
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// DeleteVerificationRequest to delete verification request from database
|
// DeleteVerificationRequest to delete verification request from database
|
||||||
|
@@ -1,6 +1,9 @@
|
|||||||
package providers
|
package providers
|
||||||
|
|
||||||
import "github.com/authorizerdev/authorizer/server/db/models"
|
import (
|
||||||
|
"github.com/authorizerdev/authorizer/server/db/models"
|
||||||
|
"github.com/authorizerdev/authorizer/server/graph/model"
|
||||||
|
)
|
||||||
|
|
||||||
type Provider interface {
|
type Provider interface {
|
||||||
// AddUser to save user information in database
|
// AddUser to save user information in database
|
||||||
@@ -10,7 +13,7 @@ type Provider interface {
|
|||||||
// DeleteUser to delete user information from database
|
// DeleteUser to delete user information from database
|
||||||
DeleteUser(user models.User) error
|
DeleteUser(user models.User) error
|
||||||
// ListUsers to get list of users from database
|
// ListUsers to get list of users from database
|
||||||
ListUsers() ([]models.User, error)
|
ListUsers(pagination model.Pagination) (*model.Users, error)
|
||||||
// GetUserByEmail to get user information from database using email address
|
// GetUserByEmail to get user information from database using email address
|
||||||
GetUserByEmail(email string) (models.User, error)
|
GetUserByEmail(email string) (models.User, error)
|
||||||
// GetUserByID to get user information from database using user ID
|
// GetUserByID to get user information from database using user ID
|
||||||
@@ -23,7 +26,7 @@ type Provider interface {
|
|||||||
// GetVerificationRequestByEmail to get verification request by email from database
|
// GetVerificationRequestByEmail to get verification request by email from database
|
||||||
GetVerificationRequestByEmail(email string, identifier string) (models.VerificationRequest, error)
|
GetVerificationRequestByEmail(email string, identifier string) (models.VerificationRequest, error)
|
||||||
// ListVerificationRequests to get list of verification requests from database
|
// ListVerificationRequests to get list of verification requests from database
|
||||||
ListVerificationRequests() ([]models.VerificationRequest, error)
|
ListVerificationRequests(pagination model.Pagination) (*model.VerificationRequests, error)
|
||||||
// DeleteVerificationRequest to delete verification request from database
|
// DeleteVerificationRequest to delete verification request from database
|
||||||
DeleteVerificationRequest(verificationRequest models.VerificationRequest) error
|
DeleteVerificationRequest(verificationRequest models.VerificationRequest) error
|
||||||
|
|
||||||
|
@@ -15,8 +15,10 @@ func (p *provider) AddEnv(env models.Env) (models.Env, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
env.Key = env.ID
|
env.Key = env.ID
|
||||||
result := p.db.Create(&env)
|
env.CreatedAt = time.Now().Unix()
|
||||||
|
env.UpdatedAt = time.Now().Unix()
|
||||||
|
|
||||||
|
result := p.db.Create(&env)
|
||||||
if result.Error != nil {
|
if result.Error != nil {
|
||||||
log.Println("error adding config:", result.Error)
|
log.Println("error adding config:", result.Error)
|
||||||
return env, result.Error
|
return env, result.Error
|
||||||
|
@@ -2,6 +2,7 @@ package sql
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"log"
|
"log"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/authorizerdev/authorizer/server/db/models"
|
"github.com/authorizerdev/authorizer/server/db/models"
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
@@ -15,6 +16,8 @@ func (p *provider) AddSession(session models.Session) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
session.Key = session.ID
|
session.Key = session.ID
|
||||||
|
session.CreatedAt = time.Now().Unix()
|
||||||
|
session.UpdatedAt = time.Now().Unix()
|
||||||
res := p.db.Clauses(
|
res := p.db.Clauses(
|
||||||
clause.OnConflict{
|
clause.OnConflict{
|
||||||
DoNothing: true,
|
DoNothing: true,
|
||||||
|
@@ -8,6 +8,7 @@ import (
|
|||||||
"github.com/authorizerdev/authorizer/server/constants"
|
"github.com/authorizerdev/authorizer/server/constants"
|
||||||
"github.com/authorizerdev/authorizer/server/db/models"
|
"github.com/authorizerdev/authorizer/server/db/models"
|
||||||
"github.com/authorizerdev/authorizer/server/envstore"
|
"github.com/authorizerdev/authorizer/server/envstore"
|
||||||
|
"github.com/authorizerdev/authorizer/server/graph/model"
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
"gorm.io/gorm/clause"
|
"gorm.io/gorm/clause"
|
||||||
)
|
)
|
||||||
@@ -22,6 +23,8 @@ func (p *provider) AddUser(user models.User) (models.User, error) {
|
|||||||
user.Roles = strings.Join(envstore.EnvInMemoryStoreObj.GetSliceStoreEnvVariable(constants.EnvKeyDefaultRoles), ",")
|
user.Roles = strings.Join(envstore.EnvInMemoryStoreObj.GetSliceStoreEnvVariable(constants.EnvKeyDefaultRoles), ",")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
user.CreatedAt = time.Now().Unix()
|
||||||
|
user.UpdatedAt = time.Now().Unix()
|
||||||
user.Key = user.ID
|
user.Key = user.ID
|
||||||
result := p.db.Clauses(
|
result := p.db.Clauses(
|
||||||
clause.OnConflict{
|
clause.OnConflict{
|
||||||
@@ -64,15 +67,32 @@ func (p *provider) DeleteUser(user models.User) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ListUsers to get list of users from database
|
// ListUsers to get list of users from database
|
||||||
func (p *provider) ListUsers() ([]models.User, error) {
|
func (p *provider) ListUsers(pagination model.Pagination) (*model.Users, error) {
|
||||||
var users []models.User
|
var users []models.User
|
||||||
result := p.db.Find(&users)
|
result := p.db.Limit(int(pagination.Limit)).Offset(int(pagination.Offset)).Order("created_at DESC").Find(&users)
|
||||||
if result.Error != nil {
|
if result.Error != nil {
|
||||||
log.Println("error getting users:", result.Error)
|
log.Println("error getting users:", result.Error)
|
||||||
return users, result.Error
|
return nil, result.Error
|
||||||
}
|
}
|
||||||
|
|
||||||
return users, nil
|
responseUsers := []*model.User{}
|
||||||
|
for _, user := range users {
|
||||||
|
responseUsers = append(responseUsers, user.AsAPIUser())
|
||||||
|
}
|
||||||
|
|
||||||
|
var total int64
|
||||||
|
totalRes := p.db.Model(&models.User{}).Count(&total)
|
||||||
|
if totalRes.Error != nil {
|
||||||
|
return nil, totalRes.Error
|
||||||
|
}
|
||||||
|
|
||||||
|
paginationClone := pagination
|
||||||
|
paginationClone.Total = total
|
||||||
|
|
||||||
|
return &model.Users{
|
||||||
|
Pagination: &paginationClone,
|
||||||
|
Users: responseUsers,
|
||||||
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetUserByEmail to get user information from database using email address
|
// GetUserByEmail to get user information from database using email address
|
||||||
|
@@ -2,8 +2,10 @@ package sql
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"log"
|
"log"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/authorizerdev/authorizer/server/db/models"
|
"github.com/authorizerdev/authorizer/server/db/models"
|
||||||
|
"github.com/authorizerdev/authorizer/server/graph/model"
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
"gorm.io/gorm/clause"
|
"gorm.io/gorm/clause"
|
||||||
)
|
)
|
||||||
@@ -15,6 +17,8 @@ func (p *provider) AddVerificationRequest(verificationRequest models.Verificatio
|
|||||||
}
|
}
|
||||||
|
|
||||||
verificationRequest.Key = verificationRequest.ID
|
verificationRequest.Key = verificationRequest.ID
|
||||||
|
verificationRequest.CreatedAt = time.Now().Unix()
|
||||||
|
verificationRequest.UpdatedAt = time.Now().Unix()
|
||||||
result := p.db.Clauses(clause.OnConflict{
|
result := p.db.Clauses(clause.OnConflict{
|
||||||
Columns: []clause.Column{{Name: "email"}, {Name: "identifier"}},
|
Columns: []clause.Column{{Name: "email"}, {Name: "identifier"}},
|
||||||
DoUpdates: clause.AssignmentColumns([]string{"token", "expires_at"}),
|
DoUpdates: clause.AssignmentColumns([]string{"token", "expires_at"}),
|
||||||
@@ -56,15 +60,33 @@ func (p *provider) GetVerificationRequestByEmail(email string, identifier string
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ListVerificationRequests to get list of verification requests from database
|
// ListVerificationRequests to get list of verification requests from database
|
||||||
func (p *provider) ListVerificationRequests() ([]models.VerificationRequest, error) {
|
func (p *provider) ListVerificationRequests(pagination model.Pagination) (*model.VerificationRequests, error) {
|
||||||
var verificationRequests []models.VerificationRequest
|
var verificationRequests []models.VerificationRequest
|
||||||
|
|
||||||
result := p.db.Find(&verificationRequests)
|
result := p.db.Limit(int(pagination.Limit)).Offset(int(pagination.Offset)).Order("created_at DESC").Find(&verificationRequests)
|
||||||
if result.Error != nil {
|
if result.Error != nil {
|
||||||
log.Println("error getting verification requests:", result.Error)
|
log.Println("error getting verification requests:", result.Error)
|
||||||
return verificationRequests, result.Error
|
return nil, result.Error
|
||||||
}
|
}
|
||||||
return verificationRequests, nil
|
|
||||||
|
responseVerificationRequests := []*model.VerificationRequest{}
|
||||||
|
for _, v := range verificationRequests {
|
||||||
|
responseVerificationRequests = append(responseVerificationRequests, v.AsAPIVerificationRequest())
|
||||||
|
}
|
||||||
|
|
||||||
|
var total int64
|
||||||
|
totalRes := p.db.Model(&models.VerificationRequest{}).Count(&total)
|
||||||
|
if totalRes.Error != nil {
|
||||||
|
return nil, totalRes.Error
|
||||||
|
}
|
||||||
|
|
||||||
|
paginationClone := pagination
|
||||||
|
paginationClone.Total = total
|
||||||
|
|
||||||
|
return &model.VerificationRequests{
|
||||||
|
VerificationRequests: responseVerificationRequests,
|
||||||
|
Pagination: &paginationClone,
|
||||||
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// DeleteVerificationRequest to delete verification request from database
|
// DeleteVerificationRequest to delete verification request from database
|
||||||
|
@@ -6,10 +6,10 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// SendForgotPasswordMail to send forgot password email
|
// SendForgotPasswordMail to send forgot password email
|
||||||
func SendForgotPasswordMail(toEmail, token, host string) error {
|
func SendForgotPasswordMail(toEmail, token, hostname string) error {
|
||||||
resetPasswordUrl := envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyResetPasswordURL)
|
resetPasswordUrl := envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyResetPasswordURL)
|
||||||
if resetPasswordUrl == "" {
|
if resetPasswordUrl == "" {
|
||||||
envstore.EnvInMemoryStoreObj.UpdateEnvVariable(constants.StringStoreIdentifier, constants.EnvKeyResetPasswordURL, envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyAuthorizerURL)+"/app/reset-password")
|
envstore.EnvInMemoryStoreObj.UpdateEnvVariable(constants.StringStoreIdentifier, constants.EnvKeyResetPasswordURL, hostname+"/app/reset-password")
|
||||||
}
|
}
|
||||||
|
|
||||||
// The receiver needs to be in slice as the receive supports multiple receiver
|
// The receiver needs to be in slice as the receive supports multiple receiver
|
||||||
|
@@ -6,7 +6,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// SendVerificationMail to send verification email
|
// SendVerificationMail to send verification email
|
||||||
func SendVerificationMail(toEmail, token string) error {
|
func SendVerificationMail(toEmail, token, hostname string) error {
|
||||||
// The receiver needs to be in slice as the receive supports multiple receiver
|
// The receiver needs to be in slice as the receive supports multiple receiver
|
||||||
Receiver := []string{toEmail}
|
Receiver := []string{toEmail}
|
||||||
|
|
||||||
@@ -99,7 +99,7 @@ func SendVerificationMail(toEmail, token string) error {
|
|||||||
data := make(map[string]interface{}, 3)
|
data := make(map[string]interface{}, 3)
|
||||||
data["org_logo"] = envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyOrganizationLogo)
|
data["org_logo"] = envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyOrganizationLogo)
|
||||||
data["org_name"] = envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyOrganizationName)
|
data["org_name"] = envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyOrganizationName)
|
||||||
data["verification_url"] = envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyAuthorizerURL) + "/verify_email?token=" + token
|
data["verification_url"] = hostname + "/verify_email?token=" + token
|
||||||
message = addEmailTemplate(message, data, "verify_email.tmpl")
|
message = addEmailTemplate(message, data, "verify_email.tmpl")
|
||||||
// bodyMessage := sender.WriteHTMLEmail(Receiver, Subject, message)
|
// bodyMessage := sender.WriteHTMLEmail(Receiver, Subject, message)
|
||||||
|
|
||||||
|
31
server/env/env.go
vendored
@@ -12,16 +12,6 @@ import (
|
|||||||
"github.com/joho/godotenv"
|
"github.com/joho/godotenv"
|
||||||
)
|
)
|
||||||
|
|
||||||
// TODO move this to env store
|
|
||||||
var (
|
|
||||||
// ARG_DB_URL is the cli arg variable for the database url
|
|
||||||
ARG_DB_URL *string
|
|
||||||
// ARG_DB_TYPE is the cli arg variable for the database type
|
|
||||||
ARG_DB_TYPE *string
|
|
||||||
// ARG_ENV_FILE is the cli arg variable for the env file
|
|
||||||
ARG_ENV_FILE *string
|
|
||||||
)
|
|
||||||
|
|
||||||
// InitEnv to initialize EnvData and through error if required env are not present
|
// InitEnv to initialize EnvData and through error if required env are not present
|
||||||
func InitEnv() {
|
func InitEnv() {
|
||||||
// get clone of current store
|
// get clone of current store
|
||||||
@@ -41,8 +31,6 @@ func InitEnv() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// set authorizer url to empty string so that fresh url is obtained with every server start
|
|
||||||
envData.StringEnv[constants.EnvKeyAuthorizerURL] = ""
|
|
||||||
if envData.StringEnv[constants.EnvKeyAppURL] == "" {
|
if envData.StringEnv[constants.EnvKeyAppURL] == "" {
|
||||||
envData.StringEnv[constants.EnvKeyAppURL] = os.Getenv(constants.EnvKeyAppURL)
|
envData.StringEnv[constants.EnvKeyAppURL] = os.Getenv(constants.EnvKeyAppURL)
|
||||||
}
|
}
|
||||||
@@ -51,8 +39,8 @@ func InitEnv() {
|
|||||||
envData.StringEnv[constants.EnvKeyEnvPath] = `.env`
|
envData.StringEnv[constants.EnvKeyEnvPath] = `.env`
|
||||||
}
|
}
|
||||||
|
|
||||||
if ARG_ENV_FILE != nil && *ARG_ENV_FILE != "" {
|
if envstore.ARG_ENV_FILE != nil && *envstore.ARG_ENV_FILE != "" {
|
||||||
envData.StringEnv[constants.EnvKeyEnvPath] = *ARG_ENV_FILE
|
envData.StringEnv[constants.EnvKeyEnvPath] = *envstore.ARG_ENV_FILE
|
||||||
}
|
}
|
||||||
|
|
||||||
err := godotenv.Load(envData.StringEnv[constants.EnvKeyEnvPath])
|
err := godotenv.Load(envData.StringEnv[constants.EnvKeyEnvPath])
|
||||||
@@ -74,8 +62,8 @@ func InitEnv() {
|
|||||||
if envData.StringEnv[constants.EnvKeyDatabaseType] == "" {
|
if envData.StringEnv[constants.EnvKeyDatabaseType] == "" {
|
||||||
envData.StringEnv[constants.EnvKeyDatabaseType] = os.Getenv("DATABASE_TYPE")
|
envData.StringEnv[constants.EnvKeyDatabaseType] = os.Getenv("DATABASE_TYPE")
|
||||||
|
|
||||||
if ARG_DB_TYPE != nil && *ARG_DB_TYPE != "" {
|
if envstore.ARG_DB_TYPE != nil && *envstore.ARG_DB_TYPE != "" {
|
||||||
envData.StringEnv[constants.EnvKeyDatabaseType] = *ARG_DB_TYPE
|
envData.StringEnv[constants.EnvKeyDatabaseType] = *envstore.ARG_DB_TYPE
|
||||||
}
|
}
|
||||||
|
|
||||||
if envData.StringEnv[constants.EnvKeyDatabaseType] == "" {
|
if envData.StringEnv[constants.EnvKeyDatabaseType] == "" {
|
||||||
@@ -86,8 +74,8 @@ func InitEnv() {
|
|||||||
if envData.StringEnv[constants.EnvKeyDatabaseURL] == "" {
|
if envData.StringEnv[constants.EnvKeyDatabaseURL] == "" {
|
||||||
envData.StringEnv[constants.EnvKeyDatabaseURL] = os.Getenv("DATABASE_URL")
|
envData.StringEnv[constants.EnvKeyDatabaseURL] = os.Getenv("DATABASE_URL")
|
||||||
|
|
||||||
if ARG_DB_URL != nil && *ARG_DB_URL != "" {
|
if envstore.ARG_DB_URL != nil && *envstore.ARG_DB_URL != "" {
|
||||||
envData.StringEnv[constants.EnvKeyDatabaseURL] = *ARG_DB_URL
|
envData.StringEnv[constants.EnvKeyDatabaseURL] = *envstore.ARG_DB_URL
|
||||||
}
|
}
|
||||||
|
|
||||||
if envData.StringEnv[constants.EnvKeyDatabaseURL] == "" {
|
if envData.StringEnv[constants.EnvKeyDatabaseURL] == "" {
|
||||||
@@ -256,10 +244,9 @@ func InitEnv() {
|
|||||||
trimVal := strings.TrimSpace(val)
|
trimVal := strings.TrimSpace(val)
|
||||||
if trimVal != "" {
|
if trimVal != "" {
|
||||||
roles = append(roles, trimVal)
|
roles = append(roles, trimVal)
|
||||||
}
|
if utils.StringSliceContains(defaultRoleSplit, trimVal) {
|
||||||
|
defaultRoles = append(defaultRoles, trimVal)
|
||||||
if utils.StringSliceContains(defaultRoleSplit, trimVal) {
|
}
|
||||||
defaultRoles = append(defaultRoles, trimVal)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
21
server/env/persist_env.go
vendored
@@ -25,15 +25,19 @@ func PersistEnv() error {
|
|||||||
envstore.EnvInMemoryStoreObj.UpdateEnvVariable(constants.StringStoreIdentifier, constants.EnvKeyEncryptionKey, hash)
|
envstore.EnvInMemoryStoreObj.UpdateEnvVariable(constants.StringStoreIdentifier, constants.EnvKeyEncryptionKey, hash)
|
||||||
encodedHash := utils.EncryptB64(hash)
|
encodedHash := utils.EncryptB64(hash)
|
||||||
|
|
||||||
configData, err := json.Marshal(envstore.EnvInMemoryStoreObj.GetEnvStoreClone())
|
encryptedConfig, err := utils.EncryptEnvData(envstore.EnvInMemoryStoreObj.GetEnvStoreClone())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
// configData, err := json.Marshal()
|
||||||
|
// if err != nil {
|
||||||
|
// return err
|
||||||
|
// }
|
||||||
|
|
||||||
encryptedConfig, err := utils.EncryptAES(configData)
|
// encryptedConfig, err := utils.EncryptAES(configData)
|
||||||
if err != nil {
|
// if err != nil {
|
||||||
return err
|
// return err
|
||||||
}
|
// }
|
||||||
|
|
||||||
env = models.Env{
|
env = models.Env{
|
||||||
Hash: encodedHash,
|
Hash: encodedHash,
|
||||||
@@ -51,7 +55,12 @@ func PersistEnv() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
envstore.EnvInMemoryStoreObj.UpdateEnvVariable(constants.StringStoreIdentifier, constants.EnvKeyEncryptionKey, decryptedEncryptionKey)
|
envstore.EnvInMemoryStoreObj.UpdateEnvVariable(constants.StringStoreIdentifier, constants.EnvKeyEncryptionKey, decryptedEncryptionKey)
|
||||||
decryptedConfigs, err := utils.DecryptAES(env.EnvData)
|
b64DecryptedConfig, err := utils.DecryptB64(env.EnvData)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
decryptedConfigs, err := utils.DecryptAES([]byte(b64DecryptedConfig))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@@ -6,6 +6,15 @@ import (
|
|||||||
"github.com/authorizerdev/authorizer/server/constants"
|
"github.com/authorizerdev/authorizer/server/constants"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
// ARG_DB_URL is the cli arg variable for the database url
|
||||||
|
ARG_DB_URL *string
|
||||||
|
// ARG_DB_TYPE is the cli arg variable for the database type
|
||||||
|
ARG_DB_TYPE *string
|
||||||
|
// ARG_ENV_FILE is the cli arg variable for the env file
|
||||||
|
ARG_ENV_FILE *string
|
||||||
|
)
|
||||||
|
|
||||||
// Store data structure
|
// Store data structure
|
||||||
type Store struct {
|
type Store struct {
|
||||||
StringEnv map[string]string `json:"string_env"`
|
StringEnv map[string]string `json:"string_env"`
|
||||||
@@ -26,7 +35,7 @@ var EnvInMemoryStoreObj = &EnvInMemoryStore{
|
|||||||
constants.EnvKeyAdminCookieName: "authorizer-admin",
|
constants.EnvKeyAdminCookieName: "authorizer-admin",
|
||||||
constants.EnvKeyJwtRoleClaim: "role",
|
constants.EnvKeyJwtRoleClaim: "role",
|
||||||
constants.EnvKeyOrganizationName: "Authorizer",
|
constants.EnvKeyOrganizationName: "Authorizer",
|
||||||
constants.EnvKeyOrganizationLogo: "https://www.authorizer.io/images/logo.png",
|
constants.EnvKeyOrganizationLogo: "https://www.authorizer.dev/images/logo.png",
|
||||||
},
|
},
|
||||||
BoolEnv: map[string]bool{
|
BoolEnv: map[string]bool{
|
||||||
constants.EnvKeyDisableBasicAuthentication: false,
|
constants.EnvKeyDisableBasicAuthentication: false,
|
||||||
|
@@ -6,7 +6,6 @@ require (
|
|||||||
github.com/99designs/gqlgen v0.14.0
|
github.com/99designs/gqlgen v0.14.0
|
||||||
github.com/arangodb/go-driver v1.2.1
|
github.com/arangodb/go-driver v1.2.1
|
||||||
github.com/coreos/go-oidc/v3 v3.1.0
|
github.com/coreos/go-oidc/v3 v3.1.0
|
||||||
github.com/gin-contrib/location v0.0.2
|
|
||||||
github.com/gin-gonic/gin v1.7.2
|
github.com/gin-gonic/gin v1.7.2
|
||||||
github.com/go-playground/validator/v10 v10.8.0 // indirect
|
github.com/go-playground/validator/v10 v10.8.0 // indirect
|
||||||
github.com/go-redis/redis/v8 v8.11.0
|
github.com/go-redis/redis/v8 v8.11.0
|
||||||
|
@@ -82,11 +82,8 @@ github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7
|
|||||||
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
||||||
github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4=
|
github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4=
|
||||||
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
|
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
|
||||||
github.com/gin-contrib/location v0.0.2 h1:QZKh1+K/LLR4KG/61eIO3b7MLuKi8tytQhV6texLgP4=
|
|
||||||
github.com/gin-contrib/location v0.0.2/go.mod h1:NGoidiRlf0BlA/VKSVp+g3cuSMeTmip/63PhEjRhUAc=
|
|
||||||
github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
|
github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
|
||||||
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
|
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
|
||||||
github.com/gin-gonic/gin v1.6.3/go.mod h1:75u5sXoLsGZoRN5Sgbi1eraJ4GU3++wFwWzhwvtwp4M=
|
|
||||||
github.com/gin-gonic/gin v1.7.2 h1:Tg03T9yM2xa8j6I3Z3oqLaQRSmKvxPd6g/2HJ6zICFA=
|
github.com/gin-gonic/gin v1.7.2 h1:Tg03T9yM2xa8j6I3Z3oqLaQRSmKvxPd6g/2HJ6zICFA=
|
||||||
github.com/gin-gonic/gin v1.7.2/go.mod h1:jD2toBW3GZUr5UMcdrwQA10I7RuaFOl/SGeDjXkfUtY=
|
github.com/gin-gonic/gin v1.7.2/go.mod h1:jD2toBW3GZUr5UMcdrwQA10I7RuaFOl/SGeDjXkfUtY=
|
||||||
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
|
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
|
||||||
@@ -100,7 +97,6 @@ github.com/go-playground/locales v0.13.0 h1:HyWk6mgj5qFqCT5fjGBuRArbVDfE4hi8+e8c
|
|||||||
github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8=
|
github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8=
|
||||||
github.com/go-playground/universal-translator v0.17.0 h1:icxd5fm+REJzpZx7ZfpaD876Lmtgy7VtROAbHHXk8no=
|
github.com/go-playground/universal-translator v0.17.0 h1:icxd5fm+REJzpZx7ZfpaD876Lmtgy7VtROAbHHXk8no=
|
||||||
github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA=
|
github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA=
|
||||||
github.com/go-playground/validator/v10 v10.2.0/go.mod h1:uOYAAleCW8F/7oMFd6aG0GOhaH6EGOAJShg8Id5JGkI=
|
|
||||||
github.com/go-playground/validator/v10 v10.4.1/go.mod h1:nlOn6nFhuKACm19sB/8EGNn9GlaMV7XkbRSipzJ0Ii4=
|
github.com/go-playground/validator/v10 v10.4.1/go.mod h1:nlOn6nFhuKACm19sB/8EGNn9GlaMV7XkbRSipzJ0Ii4=
|
||||||
github.com/go-playground/validator/v10 v10.8.0 h1:1kAa0fCrnpv+QYdkdcRzrRM7AyYs5o8+jZdJCz9xj6k=
|
github.com/go-playground/validator/v10 v10.8.0 h1:1kAa0fCrnpv+QYdkdcRzrRM7AyYs5o8+jZdJCz9xj6k=
|
||||||
github.com/go-playground/validator/v10 v10.8.0/go.mod h1:9JhgTzTaE31GZDpH/HSvHiRJrJ3iKAgqqH0Bl/Ocjdk=
|
github.com/go-playground/validator/v10 v10.8.0/go.mod h1:9JhgTzTaE31GZDpH/HSvHiRJrJ3iKAgqqH0Bl/Ocjdk=
|
||||||
|
@@ -23,9 +23,10 @@ type DeleteUserInput struct {
|
|||||||
|
|
||||||
type Env struct {
|
type Env struct {
|
||||||
AdminSecret *string `json:"ADMIN_SECRET"`
|
AdminSecret *string `json:"ADMIN_SECRET"`
|
||||||
DatabaseType *string `json:"DATABASE_TYPE"`
|
|
||||||
DatabaseURL *string `json:"DATABASE_URL"`
|
|
||||||
DatabaseName *string `json:"DATABASE_NAME"`
|
DatabaseName *string `json:"DATABASE_NAME"`
|
||||||
|
DatabaseURL *string `json:"DATABASE_URL"`
|
||||||
|
DatabaseType *string `json:"DATABASE_TYPE"`
|
||||||
|
CustomAccessTokenScript *string `json:"CUSTOM_ACCESS_TOKEN_SCRIPT"`
|
||||||
SMTPHost *string `json:"SMTP_HOST"`
|
SMTPHost *string `json:"SMTP_HOST"`
|
||||||
SMTPPort *string `json:"SMTP_PORT"`
|
SMTPPort *string `json:"SMTP_PORT"`
|
||||||
SMTPUsername *string `json:"SMTP_USERNAME"`
|
SMTPUsername *string `json:"SMTP_USERNAME"`
|
||||||
@@ -34,7 +35,6 @@ type Env struct {
|
|||||||
JwtType *string `json:"JWT_TYPE"`
|
JwtType *string `json:"JWT_TYPE"`
|
||||||
JwtSecret *string `json:"JWT_SECRET"`
|
JwtSecret *string `json:"JWT_SECRET"`
|
||||||
AllowedOrigins []string `json:"ALLOWED_ORIGINS"`
|
AllowedOrigins []string `json:"ALLOWED_ORIGINS"`
|
||||||
AuthorizerURL *string `json:"AUTHORIZER_URL"`
|
|
||||||
AppURL *string `json:"APP_URL"`
|
AppURL *string `json:"APP_URL"`
|
||||||
RedisURL *string `json:"REDIS_URL"`
|
RedisURL *string `json:"REDIS_URL"`
|
||||||
CookieName *string `json:"COOKIE_NAME"`
|
CookieName *string `json:"COOKIE_NAME"`
|
||||||
@@ -66,6 +66,11 @@ type ForgotPasswordInput struct {
|
|||||||
Email string `json:"email"`
|
Email string `json:"email"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type IsValidJWTQueryInput struct {
|
||||||
|
Jwt *string `json:"jwt"`
|
||||||
|
Roles []string `json:"roles"`
|
||||||
|
}
|
||||||
|
|
||||||
type LoginInput struct {
|
type LoginInput struct {
|
||||||
Email string `json:"email"`
|
Email string `json:"email"`
|
||||||
Password string `json:"password"`
|
Password string `json:"password"`
|
||||||
@@ -87,6 +92,22 @@ type Meta struct {
|
|||||||
IsMagicLinkLoginEnabled bool `json:"is_magic_link_login_enabled"`
|
IsMagicLinkLoginEnabled bool `json:"is_magic_link_login_enabled"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type PaginatedInput struct {
|
||||||
|
Pagination *PaginationInput `json:"pagination"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Pagination struct {
|
||||||
|
Limit int64 `json:"limit"`
|
||||||
|
Page int64 `json:"page"`
|
||||||
|
Offset int64 `json:"offset"`
|
||||||
|
Total int64 `json:"total"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type PaginationInput struct {
|
||||||
|
Limit *int64 `json:"limit"`
|
||||||
|
Page *int64 `json:"page"`
|
||||||
|
}
|
||||||
|
|
||||||
type ResendVerifyEmailInput struct {
|
type ResendVerifyEmailInput struct {
|
||||||
Email string `json:"email"`
|
Email string `json:"email"`
|
||||||
Identifier string `json:"identifier"`
|
Identifier string `json:"identifier"`
|
||||||
@@ -102,6 +123,10 @@ type Response struct {
|
|||||||
Message string `json:"message"`
|
Message string `json:"message"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type SessionQueryInput struct {
|
||||||
|
Roles []string `json:"roles"`
|
||||||
|
}
|
||||||
|
|
||||||
type SignUpInput struct {
|
type SignUpInput struct {
|
||||||
Email string `json:"email"`
|
Email string `json:"email"`
|
||||||
GivenName *string `json:"given_name"`
|
GivenName *string `json:"given_name"`
|
||||||
@@ -119,18 +144,16 @@ type SignUpInput struct {
|
|||||||
|
|
||||||
type UpdateEnvInput struct {
|
type UpdateEnvInput struct {
|
||||||
AdminSecret *string `json:"ADMIN_SECRET"`
|
AdminSecret *string `json:"ADMIN_SECRET"`
|
||||||
|
CustomAccessTokenScript *string `json:"CUSTOM_ACCESS_TOKEN_SCRIPT"`
|
||||||
OldAdminSecret *string `json:"OLD_ADMIN_SECRET"`
|
OldAdminSecret *string `json:"OLD_ADMIN_SECRET"`
|
||||||
DatabaseType *string `json:"DATABASE_TYPE"`
|
|
||||||
DatabaseURL *string `json:"DATABASE_URL"`
|
|
||||||
DatabaseName *string `json:"DATABASE_NAME"`
|
|
||||||
SMTPHost *string `json:"SMTP_HOST"`
|
SMTPHost *string `json:"SMTP_HOST"`
|
||||||
SMTPPort *string `json:"SMTP_PORT"`
|
SMTPPort *string `json:"SMTP_PORT"`
|
||||||
|
SMTPUsername *string `json:"SMTP_USERNAME"`
|
||||||
|
SMTPPassword *string `json:"SMTP_PASSWORD"`
|
||||||
SenderEmail *string `json:"SENDER_EMAIL"`
|
SenderEmail *string `json:"SENDER_EMAIL"`
|
||||||
SenderPassword *string `json:"SENDER_PASSWORD"`
|
|
||||||
JwtType *string `json:"JWT_TYPE"`
|
JwtType *string `json:"JWT_TYPE"`
|
||||||
JwtSecret *string `json:"JWT_SECRET"`
|
JwtSecret *string `json:"JWT_SECRET"`
|
||||||
AllowedOrigins []string `json:"ALLOWED_ORIGINS"`
|
AllowedOrigins []string `json:"ALLOWED_ORIGINS"`
|
||||||
AuthorizerURL *string `json:"AUTHORIZER_URL"`
|
|
||||||
AppURL *string `json:"APP_URL"`
|
AppURL *string `json:"APP_URL"`
|
||||||
RedisURL *string `json:"REDIS_URL"`
|
RedisURL *string `json:"REDIS_URL"`
|
||||||
CookieName *string `json:"COOKIE_NAME"`
|
CookieName *string `json:"COOKIE_NAME"`
|
||||||
@@ -203,6 +226,16 @@ type User struct {
|
|||||||
UpdatedAt *int64 `json:"updated_at"`
|
UpdatedAt *int64 `json:"updated_at"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type Users struct {
|
||||||
|
Pagination *Pagination `json:"pagination"`
|
||||||
|
Users []*User `json:"users"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ValidJWTResponse struct {
|
||||||
|
Valid bool `json:"valid"`
|
||||||
|
Message string `json:"message"`
|
||||||
|
}
|
||||||
|
|
||||||
type VerificationRequest struct {
|
type VerificationRequest struct {
|
||||||
ID string `json:"id"`
|
ID string `json:"id"`
|
||||||
Identifier *string `json:"identifier"`
|
Identifier *string `json:"identifier"`
|
||||||
@@ -213,6 +246,11 @@ type VerificationRequest struct {
|
|||||||
UpdatedAt *int64 `json:"updated_at"`
|
UpdatedAt *int64 `json:"updated_at"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type VerificationRequests struct {
|
||||||
|
Pagination *Pagination `json:"pagination"`
|
||||||
|
VerificationRequests []*VerificationRequest `json:"verification_requests"`
|
||||||
|
}
|
||||||
|
|
||||||
type VerifyEmailInput struct {
|
type VerifyEmailInput struct {
|
||||||
Token string `json:"token"`
|
Token string `json:"token"`
|
||||||
}
|
}
|
||||||
|
@@ -5,6 +5,13 @@ scalar Int64
|
|||||||
scalar Map
|
scalar Map
|
||||||
scalar Any
|
scalar Any
|
||||||
|
|
||||||
|
type Pagination {
|
||||||
|
limit: Int64!
|
||||||
|
page: Int64!
|
||||||
|
offset: Int64!
|
||||||
|
total: Int64!
|
||||||
|
}
|
||||||
|
|
||||||
type Meta {
|
type Meta {
|
||||||
version: String!
|
version: String!
|
||||||
is_google_login_enabled: Boolean!
|
is_google_login_enabled: Boolean!
|
||||||
@@ -36,6 +43,11 @@ type User {
|
|||||||
updated_at: Int64
|
updated_at: Int64
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type Users {
|
||||||
|
pagination: Pagination!
|
||||||
|
users: [User!]!
|
||||||
|
}
|
||||||
|
|
||||||
type VerificationRequest {
|
type VerificationRequest {
|
||||||
id: ID!
|
id: ID!
|
||||||
identifier: String
|
identifier: String
|
||||||
@@ -46,6 +58,11 @@ type VerificationRequest {
|
|||||||
updated_at: Int64
|
updated_at: Int64
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type VerificationRequests {
|
||||||
|
pagination: Pagination!
|
||||||
|
verification_requests: [VerificationRequest!]!
|
||||||
|
}
|
||||||
|
|
||||||
type Error {
|
type Error {
|
||||||
message: String!
|
message: String!
|
||||||
reason: String!
|
reason: String!
|
||||||
@@ -62,11 +79,17 @@ type Response {
|
|||||||
message: String!
|
message: String!
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type ValidJWTResponse {
|
||||||
|
valid: Boolean!
|
||||||
|
message: String!
|
||||||
|
}
|
||||||
|
|
||||||
type Env {
|
type Env {
|
||||||
ADMIN_SECRET: String
|
ADMIN_SECRET: String
|
||||||
DATABASE_TYPE: String
|
|
||||||
DATABASE_URL: String
|
|
||||||
DATABASE_NAME: String
|
DATABASE_NAME: String
|
||||||
|
DATABASE_URL: String
|
||||||
|
DATABASE_TYPE: String
|
||||||
|
CUSTOM_ACCESS_TOKEN_SCRIPT: String
|
||||||
SMTP_HOST: String
|
SMTP_HOST: String
|
||||||
SMTP_PORT: String
|
SMTP_PORT: String
|
||||||
SMTP_USERNAME: String
|
SMTP_USERNAME: String
|
||||||
@@ -75,7 +98,6 @@ type Env {
|
|||||||
JWT_TYPE: String
|
JWT_TYPE: String
|
||||||
JWT_SECRET: String
|
JWT_SECRET: String
|
||||||
ALLOWED_ORIGINS: [String!]
|
ALLOWED_ORIGINS: [String!]
|
||||||
AUTHORIZER_URL: String
|
|
||||||
APP_URL: String
|
APP_URL: String
|
||||||
REDIS_URL: String
|
REDIS_URL: String
|
||||||
COOKIE_NAME: String
|
COOKIE_NAME: String
|
||||||
@@ -100,18 +122,16 @@ type Env {
|
|||||||
|
|
||||||
input UpdateEnvInput {
|
input UpdateEnvInput {
|
||||||
ADMIN_SECRET: String
|
ADMIN_SECRET: String
|
||||||
|
CUSTOM_ACCESS_TOKEN_SCRIPT: String
|
||||||
OLD_ADMIN_SECRET: String
|
OLD_ADMIN_SECRET: String
|
||||||
DATABASE_TYPE: String
|
|
||||||
DATABASE_URL: String
|
|
||||||
DATABASE_NAME: String
|
|
||||||
SMTP_HOST: String
|
SMTP_HOST: String
|
||||||
SMTP_PORT: String
|
SMTP_PORT: String
|
||||||
|
SMTP_USERNAME: String
|
||||||
|
SMTP_PASSWORD: String
|
||||||
SENDER_EMAIL: String
|
SENDER_EMAIL: String
|
||||||
SENDER_PASSWORD: String
|
|
||||||
JWT_TYPE: String
|
JWT_TYPE: String
|
||||||
JWT_SECRET: String
|
JWT_SECRET: String
|
||||||
ALLOWED_ORIGINS: [String!]
|
ALLOWED_ORIGINS: [String!]
|
||||||
AUTHORIZER_URL: String
|
|
||||||
APP_URL: String
|
APP_URL: String
|
||||||
REDIS_URL: String
|
REDIS_URL: String
|
||||||
COOKIE_NAME: String
|
COOKIE_NAME: String
|
||||||
@@ -221,6 +241,24 @@ input MagicLinkLoginInput {
|
|||||||
roles: [String!]
|
roles: [String!]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
input SessionQueryInput {
|
||||||
|
roles: [String!]
|
||||||
|
}
|
||||||
|
|
||||||
|
input IsValidJWTQueryInput {
|
||||||
|
jwt: String
|
||||||
|
roles: [String!]
|
||||||
|
}
|
||||||
|
|
||||||
|
input PaginationInput {
|
||||||
|
limit: Int64
|
||||||
|
page: Int64
|
||||||
|
}
|
||||||
|
|
||||||
|
input PaginatedInput {
|
||||||
|
pagination: PaginationInput
|
||||||
|
}
|
||||||
|
|
||||||
type Mutation {
|
type Mutation {
|
||||||
signup(params: SignUpInput!): AuthResponse!
|
signup(params: SignUpInput!): AuthResponse!
|
||||||
login(params: LoginInput!): AuthResponse!
|
login(params: LoginInput!): AuthResponse!
|
||||||
@@ -242,11 +280,12 @@ type Mutation {
|
|||||||
|
|
||||||
type Query {
|
type Query {
|
||||||
meta: Meta!
|
meta: Meta!
|
||||||
session(roles: [String!]): AuthResponse!
|
session(params: SessionQueryInput): AuthResponse!
|
||||||
|
is_valid_jwt(params: IsValidJWTQueryInput): ValidJWTResponse!
|
||||||
profile: User!
|
profile: User!
|
||||||
# admin only apis
|
# admin only apis
|
||||||
_users: [User!]!
|
_users(params: PaginatedInput): Users!
|
||||||
_verification_requests: [VerificationRequest!]!
|
_verification_requests(params: PaginatedInput): VerificationRequests!
|
||||||
_admin_session: Response!
|
_admin_session: Response!
|
||||||
_env: Env!
|
_env: Env!
|
||||||
}
|
}
|
||||||
|
@@ -75,20 +75,24 @@ func (r *queryResolver) Meta(ctx context.Context) (*model.Meta, error) {
|
|||||||
return resolvers.MetaResolver(ctx)
|
return resolvers.MetaResolver(ctx)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *queryResolver) Session(ctx context.Context, roles []string) (*model.AuthResponse, error) {
|
func (r *queryResolver) Session(ctx context.Context, params *model.SessionQueryInput) (*model.AuthResponse, error) {
|
||||||
return resolvers.SessionResolver(ctx, roles)
|
return resolvers.SessionResolver(ctx, params)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *queryResolver) IsValidJwt(ctx context.Context, params *model.IsValidJWTQueryInput) (*model.ValidJWTResponse, error) {
|
||||||
|
return resolvers.IsValidJwtResolver(ctx, params)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *queryResolver) Profile(ctx context.Context) (*model.User, error) {
|
func (r *queryResolver) Profile(ctx context.Context) (*model.User, error) {
|
||||||
return resolvers.ProfileResolver(ctx)
|
return resolvers.ProfileResolver(ctx)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *queryResolver) Users(ctx context.Context) ([]*model.User, error) {
|
func (r *queryResolver) Users(ctx context.Context, params *model.PaginatedInput) (*model.Users, error) {
|
||||||
return resolvers.UsersResolver(ctx)
|
return resolvers.UsersResolver(ctx, params)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *queryResolver) VerificationRequests(ctx context.Context) ([]*model.VerificationRequest, error) {
|
func (r *queryResolver) VerificationRequests(ctx context.Context, params *model.PaginatedInput) (*model.VerificationRequests, error) {
|
||||||
return resolvers.VerificationRequestsResolver(ctx)
|
return resolvers.VerificationRequestsResolver(ctx, params)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *queryResolver) AdminSession(ctx context.Context) (*model.Response, error) {
|
func (r *queryResolver) AdminSession(ctx context.Context) (*model.Response, error) {
|
||||||
|
@@ -22,14 +22,19 @@ type State struct {
|
|||||||
// AppHandler is the handler for the /app route
|
// AppHandler is the handler for the /app route
|
||||||
func AppHandler() gin.HandlerFunc {
|
func AppHandler() gin.HandlerFunc {
|
||||||
return func(c *gin.Context) {
|
return func(c *gin.Context) {
|
||||||
|
hostname := utils.GetHost(c)
|
||||||
|
if envstore.EnvInMemoryStoreObj.GetBoolStoreEnvVariable(constants.EnvKeyDisableLoginPage) {
|
||||||
|
c.JSON(400, gin.H{"error": "login page is not enabled"})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
state := c.Query("state")
|
state := c.Query("state")
|
||||||
|
|
||||||
var stateObj State
|
var stateObj State
|
||||||
|
|
||||||
if state == "" {
|
if state == "" {
|
||||||
stateObj.AuthorizerURL = envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyAuthorizerURL)
|
stateObj.AuthorizerURL = hostname
|
||||||
stateObj.RedirectURL = stateObj.AuthorizerURL + "/app"
|
stateObj.RedirectURL = hostname + "/app"
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
decodedState, err := utils.DecryptB64(state)
|
decodedState, err := utils.DecryptB64(state)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -57,7 +62,7 @@ func AppHandler() gin.HandlerFunc {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// validate host and domain of authorizer url
|
// validate host and domain of authorizer url
|
||||||
if strings.TrimSuffix(stateObj.AuthorizerURL, "/") != envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyAuthorizerURL) {
|
if strings.TrimSuffix(stateObj.AuthorizerURL, "/") != hostname {
|
||||||
c.JSON(400, gin.H{"error": "invalid host url"})
|
c.JSON(400, gin.H{"error": "invalid host url"})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@@ -11,11 +11,13 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/authorizerdev/authorizer/server/constants"
|
"github.com/authorizerdev/authorizer/server/constants"
|
||||||
|
"github.com/authorizerdev/authorizer/server/cookie"
|
||||||
"github.com/authorizerdev/authorizer/server/db"
|
"github.com/authorizerdev/authorizer/server/db"
|
||||||
"github.com/authorizerdev/authorizer/server/db/models"
|
"github.com/authorizerdev/authorizer/server/db/models"
|
||||||
"github.com/authorizerdev/authorizer/server/envstore"
|
"github.com/authorizerdev/authorizer/server/envstore"
|
||||||
"github.com/authorizerdev/authorizer/server/oauth"
|
"github.com/authorizerdev/authorizer/server/oauth"
|
||||||
"github.com/authorizerdev/authorizer/server/session"
|
"github.com/authorizerdev/authorizer/server/sessionstore"
|
||||||
|
"github.com/authorizerdev/authorizer/server/token"
|
||||||
"github.com/authorizerdev/authorizer/server/utils"
|
"github.com/authorizerdev/authorizer/server/utils"
|
||||||
"github.com/coreos/go-oidc/v3/oidc"
|
"github.com/coreos/go-oidc/v3/oidc"
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
@@ -28,11 +30,11 @@ func OAuthCallbackHandler() gin.HandlerFunc {
|
|||||||
provider := c.Param("oauth_provider")
|
provider := c.Param("oauth_provider")
|
||||||
state := c.Request.FormValue("state")
|
state := c.Request.FormValue("state")
|
||||||
|
|
||||||
sessionState := session.GetSocailLoginState(state)
|
sessionState := sessionstore.GetSocailLoginState(state)
|
||||||
if sessionState == "" {
|
if sessionState == "" {
|
||||||
c.JSON(400, gin.H{"error": "invalid oauth state"})
|
c.JSON(400, gin.H{"error": "invalid oauth state"})
|
||||||
}
|
}
|
||||||
session.RemoveSocialLoginState(state)
|
sessionstore.RemoveSocialLoginState(state)
|
||||||
// contains random token, redirect url, role
|
// contains random token, redirect url, role
|
||||||
sessionSplit := strings.Split(state, "___")
|
sessionSplit := strings.Split(state, "___")
|
||||||
|
|
||||||
@@ -94,8 +96,13 @@ func OAuthCallbackHandler() gin.HandlerFunc {
|
|||||||
if !strings.Contains(signupMethod, provider) {
|
if !strings.Contains(signupMethod, provider) {
|
||||||
signupMethod = signupMethod + "," + provider
|
signupMethod = signupMethod + "," + provider
|
||||||
}
|
}
|
||||||
|
user = existingUser
|
||||||
user.SignupMethods = signupMethod
|
user.SignupMethods = signupMethod
|
||||||
user.Password = existingUser.Password
|
|
||||||
|
if user.EmailVerifiedAt == nil {
|
||||||
|
now := time.Now().Unix()
|
||||||
|
user.EmailVerifiedAt = &now
|
||||||
|
}
|
||||||
|
|
||||||
// There multiple scenarios with roles here in social login
|
// There multiple scenarios with roles here in social login
|
||||||
// 1. user has access to protected roles + roles and trying to login
|
// 1. user has access to protected roles + roles and trying to login
|
||||||
@@ -129,18 +136,17 @@ func OAuthCallbackHandler() gin.HandlerFunc {
|
|||||||
} else {
|
} else {
|
||||||
user.Roles = existingUser.Roles
|
user.Roles = existingUser.Roles
|
||||||
}
|
}
|
||||||
user.Key = existingUser.Key
|
|
||||||
user.ID = existingUser.ID
|
|
||||||
user, err = db.Provider.UpdateUser(user)
|
user, err = db.Provider.UpdateUser(user)
|
||||||
|
if err != nil {
|
||||||
|
c.JSON(500, gin.H{"error": err.Error()})
|
||||||
|
return
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
user, _ = db.Provider.GetUserByEmail(user.Email)
|
authToken, _ := token.CreateAuthToken(user, inputRoles)
|
||||||
userIdStr := fmt.Sprintf("%v", user.ID)
|
sessionstore.SetUserSession(user.ID, authToken.FingerPrint, authToken.RefreshToken.Token)
|
||||||
refreshToken, _, _ := utils.CreateAuthToken(user, constants.TokenTypeRefreshToken, inputRoles)
|
cookie.SetCookie(c, authToken.AccessToken.Token, authToken.RefreshToken.Token, authToken.FingerPrintHash)
|
||||||
|
|
||||||
accessToken, _, _ := utils.CreateAuthToken(user, constants.TokenTypeAccessToken, inputRoles)
|
|
||||||
utils.SetCookie(c, accessToken)
|
|
||||||
session.SetUserSession(userIdStr, accessToken, refreshToken)
|
|
||||||
utils.SaveSessionInDB(user.ID, c)
|
utils.SaveSessionInDB(user.ID, c)
|
||||||
|
|
||||||
c.Redirect(http.StatusTemporaryRedirect, redirectURL)
|
c.Redirect(http.StatusTemporaryRedirect, redirectURL)
|
||||||
|
@@ -7,7 +7,7 @@ import (
|
|||||||
"github.com/authorizerdev/authorizer/server/constants"
|
"github.com/authorizerdev/authorizer/server/constants"
|
||||||
"github.com/authorizerdev/authorizer/server/envstore"
|
"github.com/authorizerdev/authorizer/server/envstore"
|
||||||
"github.com/authorizerdev/authorizer/server/oauth"
|
"github.com/authorizerdev/authorizer/server/oauth"
|
||||||
"github.com/authorizerdev/authorizer/server/session"
|
"github.com/authorizerdev/authorizer/server/sessionstore"
|
||||||
"github.com/authorizerdev/authorizer/server/utils"
|
"github.com/authorizerdev/authorizer/server/utils"
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
@@ -16,7 +16,7 @@ import (
|
|||||||
// OAuthLoginHandler set host in the oauth state that is useful for redirecting to oauth_callback
|
// OAuthLoginHandler set host in the oauth state that is useful for redirecting to oauth_callback
|
||||||
func OAuthLoginHandler() gin.HandlerFunc {
|
func OAuthLoginHandler() gin.HandlerFunc {
|
||||||
return func(c *gin.Context) {
|
return func(c *gin.Context) {
|
||||||
// TODO validate redirect URL
|
hostname := utils.GetHost(c)
|
||||||
redirectURL := c.Query("redirectURL")
|
redirectURL := c.Query("redirectURL")
|
||||||
roles := c.Query("roles")
|
roles := c.Query("roles")
|
||||||
|
|
||||||
@@ -54,9 +54,9 @@ func OAuthLoginHandler() gin.HandlerFunc {
|
|||||||
isProviderConfigured = false
|
isProviderConfigured = false
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
session.SetSocailLoginState(oauthStateString, constants.SignupMethodGoogle)
|
sessionstore.SetSocailLoginState(oauthStateString, constants.SignupMethodGoogle)
|
||||||
// during the init of OAuthProvider authorizer url might be empty
|
// during the init of OAuthProvider authorizer url might be empty
|
||||||
oauth.OAuthProviders.GoogleConfig.RedirectURL = envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyAuthorizerURL) + "/oauth_callback/google"
|
oauth.OAuthProviders.GoogleConfig.RedirectURL = hostname + "/oauth_callback/google"
|
||||||
url := oauth.OAuthProviders.GoogleConfig.AuthCodeURL(oauthStateString)
|
url := oauth.OAuthProviders.GoogleConfig.AuthCodeURL(oauthStateString)
|
||||||
c.Redirect(http.StatusTemporaryRedirect, url)
|
c.Redirect(http.StatusTemporaryRedirect, url)
|
||||||
case constants.SignupMethodGithub:
|
case constants.SignupMethodGithub:
|
||||||
@@ -64,8 +64,8 @@ func OAuthLoginHandler() gin.HandlerFunc {
|
|||||||
isProviderConfigured = false
|
isProviderConfigured = false
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
session.SetSocailLoginState(oauthStateString, constants.SignupMethodGithub)
|
sessionstore.SetSocailLoginState(oauthStateString, constants.SignupMethodGithub)
|
||||||
oauth.OAuthProviders.GithubConfig.RedirectURL = envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyAuthorizerURL) + "/oauth_callback/github"
|
oauth.OAuthProviders.GithubConfig.RedirectURL = hostname + "/oauth_callback/github"
|
||||||
url := oauth.OAuthProviders.GithubConfig.AuthCodeURL(oauthStateString)
|
url := oauth.OAuthProviders.GithubConfig.AuthCodeURL(oauthStateString)
|
||||||
c.Redirect(http.StatusTemporaryRedirect, url)
|
c.Redirect(http.StatusTemporaryRedirect, url)
|
||||||
case constants.SignupMethodFacebook:
|
case constants.SignupMethodFacebook:
|
||||||
@@ -73,8 +73,8 @@ func OAuthLoginHandler() gin.HandlerFunc {
|
|||||||
isProviderConfigured = false
|
isProviderConfigured = false
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
session.SetSocailLoginState(oauthStateString, constants.SignupMethodFacebook)
|
sessionstore.SetSocailLoginState(oauthStateString, constants.SignupMethodFacebook)
|
||||||
oauth.OAuthProviders.FacebookConfig.RedirectURL = envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyAuthorizerURL) + "/oauth_callback/facebook"
|
oauth.OAuthProviders.FacebookConfig.RedirectURL = hostname + "/oauth_callback/facebook"
|
||||||
url := oauth.OAuthProviders.FacebookConfig.AuthCodeURL(oauthStateString)
|
url := oauth.OAuthProviders.FacebookConfig.AuthCodeURL(oauthStateString)
|
||||||
c.Redirect(http.StatusTemporaryRedirect, url)
|
c.Redirect(http.StatusTemporaryRedirect, url)
|
||||||
default:
|
default:
|
||||||
|
@@ -5,9 +5,10 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/authorizerdev/authorizer/server/constants"
|
"github.com/authorizerdev/authorizer/server/cookie"
|
||||||
"github.com/authorizerdev/authorizer/server/db"
|
"github.com/authorizerdev/authorizer/server/db"
|
||||||
"github.com/authorizerdev/authorizer/server/session"
|
"github.com/authorizerdev/authorizer/server/sessionstore"
|
||||||
|
"github.com/authorizerdev/authorizer/server/token"
|
||||||
"github.com/authorizerdev/authorizer/server/utils"
|
"github.com/authorizerdev/authorizer/server/utils"
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
)
|
)
|
||||||
@@ -19,20 +20,20 @@ func VerifyEmailHandler() gin.HandlerFunc {
|
|||||||
errorRes := gin.H{
|
errorRes := gin.H{
|
||||||
"message": "invalid token",
|
"message": "invalid token",
|
||||||
}
|
}
|
||||||
token := c.Query("token")
|
tokenInQuery := c.Query("token")
|
||||||
if token == "" {
|
if tokenInQuery == "" {
|
||||||
c.JSON(400, errorRes)
|
c.JSON(400, errorRes)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
verificationRequest, err := db.Provider.GetVerificationRequestByToken(token)
|
verificationRequest, err := db.Provider.GetVerificationRequestByToken(tokenInQuery)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.JSON(400, errorRes)
|
c.JSON(400, errorRes)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// verify if token exists in db
|
// verify if token exists in db
|
||||||
claim, err := utils.VerifyVerificationToken(token)
|
claim, err := token.VerifyVerificationToken(tokenInQuery)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.JSON(400, errorRes)
|
c.JSON(400, errorRes)
|
||||||
return
|
return
|
||||||
@@ -56,13 +57,17 @@ func VerifyEmailHandler() gin.HandlerFunc {
|
|||||||
db.Provider.DeleteVerificationRequest(verificationRequest)
|
db.Provider.DeleteVerificationRequest(verificationRequest)
|
||||||
|
|
||||||
roles := strings.Split(user.Roles, ",")
|
roles := strings.Split(user.Roles, ",")
|
||||||
refreshToken, _, _ := utils.CreateAuthToken(user, constants.TokenTypeRefreshToken, roles)
|
authToken, err := token.CreateAuthToken(user, roles)
|
||||||
|
if err != nil {
|
||||||
accessToken, _, _ := utils.CreateAuthToken(user, constants.TokenTypeAccessToken, roles)
|
c.JSON(400, gin.H{
|
||||||
|
"message": err.Error(),
|
||||||
session.SetUserSession(user.ID, accessToken, refreshToken)
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
sessionstore.SetUserSession(user.ID, authToken.FingerPrint, authToken.RefreshToken.Token)
|
||||||
|
cookie.SetCookie(c, authToken.AccessToken.Token, authToken.RefreshToken.Token, authToken.FingerPrintHash)
|
||||||
utils.SaveSessionInDB(user.ID, c)
|
utils.SaveSessionInDB(user.ID, c)
|
||||||
utils.SetCookie(c, accessToken)
|
|
||||||
c.Redirect(http.StatusTemporaryRedirect, claim.RedirectURL)
|
c.Redirect(http.StatusTemporaryRedirect, claim.RedirectURL)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -9,15 +9,15 @@ import (
|
|||||||
"github.com/authorizerdev/authorizer/server/envstore"
|
"github.com/authorizerdev/authorizer/server/envstore"
|
||||||
"github.com/authorizerdev/authorizer/server/oauth"
|
"github.com/authorizerdev/authorizer/server/oauth"
|
||||||
"github.com/authorizerdev/authorizer/server/routes"
|
"github.com/authorizerdev/authorizer/server/routes"
|
||||||
"github.com/authorizerdev/authorizer/server/session"
|
"github.com/authorizerdev/authorizer/server/sessionstore"
|
||||||
)
|
)
|
||||||
|
|
||||||
var VERSION string
|
var VERSION string
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
env.ARG_DB_URL = flag.String("database_url", "", "Database connection string")
|
envstore.ARG_DB_URL = flag.String("database_url", "", "Database connection string")
|
||||||
env.ARG_DB_TYPE = flag.String("database_type", "", "Database type, possible values are postgres,mysql,sqlite")
|
envstore.ARG_DB_TYPE = flag.String("database_type", "", "Database type, possible values are postgres,mysql,sqlite")
|
||||||
env.ARG_ENV_FILE = flag.String("env_file", "", "Env file path")
|
envstore.ARG_ENV_FILE = flag.String("env_file", "", "Env file path")
|
||||||
flag.Parse()
|
flag.Parse()
|
||||||
|
|
||||||
envstore.EnvInMemoryStoreObj.UpdateEnvVariable(constants.StringStoreIdentifier, constants.EnvKeyVersion, VERSION)
|
envstore.EnvInMemoryStoreObj.UpdateEnvVariable(constants.StringStoreIdentifier, constants.EnvKeyVersion, VERSION)
|
||||||
@@ -26,7 +26,7 @@ func main() {
|
|||||||
db.InitDB()
|
db.InitDB()
|
||||||
env.PersistEnv()
|
env.PersistEnv()
|
||||||
|
|
||||||
session.InitSession()
|
sessionstore.InitSession()
|
||||||
oauth.InitOAuth()
|
oauth.InitOAuth()
|
||||||
|
|
||||||
router := routes.InitRouter()
|
router := routes.InitRouter()
|
||||||
|
@@ -3,19 +3,12 @@ package middlewares
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
|
||||||
"github.com/authorizerdev/authorizer/server/constants"
|
|
||||||
"github.com/authorizerdev/authorizer/server/envstore"
|
|
||||||
"github.com/gin-contrib/location"
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
)
|
)
|
||||||
|
|
||||||
// GinContextToContextMiddleware is a middleware to add gin context in context
|
// GinContextToContextMiddleware is a middleware to add gin context in context
|
||||||
func GinContextToContextMiddleware() gin.HandlerFunc {
|
func GinContextToContextMiddleware() gin.HandlerFunc {
|
||||||
return func(c *gin.Context) {
|
return func(c *gin.Context) {
|
||||||
if envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyAuthorizerURL) == "" {
|
|
||||||
url := location.Get(c)
|
|
||||||
envstore.EnvInMemoryStoreObj.UpdateEnvVariable(constants.StringStoreIdentifier, constants.EnvKeyAuthorizerURL, url.Scheme+"://"+c.Request.Host)
|
|
||||||
}
|
|
||||||
ctx := context.WithValue(c.Request.Context(), "GinContextKey", c)
|
ctx := context.WithValue(c.Request.Context(), "GinContextKey", c)
|
||||||
c.Request = c.Request.WithContext(ctx)
|
c.Request = c.Request.WithContext(ctx)
|
||||||
c.Next()
|
c.Next()
|
||||||
|
@@ -43,7 +43,7 @@ func InitOAuth() {
|
|||||||
OAuthProviders.GoogleConfig = &oauth2.Config{
|
OAuthProviders.GoogleConfig = &oauth2.Config{
|
||||||
ClientID: envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyGoogleClientID),
|
ClientID: envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyGoogleClientID),
|
||||||
ClientSecret: envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyGoogleClientSecret),
|
ClientSecret: envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyGoogleClientSecret),
|
||||||
RedirectURL: envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyAuthorizerURL) + "/oauth_callback/google",
|
RedirectURL: "/oauth_callback/google",
|
||||||
Endpoint: OIDCProviders.GoogleOIDC.Endpoint(),
|
Endpoint: OIDCProviders.GoogleOIDC.Endpoint(),
|
||||||
Scopes: []string{oidc.ScopeOpenID, "profile", "email"},
|
Scopes: []string{oidc.ScopeOpenID, "profile", "email"},
|
||||||
}
|
}
|
||||||
@@ -52,7 +52,7 @@ func InitOAuth() {
|
|||||||
OAuthProviders.GithubConfig = &oauth2.Config{
|
OAuthProviders.GithubConfig = &oauth2.Config{
|
||||||
ClientID: envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyGithubClientID),
|
ClientID: envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyGithubClientID),
|
||||||
ClientSecret: envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyGithubClientSecret),
|
ClientSecret: envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyGithubClientSecret),
|
||||||
RedirectURL: envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyAuthorizerURL) + "/oauth_callback/github",
|
RedirectURL: "/oauth_callback/github",
|
||||||
Endpoint: githubOAuth2.Endpoint,
|
Endpoint: githubOAuth2.Endpoint,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -60,7 +60,7 @@ func InitOAuth() {
|
|||||||
OAuthProviders.FacebookConfig = &oauth2.Config{
|
OAuthProviders.FacebookConfig = &oauth2.Config{
|
||||||
ClientID: envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyFacebookClientID),
|
ClientID: envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyFacebookClientID),
|
||||||
ClientSecret: envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyFacebookClientSecret),
|
ClientSecret: envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyFacebookClientSecret),
|
||||||
RedirectURL: envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyAuthorizerURL) + "/oauth_callback/facebook",
|
RedirectURL: "/oauth_callback/facebook",
|
||||||
Endpoint: facebookOAuth2.Endpoint,
|
Endpoint: facebookOAuth2.Endpoint,
|
||||||
Scopes: []string{"public_profile", "email"},
|
Scopes: []string{"public_profile", "email"},
|
||||||
}
|
}
|
||||||
|
@@ -5,6 +5,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"github.com/authorizerdev/authorizer/server/constants"
|
"github.com/authorizerdev/authorizer/server/constants"
|
||||||
|
"github.com/authorizerdev/authorizer/server/cookie"
|
||||||
"github.com/authorizerdev/authorizer/server/envstore"
|
"github.com/authorizerdev/authorizer/server/envstore"
|
||||||
"github.com/authorizerdev/authorizer/server/graph/model"
|
"github.com/authorizerdev/authorizer/server/graph/model"
|
||||||
"github.com/authorizerdev/authorizer/server/utils"
|
"github.com/authorizerdev/authorizer/server/utils"
|
||||||
@@ -28,7 +29,7 @@ func AdminLoginResolver(ctx context.Context, params model.AdminLoginInput) (*mod
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return res, err
|
return res, err
|
||||||
}
|
}
|
||||||
utils.SetAdminCookie(gc, hashedKey)
|
cookie.SetAdminCookie(gc, hashedKey)
|
||||||
|
|
||||||
res = &model.Response{
|
res = &model.Response{
|
||||||
Message: "admin logged in successfully",
|
Message: "admin logged in successfully",
|
||||||
|
@@ -4,7 +4,9 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/authorizerdev/authorizer/server/cookie"
|
||||||
"github.com/authorizerdev/authorizer/server/graph/model"
|
"github.com/authorizerdev/authorizer/server/graph/model"
|
||||||
|
"github.com/authorizerdev/authorizer/server/token"
|
||||||
"github.com/authorizerdev/authorizer/server/utils"
|
"github.com/authorizerdev/authorizer/server/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -17,11 +19,11 @@ func AdminLogoutResolver(ctx context.Context) (*model.Response, error) {
|
|||||||
return res, err
|
return res, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if !utils.IsSuperAdmin(gc) {
|
if !token.IsSuperAdmin(gc) {
|
||||||
return res, fmt.Errorf("unauthorized")
|
return res, fmt.Errorf("unauthorized")
|
||||||
}
|
}
|
||||||
|
|
||||||
utils.DeleteAdminCookie(gc)
|
cookie.DeleteAdminCookie(gc)
|
||||||
|
|
||||||
res = &model.Response{
|
res = &model.Response{
|
||||||
Message: "admin logged out successfully",
|
Message: "admin logged out successfully",
|
||||||
|
@@ -5,8 +5,10 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"github.com/authorizerdev/authorizer/server/constants"
|
"github.com/authorizerdev/authorizer/server/constants"
|
||||||
|
"github.com/authorizerdev/authorizer/server/cookie"
|
||||||
"github.com/authorizerdev/authorizer/server/envstore"
|
"github.com/authorizerdev/authorizer/server/envstore"
|
||||||
"github.com/authorizerdev/authorizer/server/graph/model"
|
"github.com/authorizerdev/authorizer/server/graph/model"
|
||||||
|
"github.com/authorizerdev/authorizer/server/token"
|
||||||
"github.com/authorizerdev/authorizer/server/utils"
|
"github.com/authorizerdev/authorizer/server/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -19,7 +21,7 @@ func AdminSessionResolver(ctx context.Context) (*model.Response, error) {
|
|||||||
return res, err
|
return res, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if !utils.IsSuperAdmin(gc) {
|
if !token.IsSuperAdmin(gc) {
|
||||||
return res, fmt.Errorf("unauthorized")
|
return res, fmt.Errorf("unauthorized")
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -27,7 +29,7 @@ func AdminSessionResolver(ctx context.Context) (*model.Response, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return res, err
|
return res, err
|
||||||
}
|
}
|
||||||
utils.SetAdminCookie(gc, hashedKey)
|
cookie.SetAdminCookie(gc, hashedKey)
|
||||||
|
|
||||||
res = &model.Response{
|
res = &model.Response{
|
||||||
Message: "admin logged in successfully",
|
Message: "admin logged in successfully",
|
||||||
|
@@ -7,6 +7,7 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/authorizerdev/authorizer/server/constants"
|
"github.com/authorizerdev/authorizer/server/constants"
|
||||||
|
"github.com/authorizerdev/authorizer/server/cookie"
|
||||||
"github.com/authorizerdev/authorizer/server/db"
|
"github.com/authorizerdev/authorizer/server/db"
|
||||||
"github.com/authorizerdev/authorizer/server/envstore"
|
"github.com/authorizerdev/authorizer/server/envstore"
|
||||||
"github.com/authorizerdev/authorizer/server/graph/model"
|
"github.com/authorizerdev/authorizer/server/graph/model"
|
||||||
@@ -71,7 +72,7 @@ func AdminSignupResolver(ctx context.Context, params model.AdminSignupInput) (*m
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return res, err
|
return res, err
|
||||||
}
|
}
|
||||||
utils.SetAdminCookie(gc, hashedKey)
|
cookie.SetAdminCookie(gc, hashedKey)
|
||||||
|
|
||||||
res = &model.Response{
|
res = &model.Response{
|
||||||
Message: "admin signed up successfully",
|
Message: "admin signed up successfully",
|
||||||
|
@@ -7,7 +7,8 @@ import (
|
|||||||
|
|
||||||
"github.com/authorizerdev/authorizer/server/db"
|
"github.com/authorizerdev/authorizer/server/db"
|
||||||
"github.com/authorizerdev/authorizer/server/graph/model"
|
"github.com/authorizerdev/authorizer/server/graph/model"
|
||||||
"github.com/authorizerdev/authorizer/server/session"
|
"github.com/authorizerdev/authorizer/server/sessionstore"
|
||||||
|
"github.com/authorizerdev/authorizer/server/token"
|
||||||
"github.com/authorizerdev/authorizer/server/utils"
|
"github.com/authorizerdev/authorizer/server/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -19,7 +20,7 @@ func DeleteUserResolver(ctx context.Context, params model.DeleteUserInput) (*mod
|
|||||||
return res, err
|
return res, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if !utils.IsSuperAdmin(gc) {
|
if !token.IsSuperAdmin(gc) {
|
||||||
return res, fmt.Errorf("unauthorized")
|
return res, fmt.Errorf("unauthorized")
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -28,7 +29,7 @@ func DeleteUserResolver(ctx context.Context, params model.DeleteUserInput) (*mod
|
|||||||
return res, err
|
return res, err
|
||||||
}
|
}
|
||||||
|
|
||||||
session.DeleteAllUserSession(fmt.Sprintf("%x", user.ID))
|
sessionstore.DeleteAllUserSession(fmt.Sprintf("%x", user.ID))
|
||||||
|
|
||||||
err = db.Provider.DeleteUser(user)
|
err = db.Provider.DeleteUser(user)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@@ -7,6 +7,7 @@ import (
|
|||||||
"github.com/authorizerdev/authorizer/server/constants"
|
"github.com/authorizerdev/authorizer/server/constants"
|
||||||
"github.com/authorizerdev/authorizer/server/envstore"
|
"github.com/authorizerdev/authorizer/server/envstore"
|
||||||
"github.com/authorizerdev/authorizer/server/graph/model"
|
"github.com/authorizerdev/authorizer/server/graph/model"
|
||||||
|
"github.com/authorizerdev/authorizer/server/token"
|
||||||
"github.com/authorizerdev/authorizer/server/utils"
|
"github.com/authorizerdev/authorizer/server/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -20,16 +21,17 @@ func EnvResolver(ctx context.Context) (*model.Env, error) {
|
|||||||
return res, err
|
return res, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if !utils.IsSuperAdmin(gc) {
|
if !token.IsSuperAdmin(gc) {
|
||||||
return res, fmt.Errorf("unauthorized")
|
return res, fmt.Errorf("unauthorized")
|
||||||
}
|
}
|
||||||
|
|
||||||
// get clone of store
|
// get clone of store
|
||||||
store := envstore.EnvInMemoryStoreObj.GetEnvStoreClone()
|
store := envstore.EnvInMemoryStoreObj.GetEnvStoreClone()
|
||||||
adminSecret := store.StringEnv[constants.EnvKeyAdminSecret]
|
adminSecret := store.StringEnv[constants.EnvKeyAdminSecret]
|
||||||
databaseType := store.StringEnv[constants.EnvKeyDatabaseType]
|
|
||||||
databaseURL := store.StringEnv[constants.EnvKeyDatabaseURL]
|
databaseURL := store.StringEnv[constants.EnvKeyDatabaseURL]
|
||||||
databaseName := store.StringEnv[constants.EnvKeyDatabaseName]
|
databaseName := store.StringEnv[constants.EnvKeyDatabaseName]
|
||||||
|
databaseType := store.StringEnv[constants.EnvKeyDatabaseType]
|
||||||
|
customAccessTokenScript := store.StringEnv[constants.EnvKeyCustomAccessTokenScript]
|
||||||
smtpHost := store.StringEnv[constants.EnvKeySmtpHost]
|
smtpHost := store.StringEnv[constants.EnvKeySmtpHost]
|
||||||
smtpPort := store.StringEnv[constants.EnvKeySmtpPort]
|
smtpPort := store.StringEnv[constants.EnvKeySmtpPort]
|
||||||
smtpUsername := store.StringEnv[constants.EnvKeySmtpUsername]
|
smtpUsername := store.StringEnv[constants.EnvKeySmtpUsername]
|
||||||
@@ -39,7 +41,6 @@ func EnvResolver(ctx context.Context) (*model.Env, error) {
|
|||||||
jwtSecret := store.StringEnv[constants.EnvKeyJwtSecret]
|
jwtSecret := store.StringEnv[constants.EnvKeyJwtSecret]
|
||||||
jwtRoleClaim := store.StringEnv[constants.EnvKeyJwtRoleClaim]
|
jwtRoleClaim := store.StringEnv[constants.EnvKeyJwtRoleClaim]
|
||||||
allowedOrigins := store.SliceEnv[constants.EnvKeyAllowedOrigins]
|
allowedOrigins := store.SliceEnv[constants.EnvKeyAllowedOrigins]
|
||||||
authorizerURL := store.StringEnv[constants.EnvKeyAuthorizerURL]
|
|
||||||
appURL := store.StringEnv[constants.EnvKeyAppURL]
|
appURL := store.StringEnv[constants.EnvKeyAppURL]
|
||||||
redisURL := store.StringEnv[constants.EnvKeyRedisURL]
|
redisURL := store.StringEnv[constants.EnvKeyRedisURL]
|
||||||
cookieName := store.StringEnv[constants.EnvKeyCookieName]
|
cookieName := store.StringEnv[constants.EnvKeyCookieName]
|
||||||
@@ -62,9 +63,10 @@ func EnvResolver(ctx context.Context) (*model.Env, error) {
|
|||||||
|
|
||||||
res = &model.Env{
|
res = &model.Env{
|
||||||
AdminSecret: &adminSecret,
|
AdminSecret: &adminSecret,
|
||||||
DatabaseType: &databaseType,
|
|
||||||
DatabaseURL: &databaseURL,
|
|
||||||
DatabaseName: &databaseName,
|
DatabaseName: &databaseName,
|
||||||
|
DatabaseURL: &databaseURL,
|
||||||
|
DatabaseType: &databaseType,
|
||||||
|
CustomAccessTokenScript: &customAccessTokenScript,
|
||||||
SMTPHost: &smtpHost,
|
SMTPHost: &smtpHost,
|
||||||
SMTPPort: &smtpPort,
|
SMTPPort: &smtpPort,
|
||||||
SMTPPassword: &smtpPassword,
|
SMTPPassword: &smtpPassword,
|
||||||
@@ -74,7 +76,6 @@ func EnvResolver(ctx context.Context) (*model.Env, error) {
|
|||||||
JwtSecret: &jwtSecret,
|
JwtSecret: &jwtSecret,
|
||||||
JwtRoleClaim: &jwtRoleClaim,
|
JwtRoleClaim: &jwtRoleClaim,
|
||||||
AllowedOrigins: allowedOrigins,
|
AllowedOrigins: allowedOrigins,
|
||||||
AuthorizerURL: &authorizerURL,
|
|
||||||
AppURL: &appURL,
|
AppURL: &appURL,
|
||||||
RedisURL: &redisURL,
|
RedisURL: &redisURL,
|
||||||
CookieName: &cookieName,
|
CookieName: &cookieName,
|
||||||
|
@@ -13,6 +13,7 @@ import (
|
|||||||
"github.com/authorizerdev/authorizer/server/email"
|
"github.com/authorizerdev/authorizer/server/email"
|
||||||
"github.com/authorizerdev/authorizer/server/envstore"
|
"github.com/authorizerdev/authorizer/server/envstore"
|
||||||
"github.com/authorizerdev/authorizer/server/graph/model"
|
"github.com/authorizerdev/authorizer/server/graph/model"
|
||||||
|
"github.com/authorizerdev/authorizer/server/token"
|
||||||
"github.com/authorizerdev/authorizer/server/utils"
|
"github.com/authorizerdev/authorizer/server/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -26,7 +27,6 @@ func ForgotPasswordResolver(ctx context.Context, params model.ForgotPasswordInpu
|
|||||||
if envstore.EnvInMemoryStoreObj.GetBoolStoreEnvVariable(constants.EnvKeyDisableBasicAuthentication) {
|
if envstore.EnvInMemoryStoreObj.GetBoolStoreEnvVariable(constants.EnvKeyDisableBasicAuthentication) {
|
||||||
return res, fmt.Errorf(`basic authentication is disabled for this instance`)
|
return res, fmt.Errorf(`basic authentication is disabled for this instance`)
|
||||||
}
|
}
|
||||||
host := gc.Request.Host
|
|
||||||
params.Email = strings.ToLower(params.Email)
|
params.Email = strings.ToLower(params.Email)
|
||||||
|
|
||||||
if !utils.IsValidEmail(params.Email) {
|
if !utils.IsValidEmail(params.Email) {
|
||||||
@@ -38,12 +38,13 @@ func ForgotPasswordResolver(ctx context.Context, params model.ForgotPasswordInpu
|
|||||||
return res, fmt.Errorf(`user with this email not found`)
|
return res, fmt.Errorf(`user with this email not found`)
|
||||||
}
|
}
|
||||||
|
|
||||||
token, err := utils.CreateVerificationToken(params.Email, constants.VerificationTypeForgotPassword)
|
hostname := utils.GetHost(gc)
|
||||||
|
verificationToken, err := token.CreateVerificationToken(params.Email, constants.VerificationTypeForgotPassword, hostname)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Println(`error generating token`, err)
|
log.Println(`error generating token`, err)
|
||||||
}
|
}
|
||||||
db.Provider.AddVerificationRequest(models.VerificationRequest{
|
db.Provider.AddVerificationRequest(models.VerificationRequest{
|
||||||
Token: token,
|
Token: verificationToken,
|
||||||
Identifier: constants.VerificationTypeForgotPassword,
|
Identifier: constants.VerificationTypeForgotPassword,
|
||||||
ExpiresAt: time.Now().Add(time.Minute * 30).Unix(),
|
ExpiresAt: time.Now().Add(time.Minute * 30).Unix(),
|
||||||
Email: params.Email,
|
Email: params.Email,
|
||||||
@@ -51,7 +52,7 @@ func ForgotPasswordResolver(ctx context.Context, params model.ForgotPasswordInpu
|
|||||||
|
|
||||||
// exec it as go routin so that we can reduce the api latency
|
// exec it as go routin so that we can reduce the api latency
|
||||||
go func() {
|
go func() {
|
||||||
email.SendForgotPasswordMail(params.Email, token, host)
|
email.SendForgotPasswordMail(params.Email, verificationToken, hostname)
|
||||||
}()
|
}()
|
||||||
|
|
||||||
res = &model.Response{
|
res = &model.Response{
|
||||||
|
52
server/resolvers/is_valid_jwt.go
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
package resolvers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/authorizerdev/authorizer/server/constants"
|
||||||
|
"github.com/authorizerdev/authorizer/server/envstore"
|
||||||
|
"github.com/authorizerdev/authorizer/server/graph/model"
|
||||||
|
"github.com/authorizerdev/authorizer/server/token"
|
||||||
|
tokenHelper "github.com/authorizerdev/authorizer/server/token"
|
||||||
|
"github.com/authorizerdev/authorizer/server/utils"
|
||||||
|
)
|
||||||
|
|
||||||
|
// IsValidJwtResolver resolver to return if given jwt is valid
|
||||||
|
func IsValidJwtResolver(ctx context.Context, params *model.IsValidJWTQueryInput) (*model.ValidJWTResponse, error) {
|
||||||
|
gc, err := utils.GinContextFromContext(ctx)
|
||||||
|
token, err := token.GetAccessToken(gc)
|
||||||
|
|
||||||
|
if token == "" || err != nil {
|
||||||
|
if params != nil && *params.Jwt != "" {
|
||||||
|
token = *params.Jwt
|
||||||
|
} else {
|
||||||
|
return nil, errors.New("no jwt provided via cookie / header / params")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
claims, err := tokenHelper.VerifyJWTToken(token)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
claimRoleInterface := claims[envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyJwtRoleClaim)].([]interface{})
|
||||||
|
claimRoles := []string{}
|
||||||
|
for _, v := range claimRoleInterface {
|
||||||
|
claimRoles = append(claimRoles, v.(string))
|
||||||
|
}
|
||||||
|
|
||||||
|
if params != nil && params.Roles != nil && len(params.Roles) > 0 {
|
||||||
|
for _, v := range params.Roles {
|
||||||
|
if !utils.StringSliceContains(claimRoles, v) {
|
||||||
|
return nil, fmt.Errorf(`unauthorized`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return &model.ValidJWTResponse{
|
||||||
|
Valid: true,
|
||||||
|
Message: "Valid JWT",
|
||||||
|
}, nil
|
||||||
|
}
|
@@ -7,10 +7,12 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/authorizerdev/authorizer/server/constants"
|
"github.com/authorizerdev/authorizer/server/constants"
|
||||||
|
"github.com/authorizerdev/authorizer/server/cookie"
|
||||||
"github.com/authorizerdev/authorizer/server/db"
|
"github.com/authorizerdev/authorizer/server/db"
|
||||||
"github.com/authorizerdev/authorizer/server/envstore"
|
"github.com/authorizerdev/authorizer/server/envstore"
|
||||||
"github.com/authorizerdev/authorizer/server/graph/model"
|
"github.com/authorizerdev/authorizer/server/graph/model"
|
||||||
"github.com/authorizerdev/authorizer/server/session"
|
"github.com/authorizerdev/authorizer/server/sessionstore"
|
||||||
|
"github.com/authorizerdev/authorizer/server/token"
|
||||||
"github.com/authorizerdev/authorizer/server/utils"
|
"github.com/authorizerdev/authorizer/server/utils"
|
||||||
"golang.org/x/crypto/bcrypt"
|
"golang.org/x/crypto/bcrypt"
|
||||||
)
|
)
|
||||||
@@ -56,21 +58,21 @@ func LoginResolver(ctx context.Context, params model.LoginInput) (*model.AuthRes
|
|||||||
|
|
||||||
roles = params.Roles
|
roles = params.Roles
|
||||||
}
|
}
|
||||||
refreshToken, _, _ := utils.CreateAuthToken(user, constants.TokenTypeRefreshToken, roles)
|
|
||||||
|
|
||||||
accessToken, expiresAt, _ := utils.CreateAuthToken(user, constants.TokenTypeAccessToken, roles)
|
authToken, err := token.CreateAuthToken(user, roles)
|
||||||
|
if err != nil {
|
||||||
session.SetUserSession(user.ID, accessToken, refreshToken)
|
return res, err
|
||||||
|
}
|
||||||
|
sessionstore.SetUserSession(user.ID, authToken.FingerPrint, authToken.RefreshToken.Token)
|
||||||
|
cookie.SetCookie(gc, authToken.AccessToken.Token, authToken.RefreshToken.Token, authToken.FingerPrintHash)
|
||||||
utils.SaveSessionInDB(user.ID, gc)
|
utils.SaveSessionInDB(user.ID, gc)
|
||||||
|
|
||||||
res = &model.AuthResponse{
|
res = &model.AuthResponse{
|
||||||
Message: `Logged in successfully`,
|
Message: `Logged in successfully`,
|
||||||
AccessToken: &accessToken,
|
AccessToken: &authToken.AccessToken.Token,
|
||||||
ExpiresAt: &expiresAt,
|
ExpiresAt: &authToken.AccessToken.ExpiresAt,
|
||||||
User: utils.GetResponseUserData(user),
|
User: user.AsAPIUser(),
|
||||||
}
|
}
|
||||||
|
|
||||||
utils.SetCookie(gc, accessToken)
|
|
||||||
|
|
||||||
return res, nil
|
return res, nil
|
||||||
}
|
}
|
||||||
|
@@ -2,10 +2,11 @@ package resolvers
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
|
||||||
|
|
||||||
|
"github.com/authorizerdev/authorizer/server/cookie"
|
||||||
"github.com/authorizerdev/authorizer/server/graph/model"
|
"github.com/authorizerdev/authorizer/server/graph/model"
|
||||||
"github.com/authorizerdev/authorizer/server/session"
|
"github.com/authorizerdev/authorizer/server/sessionstore"
|
||||||
|
"github.com/authorizerdev/authorizer/server/token"
|
||||||
"github.com/authorizerdev/authorizer/server/utils"
|
"github.com/authorizerdev/authorizer/server/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -17,22 +18,38 @@ func LogoutResolver(ctx context.Context) (*model.Response, error) {
|
|||||||
return res, err
|
return res, err
|
||||||
}
|
}
|
||||||
|
|
||||||
token, err := utils.GetAuthToken(gc)
|
// get refresh token
|
||||||
|
refreshToken, err := token.GetRefreshToken(gc)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return res, err
|
return res, err
|
||||||
}
|
}
|
||||||
|
|
||||||
claim, err := utils.VerifyAuthToken(token)
|
// get fingerprint hash
|
||||||
|
fingerprintHash, err := token.GetFingerPrint(gc)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return res, err
|
return res, err
|
||||||
}
|
}
|
||||||
|
|
||||||
userId := fmt.Sprintf("%v", claim["id"])
|
decryptedFingerPrint, err := utils.DecryptAES([]byte(fingerprintHash))
|
||||||
session.DeleteUserSession(userId, token)
|
if err != nil {
|
||||||
|
return res, err
|
||||||
|
}
|
||||||
|
|
||||||
|
fingerPrint := string(decryptedFingerPrint)
|
||||||
|
|
||||||
|
// verify refresh token and fingerprint
|
||||||
|
claims, err := token.VerifyJWTToken(refreshToken)
|
||||||
|
if err != nil {
|
||||||
|
return res, err
|
||||||
|
}
|
||||||
|
|
||||||
|
userID := claims["id"].(string)
|
||||||
|
sessionstore.DeleteUserSession(userID, fingerPrint)
|
||||||
|
cookie.DeleteCookie(gc)
|
||||||
|
|
||||||
res = &model.Response{
|
res = &model.Response{
|
||||||
Message: "Logged out successfully",
|
Message: "Logged out successfully",
|
||||||
}
|
}
|
||||||
|
|
||||||
utils.DeleteCookie(gc)
|
|
||||||
return res, nil
|
return res, nil
|
||||||
}
|
}
|
||||||
|
@@ -13,12 +13,17 @@ import (
|
|||||||
"github.com/authorizerdev/authorizer/server/email"
|
"github.com/authorizerdev/authorizer/server/email"
|
||||||
"github.com/authorizerdev/authorizer/server/envstore"
|
"github.com/authorizerdev/authorizer/server/envstore"
|
||||||
"github.com/authorizerdev/authorizer/server/graph/model"
|
"github.com/authorizerdev/authorizer/server/graph/model"
|
||||||
|
"github.com/authorizerdev/authorizer/server/token"
|
||||||
"github.com/authorizerdev/authorizer/server/utils"
|
"github.com/authorizerdev/authorizer/server/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
// MagicLinkLoginResolver is a resolver for magic link login mutation
|
// MagicLinkLoginResolver is a resolver for magic link login mutation
|
||||||
func MagicLinkLoginResolver(ctx context.Context, params model.MagicLinkLoginInput) (*model.Response, error) {
|
func MagicLinkLoginResolver(ctx context.Context, params model.MagicLinkLoginInput) (*model.Response, error) {
|
||||||
var res *model.Response
|
var res *model.Response
|
||||||
|
gc, err := utils.GinContextFromContext(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return res, err
|
||||||
|
}
|
||||||
|
|
||||||
if envstore.EnvInMemoryStoreObj.GetBoolStoreEnvVariable(constants.EnvKeyDisableMagicLinkLogin) {
|
if envstore.EnvInMemoryStoreObj.GetBoolStoreEnvVariable(constants.EnvKeyDisableMagicLinkLogin) {
|
||||||
return res, fmt.Errorf(`magic link login is disabled for this instance`)
|
return res, fmt.Errorf(`magic link login is disabled for this instance`)
|
||||||
@@ -101,15 +106,16 @@ func MagicLinkLoginResolver(ctx context.Context, params model.MagicLinkLoginInpu
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
hostname := utils.GetHost(gc)
|
||||||
if !envstore.EnvInMemoryStoreObj.GetBoolStoreEnvVariable(constants.EnvKeyDisableEmailVerification) {
|
if !envstore.EnvInMemoryStoreObj.GetBoolStoreEnvVariable(constants.EnvKeyDisableEmailVerification) {
|
||||||
// insert verification request
|
// insert verification request
|
||||||
verificationType := constants.VerificationTypeMagicLinkLogin
|
verificationType := constants.VerificationTypeMagicLinkLogin
|
||||||
token, err := utils.CreateVerificationToken(params.Email, verificationType)
|
verificationToken, err := token.CreateVerificationToken(params.Email, verificationType, hostname)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Println(`error generating token`, err)
|
log.Println(`error generating token`, err)
|
||||||
}
|
}
|
||||||
db.Provider.AddVerificationRequest(models.VerificationRequest{
|
db.Provider.AddVerificationRequest(models.VerificationRequest{
|
||||||
Token: token,
|
Token: verificationToken,
|
||||||
Identifier: verificationType,
|
Identifier: verificationType,
|
||||||
ExpiresAt: time.Now().Add(time.Minute * 30).Unix(),
|
ExpiresAt: time.Now().Add(time.Minute * 30).Unix(),
|
||||||
Email: params.Email,
|
Email: params.Email,
|
||||||
@@ -117,7 +123,7 @@ func MagicLinkLoginResolver(ctx context.Context, params model.MagicLinkLoginInpu
|
|||||||
|
|
||||||
// exec it as go routin so that we can reduce the api latency
|
// exec it as go routin so that we can reduce the api latency
|
||||||
go func() {
|
go func() {
|
||||||
email.SendVerificationMail(params.Email, token)
|
email.SendVerificationMail(params.Email, verificationToken, hostname)
|
||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -6,7 +6,7 @@ import (
|
|||||||
|
|
||||||
"github.com/authorizerdev/authorizer/server/db"
|
"github.com/authorizerdev/authorizer/server/db"
|
||||||
"github.com/authorizerdev/authorizer/server/graph/model"
|
"github.com/authorizerdev/authorizer/server/graph/model"
|
||||||
"github.com/authorizerdev/authorizer/server/session"
|
"github.com/authorizerdev/authorizer/server/token"
|
||||||
"github.com/authorizerdev/authorizer/server/utils"
|
"github.com/authorizerdev/authorizer/server/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -18,30 +18,17 @@ func ProfileResolver(ctx context.Context) (*model.User, error) {
|
|||||||
return res, err
|
return res, err
|
||||||
}
|
}
|
||||||
|
|
||||||
token, err := utils.GetAuthToken(gc)
|
claims, err := token.ValidateAccessToken(gc)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return res, err
|
return res, err
|
||||||
}
|
}
|
||||||
|
|
||||||
claim, err := utils.VerifyAuthToken(token)
|
userID := fmt.Sprintf("%v", claims["id"])
|
||||||
|
|
||||||
|
user, err := db.Provider.GetUserByID(userID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return res, err
|
return res, err
|
||||||
}
|
}
|
||||||
|
|
||||||
userID := fmt.Sprintf("%v", claim["id"])
|
return user.AsAPIUser(), nil
|
||||||
email := fmt.Sprintf("%v", claim["email"])
|
|
||||||
sessionToken := session.GetUserSession(userID, token)
|
|
||||||
|
|
||||||
if sessionToken == "" {
|
|
||||||
return res, fmt.Errorf(`unauthorized`)
|
|
||||||
}
|
|
||||||
|
|
||||||
user, err := db.Provider.GetUserByEmail(email)
|
|
||||||
if err != nil {
|
|
||||||
return res, err
|
|
||||||
}
|
|
||||||
|
|
||||||
res = utils.GetResponseUserData(user)
|
|
||||||
|
|
||||||
return res, nil
|
|
||||||
}
|
}
|
||||||
|
@@ -11,12 +11,17 @@ import (
|
|||||||
"github.com/authorizerdev/authorizer/server/db/models"
|
"github.com/authorizerdev/authorizer/server/db/models"
|
||||||
"github.com/authorizerdev/authorizer/server/email"
|
"github.com/authorizerdev/authorizer/server/email"
|
||||||
"github.com/authorizerdev/authorizer/server/graph/model"
|
"github.com/authorizerdev/authorizer/server/graph/model"
|
||||||
|
"github.com/authorizerdev/authorizer/server/token"
|
||||||
"github.com/authorizerdev/authorizer/server/utils"
|
"github.com/authorizerdev/authorizer/server/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
// ResendVerifyEmailResolver is a resolver for resend verify email mutation
|
// ResendVerifyEmailResolver is a resolver for resend verify email mutation
|
||||||
func ResendVerifyEmailResolver(ctx context.Context, params model.ResendVerifyEmailInput) (*model.Response, error) {
|
func ResendVerifyEmailResolver(ctx context.Context, params model.ResendVerifyEmailInput) (*model.Response, error) {
|
||||||
var res *model.Response
|
var res *model.Response
|
||||||
|
gc, err := utils.GinContextFromContext(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return res, err
|
||||||
|
}
|
||||||
params.Email = strings.ToLower(params.Email)
|
params.Email = strings.ToLower(params.Email)
|
||||||
|
|
||||||
if !utils.IsValidEmail(params.Email) {
|
if !utils.IsValidEmail(params.Email) {
|
||||||
@@ -38,12 +43,13 @@ func ResendVerifyEmailResolver(ctx context.Context, params model.ResendVerifyEma
|
|||||||
log.Println("error deleting verification request:", err)
|
log.Println("error deleting verification request:", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
token, err := utils.CreateVerificationToken(params.Email, params.Identifier)
|
hostname := utils.GetHost(gc)
|
||||||
|
verificationToken, err := token.CreateVerificationToken(params.Email, params.Identifier, hostname)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Println(`error generating token`, err)
|
log.Println(`error generating token`, err)
|
||||||
}
|
}
|
||||||
db.Provider.AddVerificationRequest(models.VerificationRequest{
|
db.Provider.AddVerificationRequest(models.VerificationRequest{
|
||||||
Token: token,
|
Token: verificationToken,
|
||||||
Identifier: params.Identifier,
|
Identifier: params.Identifier,
|
||||||
ExpiresAt: time.Now().Add(time.Minute * 30).Unix(),
|
ExpiresAt: time.Now().Add(time.Minute * 30).Unix(),
|
||||||
Email: params.Email,
|
Email: params.Email,
|
||||||
@@ -51,7 +57,7 @@ func ResendVerifyEmailResolver(ctx context.Context, params model.ResendVerifyEma
|
|||||||
|
|
||||||
// exec it as go routin so that we can reduce the api latency
|
// exec it as go routin so that we can reduce the api latency
|
||||||
go func() {
|
go func() {
|
||||||
email.SendVerificationMail(params.Email, token)
|
email.SendVerificationMail(params.Email, verificationToken, hostname)
|
||||||
}()
|
}()
|
||||||
|
|
||||||
res = &model.Response{
|
res = &model.Response{
|
||||||
|
@@ -10,6 +10,7 @@ import (
|
|||||||
"github.com/authorizerdev/authorizer/server/db"
|
"github.com/authorizerdev/authorizer/server/db"
|
||||||
"github.com/authorizerdev/authorizer/server/envstore"
|
"github.com/authorizerdev/authorizer/server/envstore"
|
||||||
"github.com/authorizerdev/authorizer/server/graph/model"
|
"github.com/authorizerdev/authorizer/server/graph/model"
|
||||||
|
"github.com/authorizerdev/authorizer/server/token"
|
||||||
"github.com/authorizerdev/authorizer/server/utils"
|
"github.com/authorizerdev/authorizer/server/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -30,7 +31,7 @@ func ResetPasswordResolver(ctx context.Context, params model.ResetPasswordInput)
|
|||||||
}
|
}
|
||||||
|
|
||||||
// verify if token exists in db
|
// verify if token exists in db
|
||||||
claim, err := utils.VerifyVerificationToken(params.Token)
|
claim, err := token.VerifyVerificationToken(params.Token)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return res, fmt.Errorf(`invalid token`)
|
return res, fmt.Errorf(`invalid token`)
|
||||||
}
|
}
|
||||||
|
@@ -3,80 +3,92 @@ package resolvers
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/authorizerdev/authorizer/server/constants"
|
"github.com/authorizerdev/authorizer/server/cookie"
|
||||||
"github.com/authorizerdev/authorizer/server/db"
|
"github.com/authorizerdev/authorizer/server/db"
|
||||||
"github.com/authorizerdev/authorizer/server/envstore"
|
|
||||||
"github.com/authorizerdev/authorizer/server/graph/model"
|
"github.com/authorizerdev/authorizer/server/graph/model"
|
||||||
"github.com/authorizerdev/authorizer/server/session"
|
"github.com/authorizerdev/authorizer/server/sessionstore"
|
||||||
|
"github.com/authorizerdev/authorizer/server/token"
|
||||||
"github.com/authorizerdev/authorizer/server/utils"
|
"github.com/authorizerdev/authorizer/server/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
// SessionResolver is a resolver for session query
|
// SessionResolver is a resolver for session query
|
||||||
func SessionResolver(ctx context.Context, roles []string) (*model.AuthResponse, error) {
|
func SessionResolver(ctx context.Context, params *model.SessionQueryInput) (*model.AuthResponse, error) {
|
||||||
var res *model.AuthResponse
|
var res *model.AuthResponse
|
||||||
|
|
||||||
gc, err := utils.GinContextFromContext(ctx)
|
gc, err := utils.GinContextFromContext(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return res, err
|
return res, err
|
||||||
}
|
}
|
||||||
token, err := utils.GetAuthToken(gc)
|
|
||||||
|
// get refresh token
|
||||||
|
refreshToken, err := token.GetRefreshToken(gc)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return res, err
|
return res, err
|
||||||
}
|
}
|
||||||
|
|
||||||
claim, accessTokenErr := utils.VerifyAuthToken(token)
|
// get fingerprint hash
|
||||||
expiresAt := claim["exp"].(int64)
|
fingerprintHash, err := token.GetFingerPrint(gc)
|
||||||
email := fmt.Sprintf("%v", claim["email"])
|
|
||||||
|
|
||||||
user, err := db.Provider.GetUserByEmail(email)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return res, err
|
return res, err
|
||||||
}
|
}
|
||||||
|
|
||||||
userIdStr := fmt.Sprintf("%v", user.ID)
|
decryptedFingerPrint, err := utils.DecryptAES([]byte(fingerprintHash))
|
||||||
|
if err != nil {
|
||||||
|
return res, err
|
||||||
|
}
|
||||||
|
|
||||||
sessionToken := session.GetUserSession(userIdStr, token)
|
fingerPrint := string(decryptedFingerPrint)
|
||||||
|
|
||||||
if sessionToken == "" {
|
// verify refresh token and fingerprint
|
||||||
|
claims, err := token.VerifyJWTToken(refreshToken)
|
||||||
|
if err != nil {
|
||||||
|
return res, err
|
||||||
|
}
|
||||||
|
|
||||||
|
userID := claims["id"].(string)
|
||||||
|
|
||||||
|
persistedRefresh := sessionstore.GetUserSession(userID, fingerPrint)
|
||||||
|
if refreshToken != persistedRefresh {
|
||||||
return res, fmt.Errorf(`unauthorized`)
|
return res, fmt.Errorf(`unauthorized`)
|
||||||
}
|
}
|
||||||
|
|
||||||
expiresTimeObj := time.Unix(expiresAt, 0)
|
user, err := db.Provider.GetUserByID(userID)
|
||||||
currentTimeObj := time.Now()
|
if err != nil {
|
||||||
|
return res, err
|
||||||
claimRoleInterface := claim[envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyJwtRoleClaim)].([]interface{})
|
|
||||||
claimRoles := make([]string, len(claimRoleInterface))
|
|
||||||
for i, v := range claimRoleInterface {
|
|
||||||
claimRoles[i] = v.(string)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(roles) > 0 {
|
// refresh token has "roles" as claim
|
||||||
for _, v := range roles {
|
claimRoleInterface := claims["roles"].([]interface{})
|
||||||
|
claimRoles := []string{}
|
||||||
|
for _, v := range claimRoleInterface {
|
||||||
|
claimRoles = append(claimRoles, v.(string))
|
||||||
|
}
|
||||||
|
|
||||||
|
if params != nil && params.Roles != nil && len(params.Roles) > 0 {
|
||||||
|
for _, v := range params.Roles {
|
||||||
if !utils.StringSliceContains(claimRoles, v) {
|
if !utils.StringSliceContains(claimRoles, v) {
|
||||||
return res, fmt.Errorf(`unauthorized`)
|
return res, fmt.Errorf(`unauthorized`)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO change this logic to make it more secure
|
// delete older session
|
||||||
if accessTokenErr != nil || expiresTimeObj.Sub(currentTimeObj).Minutes() <= 5 {
|
sessionstore.DeleteUserSession(userID, fingerPrint)
|
||||||
// if access token has expired and refresh/session token is valid
|
|
||||||
// generate new accessToken
|
authToken, err := token.CreateAuthToken(user, claimRoles)
|
||||||
currentRefreshToken := session.GetUserSession(userIdStr, token)
|
if err != nil {
|
||||||
session.DeleteUserSession(userIdStr, token)
|
return res, err
|
||||||
token, expiresAt, _ = utils.CreateAuthToken(user, constants.TokenTypeAccessToken, claimRoles)
|
}
|
||||||
session.SetUserSession(userIdStr, token, currentRefreshToken)
|
sessionstore.SetUserSession(user.ID, authToken.FingerPrint, authToken.RefreshToken.Token)
|
||||||
utils.SaveSessionInDB(user.ID, gc)
|
cookie.SetCookie(gc, authToken.AccessToken.Token, authToken.RefreshToken.Token, authToken.FingerPrintHash)
|
||||||
|
|
||||||
|
res = &model.AuthResponse{
|
||||||
|
Message: `Session token refreshed`,
|
||||||
|
AccessToken: &authToken.AccessToken.Token,
|
||||||
|
ExpiresAt: &authToken.AccessToken.ExpiresAt,
|
||||||
|
User: user.AsAPIUser(),
|
||||||
}
|
}
|
||||||
|
|
||||||
utils.SetCookie(gc, token)
|
|
||||||
res = &model.AuthResponse{
|
|
||||||
Message: `Token verified`,
|
|
||||||
AccessToken: &token,
|
|
||||||
ExpiresAt: &expiresAt,
|
|
||||||
User: utils.GetResponseUserData(user),
|
|
||||||
}
|
|
||||||
return res, nil
|
return res, nil
|
||||||
}
|
}
|
||||||
|
@@ -8,12 +8,14 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/authorizerdev/authorizer/server/constants"
|
"github.com/authorizerdev/authorizer/server/constants"
|
||||||
|
"github.com/authorizerdev/authorizer/server/cookie"
|
||||||
"github.com/authorizerdev/authorizer/server/db"
|
"github.com/authorizerdev/authorizer/server/db"
|
||||||
"github.com/authorizerdev/authorizer/server/db/models"
|
"github.com/authorizerdev/authorizer/server/db/models"
|
||||||
"github.com/authorizerdev/authorizer/server/email"
|
"github.com/authorizerdev/authorizer/server/email"
|
||||||
"github.com/authorizerdev/authorizer/server/envstore"
|
"github.com/authorizerdev/authorizer/server/envstore"
|
||||||
"github.com/authorizerdev/authorizer/server/graph/model"
|
"github.com/authorizerdev/authorizer/server/graph/model"
|
||||||
"github.com/authorizerdev/authorizer/server/session"
|
"github.com/authorizerdev/authorizer/server/sessionstore"
|
||||||
|
"github.com/authorizerdev/authorizer/server/token"
|
||||||
"github.com/authorizerdev/authorizer/server/utils"
|
"github.com/authorizerdev/authorizer/server/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -114,19 +116,19 @@ func SignupResolver(ctx context.Context, params model.SignUpInput) (*model.AuthR
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return res, err
|
return res, err
|
||||||
}
|
}
|
||||||
userIdStr := fmt.Sprintf("%v", user.ID)
|
|
||||||
roles := strings.Split(user.Roles, ",")
|
roles := strings.Split(user.Roles, ",")
|
||||||
userToReturn := utils.GetResponseUserData(user)
|
userToReturn := user.AsAPIUser()
|
||||||
|
|
||||||
|
hostname := utils.GetHost(gc)
|
||||||
if !envstore.EnvInMemoryStoreObj.GetBoolStoreEnvVariable(constants.EnvKeyDisableEmailVerification) {
|
if !envstore.EnvInMemoryStoreObj.GetBoolStoreEnvVariable(constants.EnvKeyDisableEmailVerification) {
|
||||||
// insert verification request
|
// insert verification request
|
||||||
verificationType := constants.VerificationTypeBasicAuthSignup
|
verificationType := constants.VerificationTypeBasicAuthSignup
|
||||||
token, err := utils.CreateVerificationToken(params.Email, verificationType)
|
verificationToken, err := token.CreateVerificationToken(params.Email, verificationType, hostname)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Println(`error generating token`, err)
|
log.Println(`error generating token`, err)
|
||||||
}
|
}
|
||||||
db.Provider.AddVerificationRequest(models.VerificationRequest{
|
db.Provider.AddVerificationRequest(models.VerificationRequest{
|
||||||
Token: token,
|
Token: verificationToken,
|
||||||
Identifier: verificationType,
|
Identifier: verificationType,
|
||||||
ExpiresAt: time.Now().Add(time.Minute * 30).Unix(),
|
ExpiresAt: time.Now().Add(time.Minute * 30).Unix(),
|
||||||
Email: params.Email,
|
Email: params.Email,
|
||||||
@@ -134,7 +136,7 @@ func SignupResolver(ctx context.Context, params model.SignUpInput) (*model.AuthR
|
|||||||
|
|
||||||
// exec it as go routin so that we can reduce the api latency
|
// exec it as go routin so that we can reduce the api latency
|
||||||
go func() {
|
go func() {
|
||||||
email.SendVerificationMail(params.Email, token)
|
email.SendVerificationMail(params.Email, verificationToken, hostname)
|
||||||
}()
|
}()
|
||||||
|
|
||||||
res = &model.AuthResponse{
|
res = &model.AuthResponse{
|
||||||
@@ -143,20 +145,20 @@ func SignupResolver(ctx context.Context, params model.SignUpInput) (*model.AuthR
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|
||||||
refreshToken, _, _ := utils.CreateAuthToken(user, constants.TokenTypeRefreshToken, roles)
|
authToken, err := token.CreateAuthToken(user, roles)
|
||||||
|
if err != nil {
|
||||||
accessToken, expiresAt, _ := utils.CreateAuthToken(user, constants.TokenTypeAccessToken, roles)
|
return res, err
|
||||||
|
}
|
||||||
session.SetUserSession(userIdStr, accessToken, refreshToken)
|
sessionstore.SetUserSession(user.ID, authToken.FingerPrint, authToken.RefreshToken.Token)
|
||||||
|
cookie.SetCookie(gc, authToken.AccessToken.Token, authToken.RefreshToken.Token, authToken.FingerPrintHash)
|
||||||
utils.SaveSessionInDB(user.ID, gc)
|
utils.SaveSessionInDB(user.ID, gc)
|
||||||
|
|
||||||
res = &model.AuthResponse{
|
res = &model.AuthResponse{
|
||||||
Message: `Signed up successfully.`,
|
Message: `Signed up successfully.`,
|
||||||
AccessToken: &accessToken,
|
AccessToken: &authToken.AccessToken.Token,
|
||||||
ExpiresAt: &expiresAt,
|
ExpiresAt: &authToken.AccessToken.ExpiresAt,
|
||||||
User: userToReturn,
|
User: userToReturn,
|
||||||
}
|
}
|
||||||
|
|
||||||
utils.SetCookie(gc, accessToken)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return res, nil
|
return res, nil
|
||||||
|
@@ -9,11 +9,14 @@ import (
|
|||||||
"reflect"
|
"reflect"
|
||||||
|
|
||||||
"github.com/authorizerdev/authorizer/server/constants"
|
"github.com/authorizerdev/authorizer/server/constants"
|
||||||
|
"github.com/authorizerdev/authorizer/server/cookie"
|
||||||
"github.com/authorizerdev/authorizer/server/db"
|
"github.com/authorizerdev/authorizer/server/db"
|
||||||
"github.com/authorizerdev/authorizer/server/envstore"
|
"github.com/authorizerdev/authorizer/server/envstore"
|
||||||
"github.com/authorizerdev/authorizer/server/graph/model"
|
"github.com/authorizerdev/authorizer/server/graph/model"
|
||||||
|
"github.com/authorizerdev/authorizer/server/oauth"
|
||||||
|
"github.com/authorizerdev/authorizer/server/sessionstore"
|
||||||
|
"github.com/authorizerdev/authorizer/server/token"
|
||||||
"github.com/authorizerdev/authorizer/server/utils"
|
"github.com/authorizerdev/authorizer/server/utils"
|
||||||
"golang.org/x/crypto/bcrypt"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// UpdateEnvResolver is a resolver for update config mutation
|
// UpdateEnvResolver is a resolver for update config mutation
|
||||||
@@ -26,7 +29,7 @@ func UpdateEnvResolver(ctx context.Context, params model.UpdateEnvInput) (*model
|
|||||||
return res, err
|
return res, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if !utils.IsSuperAdmin(gc) {
|
if !token.IsSuperAdmin(gc) {
|
||||||
return res, fmt.Errorf("unauthorized")
|
return res, fmt.Errorf("unauthorized")
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -41,6 +44,23 @@ func UpdateEnvResolver(ctx context.Context, params model.UpdateEnvInput) (*model
|
|||||||
return res, fmt.Errorf("error un-marshalling params: %t", err)
|
return res, fmt.Errorf("error un-marshalling params: %t", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// in case of admin secret change update the cookie with new hash
|
||||||
|
if params.AdminSecret != nil {
|
||||||
|
if params.OldAdminSecret == nil {
|
||||||
|
return res, errors.New("admin secret and old admin secret are required for secret change")
|
||||||
|
}
|
||||||
|
|
||||||
|
if *params.OldAdminSecret != envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyAdminSecret) {
|
||||||
|
return res, errors.New("old admin secret is not correct")
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(*params.AdminSecret) < 6 {
|
||||||
|
err = fmt.Errorf("admin secret must be at least 6 characters")
|
||||||
|
return res, err
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
updatedData := envstore.EnvInMemoryStoreObj.GetEnvStoreClone()
|
updatedData := envstore.EnvInMemoryStoreObj.GetEnvStoreClone()
|
||||||
for key, value := range data {
|
for key, value := range data {
|
||||||
if value != nil {
|
if value != nil {
|
||||||
@@ -74,8 +94,31 @@ func UpdateEnvResolver(ctx context.Context, params model.UpdateEnvInput) (*model
|
|||||||
updatedData.BoolEnv[constants.EnvKeyDisableMagicLinkLogin] = true
|
updatedData.BoolEnv[constants.EnvKeyDisableMagicLinkLogin] = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// check the roles change
|
||||||
|
if len(params.Roles) > 0 {
|
||||||
|
if len(params.DefaultRoles) > 0 {
|
||||||
|
// should be subset of roles
|
||||||
|
for _, role := range params.DefaultRoles {
|
||||||
|
if !utils.StringSliceContains(params.Roles, role) {
|
||||||
|
return res, fmt.Errorf("default role %s is not in roles", role)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(params.ProtectedRoles) > 0 {
|
||||||
|
for _, role := range params.ProtectedRoles {
|
||||||
|
if utils.StringSliceContains(params.Roles, role) || utils.StringSliceContains(params.DefaultRoles, role) {
|
||||||
|
return res, fmt.Errorf("protected role %s found roles or default roles", role)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Update local store
|
// Update local store
|
||||||
envstore.EnvInMemoryStoreObj.UpdateEnvStore(updatedData)
|
envstore.EnvInMemoryStoreObj.UpdateEnvStore(updatedData)
|
||||||
|
sessionstore.InitSession()
|
||||||
|
oauth.InitOAuth()
|
||||||
|
|
||||||
// Fetch the current db store and update it
|
// Fetch the current db store and update it
|
||||||
env, err := db.Provider.GetEnv()
|
env, err := db.Provider.GetEnv()
|
||||||
@@ -83,32 +126,17 @@ func UpdateEnvResolver(ctx context.Context, params model.UpdateEnvInput) (*model
|
|||||||
return res, err
|
return res, err
|
||||||
}
|
}
|
||||||
|
|
||||||
encryptedConfig, err := utils.EncryptEnvData(updatedData)
|
|
||||||
if err != nil {
|
|
||||||
return res, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// in case of db change re-initialize db
|
|
||||||
if params.DatabaseType != nil || params.DatabaseURL != nil || params.DatabaseName != nil {
|
|
||||||
db.InitDB()
|
|
||||||
}
|
|
||||||
|
|
||||||
// in case of admin secret change update the cookie with new hash
|
|
||||||
if params.AdminSecret != nil {
|
if params.AdminSecret != nil {
|
||||||
if params.OldAdminSecret == nil {
|
|
||||||
return res, errors.New("admin secret and old admin secret are required for secret change")
|
|
||||||
}
|
|
||||||
|
|
||||||
err := bcrypt.CompareHashAndPassword([]byte(*params.OldAdminSecret), []byte(envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyAdminSecret)))
|
|
||||||
if err != nil {
|
|
||||||
return res, errors.New("old admin secret is not correct")
|
|
||||||
}
|
|
||||||
|
|
||||||
hashedKey, err := utils.EncryptPassword(envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyAdminSecret))
|
hashedKey, err := utils.EncryptPassword(envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyAdminSecret))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return res, err
|
return res, err
|
||||||
}
|
}
|
||||||
utils.SetAdminCookie(gc, hashedKey)
|
cookie.SetAdminCookie(gc, hashedKey)
|
||||||
|
}
|
||||||
|
|
||||||
|
encryptedConfig, err := utils.EncryptEnvData(updatedData)
|
||||||
|
if err != nil {
|
||||||
|
return res, err
|
||||||
}
|
}
|
||||||
|
|
||||||
env.EnvData = encryptedConfig
|
env.EnvData = encryptedConfig
|
||||||
|
@@ -8,11 +8,13 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/authorizerdev/authorizer/server/constants"
|
"github.com/authorizerdev/authorizer/server/constants"
|
||||||
|
"github.com/authorizerdev/authorizer/server/cookie"
|
||||||
"github.com/authorizerdev/authorizer/server/db"
|
"github.com/authorizerdev/authorizer/server/db"
|
||||||
"github.com/authorizerdev/authorizer/server/db/models"
|
"github.com/authorizerdev/authorizer/server/db/models"
|
||||||
"github.com/authorizerdev/authorizer/server/email"
|
"github.com/authorizerdev/authorizer/server/email"
|
||||||
"github.com/authorizerdev/authorizer/server/graph/model"
|
"github.com/authorizerdev/authorizer/server/graph/model"
|
||||||
"github.com/authorizerdev/authorizer/server/session"
|
"github.com/authorizerdev/authorizer/server/sessionstore"
|
||||||
|
"github.com/authorizerdev/authorizer/server/token"
|
||||||
"github.com/authorizerdev/authorizer/server/utils"
|
"github.com/authorizerdev/authorizer/server/utils"
|
||||||
"golang.org/x/crypto/bcrypt"
|
"golang.org/x/crypto/bcrypt"
|
||||||
)
|
)
|
||||||
@@ -25,29 +27,17 @@ func UpdateProfileResolver(ctx context.Context, params model.UpdateProfileInput)
|
|||||||
return res, err
|
return res, err
|
||||||
}
|
}
|
||||||
|
|
||||||
token, err := utils.GetAuthToken(gc)
|
claims, err := token.ValidateAccessToken(gc)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return res, err
|
return res, err
|
||||||
}
|
}
|
||||||
|
|
||||||
claim, err := utils.VerifyAuthToken(token)
|
|
||||||
if err != nil {
|
|
||||||
return res, err
|
|
||||||
}
|
|
||||||
|
|
||||||
id := fmt.Sprintf("%v", claim["id"])
|
|
||||||
sessionToken := session.GetUserSession(id, token)
|
|
||||||
|
|
||||||
if sessionToken == "" {
|
|
||||||
return res, fmt.Errorf(`unauthorized`)
|
|
||||||
}
|
|
||||||
|
|
||||||
// validate if all params are not empty
|
// validate if all params are not empty
|
||||||
if params.GivenName == nil && params.FamilyName == nil && params.Picture == nil && params.MiddleName == nil && params.Nickname == nil && params.OldPassword == nil && params.Email == nil && params.Birthdate == nil && params.Gender == nil && params.PhoneNumber == nil {
|
if params.GivenName == nil && params.FamilyName == nil && params.Picture == nil && params.MiddleName == nil && params.Nickname == nil && params.OldPassword == nil && params.Email == nil && params.Birthdate == nil && params.Gender == nil && params.PhoneNumber == nil {
|
||||||
return res, fmt.Errorf("please enter atleast one param to update")
|
return res, fmt.Errorf("please enter at least one param to update")
|
||||||
}
|
}
|
||||||
|
|
||||||
userEmail := fmt.Sprintf("%v", claim["email"])
|
userEmail := fmt.Sprintf("%v", claims["email"])
|
||||||
user, err := db.Provider.GetUserByEmail(userEmail)
|
user, err := db.Provider.GetUserByEmail(userEmail)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return res, err
|
return res, err
|
||||||
@@ -123,20 +113,21 @@ func UpdateProfileResolver(ctx context.Context, params model.UpdateProfileInput)
|
|||||||
return res, fmt.Errorf("user with this email address already exists")
|
return res, fmt.Errorf("user with this email address already exists")
|
||||||
}
|
}
|
||||||
|
|
||||||
session.DeleteAllUserSession(fmt.Sprintf("%v", user.ID))
|
sessionstore.DeleteAllUserSession(fmt.Sprintf("%v", user.ID))
|
||||||
utils.DeleteCookie(gc)
|
cookie.DeleteCookie(gc)
|
||||||
|
|
||||||
|
hostname := utils.GetHost(gc)
|
||||||
user.Email = newEmail
|
user.Email = newEmail
|
||||||
user.EmailVerifiedAt = nil
|
user.EmailVerifiedAt = nil
|
||||||
hasEmailChanged = true
|
hasEmailChanged = true
|
||||||
// insert verification request
|
// insert verification request
|
||||||
verificationType := constants.VerificationTypeUpdateEmail
|
verificationType := constants.VerificationTypeUpdateEmail
|
||||||
token, err := utils.CreateVerificationToken(newEmail, verificationType)
|
verificationToken, err := token.CreateVerificationToken(newEmail, verificationType, hostname)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Println(`error generating token`, err)
|
log.Println(`error generating token`, err)
|
||||||
}
|
}
|
||||||
db.Provider.AddVerificationRequest(models.VerificationRequest{
|
db.Provider.AddVerificationRequest(models.VerificationRequest{
|
||||||
Token: token,
|
Token: verificationToken,
|
||||||
Identifier: verificationType,
|
Identifier: verificationType,
|
||||||
ExpiresAt: time.Now().Add(time.Minute * 30).Unix(),
|
ExpiresAt: time.Now().Add(time.Minute * 30).Unix(),
|
||||||
Email: newEmail,
|
Email: newEmail,
|
||||||
@@ -144,7 +135,7 @@ func UpdateProfileResolver(ctx context.Context, params model.UpdateProfileInput)
|
|||||||
|
|
||||||
// exec it as go routin so that we can reduce the api latency
|
// exec it as go routin so that we can reduce the api latency
|
||||||
go func() {
|
go func() {
|
||||||
email.SendVerificationMail(newEmail, token)
|
email.SendVerificationMail(newEmail, verificationToken, hostname)
|
||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -8,12 +8,14 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/authorizerdev/authorizer/server/constants"
|
"github.com/authorizerdev/authorizer/server/constants"
|
||||||
|
"github.com/authorizerdev/authorizer/server/cookie"
|
||||||
"github.com/authorizerdev/authorizer/server/db"
|
"github.com/authorizerdev/authorizer/server/db"
|
||||||
"github.com/authorizerdev/authorizer/server/db/models"
|
"github.com/authorizerdev/authorizer/server/db/models"
|
||||||
"github.com/authorizerdev/authorizer/server/email"
|
"github.com/authorizerdev/authorizer/server/email"
|
||||||
"github.com/authorizerdev/authorizer/server/envstore"
|
"github.com/authorizerdev/authorizer/server/envstore"
|
||||||
"github.com/authorizerdev/authorizer/server/graph/model"
|
"github.com/authorizerdev/authorizer/server/graph/model"
|
||||||
"github.com/authorizerdev/authorizer/server/session"
|
"github.com/authorizerdev/authorizer/server/sessionstore"
|
||||||
|
"github.com/authorizerdev/authorizer/server/token"
|
||||||
"github.com/authorizerdev/authorizer/server/utils"
|
"github.com/authorizerdev/authorizer/server/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -26,7 +28,7 @@ func UpdateUserResolver(ctx context.Context, params model.UpdateUserInput) (*mod
|
|||||||
return res, err
|
return res, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if !utils.IsSuperAdmin(gc) {
|
if !token.IsSuperAdmin(gc) {
|
||||||
return res, fmt.Errorf("unauthorized")
|
return res, fmt.Errorf("unauthorized")
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -93,19 +95,20 @@ func UpdateUserResolver(ctx context.Context, params model.UpdateUserInput) (*mod
|
|||||||
return res, fmt.Errorf("user with this email address already exists")
|
return res, fmt.Errorf("user with this email address already exists")
|
||||||
}
|
}
|
||||||
|
|
||||||
session.DeleteAllUserSession(fmt.Sprintf("%v", user.ID))
|
sessionstore.DeleteAllUserSession(fmt.Sprintf("%v", user.ID))
|
||||||
utils.DeleteCookie(gc)
|
cookie.DeleteCookie(gc)
|
||||||
|
|
||||||
|
hostname := utils.GetHost(gc)
|
||||||
user.Email = newEmail
|
user.Email = newEmail
|
||||||
user.EmailVerifiedAt = nil
|
user.EmailVerifiedAt = nil
|
||||||
// insert verification request
|
// insert verification request
|
||||||
verificationType := constants.VerificationTypeUpdateEmail
|
verificationType := constants.VerificationTypeUpdateEmail
|
||||||
token, err := utils.CreateVerificationToken(newEmail, verificationType)
|
verificationToken, err := token.CreateVerificationToken(newEmail, verificationType, hostname)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Println(`error generating token`, err)
|
log.Println(`error generating token`, err)
|
||||||
}
|
}
|
||||||
db.Provider.AddVerificationRequest(models.VerificationRequest{
|
db.Provider.AddVerificationRequest(models.VerificationRequest{
|
||||||
Token: token,
|
Token: verificationToken,
|
||||||
Identifier: verificationType,
|
Identifier: verificationType,
|
||||||
ExpiresAt: time.Now().Add(time.Minute * 30).Unix(),
|
ExpiresAt: time.Now().Add(time.Minute * 30).Unix(),
|
||||||
Email: newEmail,
|
Email: newEmail,
|
||||||
@@ -113,7 +116,7 @@ func UpdateUserResolver(ctx context.Context, params model.UpdateUserInput) (*mod
|
|||||||
|
|
||||||
// exec it as go routin so that we can reduce the api latency
|
// exec it as go routin so that we can reduce the api latency
|
||||||
go func() {
|
go func() {
|
||||||
email.SendVerificationMail(newEmail, token)
|
email.SendVerificationMail(newEmail, verificationToken, hostname)
|
||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -125,7 +128,7 @@ func UpdateUserResolver(ctx context.Context, params model.UpdateUserInput) (*mod
|
|||||||
inputRoles = append(inputRoles, *item)
|
inputRoles = append(inputRoles, *item)
|
||||||
}
|
}
|
||||||
|
|
||||||
if !utils.IsValidRoles(append([]string{}, append(envstore.EnvInMemoryStoreObj.GetSliceStoreEnvVariable(constants.EnvKeyRoles), envstore.EnvInMemoryStoreObj.GetSliceStoreEnvVariable(constants.EnvKeyProtectedRoles)...)...), inputRoles) {
|
if !utils.IsValidRoles(inputRoles, append([]string{}, append(envstore.EnvInMemoryStoreObj.GetSliceStoreEnvVariable(constants.EnvKeyRoles), envstore.EnvInMemoryStoreObj.GetSliceStoreEnvVariable(constants.EnvKeyProtectedRoles)...)...)) {
|
||||||
return res, fmt.Errorf("invalid list of roles")
|
return res, fmt.Errorf("invalid list of roles")
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -133,8 +136,8 @@ func UpdateUserResolver(ctx context.Context, params model.UpdateUserInput) (*mod
|
|||||||
rolesToSave = strings.Join(inputRoles, ",")
|
rolesToSave = strings.Join(inputRoles, ",")
|
||||||
}
|
}
|
||||||
|
|
||||||
session.DeleteAllUserSession(fmt.Sprintf("%v", user.ID))
|
sessionstore.DeleteAllUserSession(fmt.Sprintf("%v", user.ID))
|
||||||
utils.DeleteCookie(gc)
|
cookie.DeleteCookie(gc)
|
||||||
}
|
}
|
||||||
|
|
||||||
if rolesToSave != "" {
|
if rolesToSave != "" {
|
||||||
|
@@ -6,29 +6,27 @@ import (
|
|||||||
|
|
||||||
"github.com/authorizerdev/authorizer/server/db"
|
"github.com/authorizerdev/authorizer/server/db"
|
||||||
"github.com/authorizerdev/authorizer/server/graph/model"
|
"github.com/authorizerdev/authorizer/server/graph/model"
|
||||||
|
"github.com/authorizerdev/authorizer/server/token"
|
||||||
"github.com/authorizerdev/authorizer/server/utils"
|
"github.com/authorizerdev/authorizer/server/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
// UsersResolver is a resolver for users query
|
// UsersResolver is a resolver for users query
|
||||||
// This is admin only query
|
// This is admin only query
|
||||||
func UsersResolver(ctx context.Context) ([]*model.User, error) {
|
func UsersResolver(ctx context.Context, params *model.PaginatedInput) (*model.Users, error) {
|
||||||
gc, err := utils.GinContextFromContext(ctx)
|
gc, err := utils.GinContextFromContext(ctx)
|
||||||
var res []*model.User
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return res, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if !utils.IsSuperAdmin(gc) {
|
if !token.IsSuperAdmin(gc) {
|
||||||
return res, fmt.Errorf("unauthorized")
|
return nil, fmt.Errorf("unauthorized")
|
||||||
}
|
}
|
||||||
|
|
||||||
users, err := db.Provider.ListUsers()
|
pagination := utils.GetPagination(params)
|
||||||
|
|
||||||
|
res, err := db.Provider.ListUsers(pagination)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return res, err
|
return nil, err
|
||||||
}
|
|
||||||
|
|
||||||
for i := 0; i < len(users); i++ {
|
|
||||||
res = append(res, utils.GetResponseUserData(users[i]))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return res, nil
|
return res, nil
|
||||||
|
@@ -6,37 +6,27 @@ import (
|
|||||||
|
|
||||||
"github.com/authorizerdev/authorizer/server/db"
|
"github.com/authorizerdev/authorizer/server/db"
|
||||||
"github.com/authorizerdev/authorizer/server/graph/model"
|
"github.com/authorizerdev/authorizer/server/graph/model"
|
||||||
|
"github.com/authorizerdev/authorizer/server/token"
|
||||||
"github.com/authorizerdev/authorizer/server/utils"
|
"github.com/authorizerdev/authorizer/server/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
// VerificationRequestsResolver is a resolver for verification requests query
|
// VerificationRequestsResolver is a resolver for verification requests query
|
||||||
// This is admin only query
|
// This is admin only query
|
||||||
func VerificationRequestsResolver(ctx context.Context) ([]*model.VerificationRequest, error) {
|
func VerificationRequestsResolver(ctx context.Context, params *model.PaginatedInput) (*model.VerificationRequests, error) {
|
||||||
gc, err := utils.GinContextFromContext(ctx)
|
gc, err := utils.GinContextFromContext(ctx)
|
||||||
var res []*model.VerificationRequest
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return res, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if !utils.IsSuperAdmin(gc) {
|
if !token.IsSuperAdmin(gc) {
|
||||||
return res, fmt.Errorf("unauthorized")
|
return nil, fmt.Errorf("unauthorized")
|
||||||
}
|
}
|
||||||
|
|
||||||
verificationRequests, err := db.Provider.ListVerificationRequests()
|
pagination := utils.GetPagination(params)
|
||||||
|
|
||||||
|
res, err := db.Provider.ListVerificationRequests(pagination)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return res, err
|
return nil, err
|
||||||
}
|
|
||||||
|
|
||||||
for i := 0; i < len(verificationRequests); i++ {
|
|
||||||
res = append(res, &model.VerificationRequest{
|
|
||||||
ID: fmt.Sprintf("%v", verificationRequests[i].ID),
|
|
||||||
Email: &verificationRequests[i].Email,
|
|
||||||
Token: &verificationRequests[i].Token,
|
|
||||||
Identifier: &verificationRequests[i].Identifier,
|
|
||||||
Expires: &verificationRequests[i].ExpiresAt,
|
|
||||||
CreatedAt: &verificationRequests[i].CreatedAt,
|
|
||||||
UpdatedAt: &verificationRequests[i].UpdatedAt,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return res, nil
|
return res, nil
|
||||||
|
@@ -6,10 +6,11 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/authorizerdev/authorizer/server/constants"
|
"github.com/authorizerdev/authorizer/server/cookie"
|
||||||
"github.com/authorizerdev/authorizer/server/db"
|
"github.com/authorizerdev/authorizer/server/db"
|
||||||
"github.com/authorizerdev/authorizer/server/graph/model"
|
"github.com/authorizerdev/authorizer/server/graph/model"
|
||||||
"github.com/authorizerdev/authorizer/server/session"
|
"github.com/authorizerdev/authorizer/server/sessionstore"
|
||||||
|
"github.com/authorizerdev/authorizer/server/token"
|
||||||
"github.com/authorizerdev/authorizer/server/utils"
|
"github.com/authorizerdev/authorizer/server/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -27,7 +28,7 @@ func VerifyEmailResolver(ctx context.Context, params model.VerifyEmailInput) (*m
|
|||||||
}
|
}
|
||||||
|
|
||||||
// verify if token exists in db
|
// verify if token exists in db
|
||||||
claim, err := utils.VerifyVerificationToken(params.Token)
|
claim, err := token.VerifyVerificationToken(params.Token)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return res, fmt.Errorf(`invalid token`)
|
return res, fmt.Errorf(`invalid token`)
|
||||||
}
|
}
|
||||||
@@ -45,20 +46,20 @@ func VerifyEmailResolver(ctx context.Context, params model.VerifyEmailInput) (*m
|
|||||||
db.Provider.DeleteVerificationRequest(verificationRequest)
|
db.Provider.DeleteVerificationRequest(verificationRequest)
|
||||||
|
|
||||||
roles := strings.Split(user.Roles, ",")
|
roles := strings.Split(user.Roles, ",")
|
||||||
refreshToken, _, _ := utils.CreateAuthToken(user, constants.TokenTypeRefreshToken, roles)
|
authToken, err := token.CreateAuthToken(user, roles)
|
||||||
accessToken, expiresAt, _ := utils.CreateAuthToken(user, constants.TokenTypeAccessToken, roles)
|
if err != nil {
|
||||||
|
return res, err
|
||||||
session.SetUserSession(user.ID, accessToken, refreshToken)
|
}
|
||||||
|
sessionstore.SetUserSession(user.ID, authToken.FingerPrint, authToken.RefreshToken.Token)
|
||||||
|
cookie.SetCookie(gc, authToken.AccessToken.Token, authToken.RefreshToken.Token, authToken.FingerPrintHash)
|
||||||
utils.SaveSessionInDB(user.ID, gc)
|
utils.SaveSessionInDB(user.ID, gc)
|
||||||
|
|
||||||
res = &model.AuthResponse{
|
res = &model.AuthResponse{
|
||||||
Message: `Email verified successfully.`,
|
Message: `Email verified successfully.`,
|
||||||
AccessToken: &accessToken,
|
AccessToken: &authToken.AccessToken.Token,
|
||||||
ExpiresAt: &expiresAt,
|
ExpiresAt: &authToken.AccessToken.ExpiresAt,
|
||||||
User: utils.GetResponseUserData(user),
|
User: user.AsAPIUser(),
|
||||||
}
|
}
|
||||||
|
|
||||||
utils.SetCookie(gc, accessToken)
|
|
||||||
|
|
||||||
return res, nil
|
return res, nil
|
||||||
}
|
}
|
||||||
|
@@ -1,18 +1,15 @@
|
|||||||
package routes
|
package routes
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/authorizerdev/authorizer/server/constants"
|
|
||||||
"github.com/authorizerdev/authorizer/server/envstore"
|
|
||||||
"github.com/authorizerdev/authorizer/server/handlers"
|
"github.com/authorizerdev/authorizer/server/handlers"
|
||||||
"github.com/authorizerdev/authorizer/server/middlewares"
|
"github.com/authorizerdev/authorizer/server/middlewares"
|
||||||
"github.com/gin-contrib/location"
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
)
|
)
|
||||||
|
|
||||||
// InitRouter initializes gin router
|
// InitRouter initializes gin router
|
||||||
func InitRouter() *gin.Engine {
|
func InitRouter() *gin.Engine {
|
||||||
router := gin.Default()
|
router := gin.Default()
|
||||||
router.Use(location.Default())
|
// router.Use(location.Default())
|
||||||
router.Use(middlewares.GinContextToContextMiddleware())
|
router.Use(middlewares.GinContextToContextMiddleware())
|
||||||
router.Use(middlewares.CORSMiddleware())
|
router.Use(middlewares.CORSMiddleware())
|
||||||
|
|
||||||
@@ -25,21 +22,21 @@ func InitRouter() *gin.Engine {
|
|||||||
|
|
||||||
router.LoadHTMLGlob("templates/*")
|
router.LoadHTMLGlob("templates/*")
|
||||||
// login page app related routes.
|
// login page app related routes.
|
||||||
if !envstore.EnvInMemoryStoreObj.GetBoolStoreEnvVariable(constants.EnvKeyDisableLoginPage) {
|
app := router.Group("/app")
|
||||||
app := router.Group("/app")
|
{
|
||||||
{
|
app.Static("/favicon_io", "app/favicon_io")
|
||||||
app.Static("/build", "app/build")
|
app.Static("/build", "app/build")
|
||||||
app.GET("/", handlers.AppHandler())
|
app.GET("/", handlers.AppHandler())
|
||||||
app.GET("/reset-password", handlers.AppHandler())
|
app.GET("/:page", handlers.AppHandler())
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// dashboard related routes
|
// dashboard related routes
|
||||||
app := router.Group("/dashboard")
|
dashboard := router.Group("/dashboard")
|
||||||
{
|
{
|
||||||
app.Static("/build", "dashboard/build")
|
dashboard.Static("/favicon_io", "dashboard/favicon_io")
|
||||||
app.GET("/", handlers.DashboardHandler())
|
dashboard.Static("/build", "dashboard/build")
|
||||||
app.GET("/:page", handlers.DashboardHandler())
|
dashboard.GET("/", handlers.DashboardHandler())
|
||||||
|
dashboard.GET("/:page", handlers.DashboardHandler())
|
||||||
}
|
}
|
||||||
return router
|
return router
|
||||||
}
|
}
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
package session
|
package sessionstore
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"sync"
|
"sync"
|
||||||
@@ -69,6 +69,19 @@ func (c *InMemoryStore) GetUserSession(userId, accessToken string) string {
|
|||||||
return token
|
return token
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetUserSessions returns all the user session token from the in-memory store.
|
||||||
|
func (c *InMemoryStore) GetUserSessions(userId string) map[string]string {
|
||||||
|
// c.mutex.Lock()
|
||||||
|
// defer c.mutex.Unlock()
|
||||||
|
|
||||||
|
sessionMap, ok := c.store[userId]
|
||||||
|
if !ok {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return sessionMap
|
||||||
|
}
|
||||||
|
|
||||||
// SetSocialLoginState sets the social login state in the in-memory store.
|
// SetSocialLoginState sets the social login state in the in-memory store.
|
||||||
func (c *InMemoryStore) SetSocialLoginState(key, state string) {
|
func (c *InMemoryStore) SetSocialLoginState(key, state string) {
|
||||||
c.mutex.Lock()
|
c.mutex.Lock()
|
@@ -1,4 +1,4 @@
|
|||||||
package session
|
package sessionstore
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
@@ -60,6 +60,16 @@ func (c *RedisStore) GetUserSession(userId, accessToken string) string {
|
|||||||
return token
|
return token
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetUserSessions returns all the user session token from the redis store.
|
||||||
|
func (c *RedisStore) GetUserSessions(userID string) map[string]string {
|
||||||
|
res, err := c.store.HGetAll(c.ctx, "authorizer_"+userID).Result()
|
||||||
|
if err != nil {
|
||||||
|
log.Println("error getting token from redis store:", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
// SetSocialLoginState sets the social login state in redis store.
|
// SetSocialLoginState sets the social login state in redis store.
|
||||||
func (c *RedisStore) SetSocialLoginState(key, state string) {
|
func (c *RedisStore) SetSocialLoginState(key, state string) {
|
||||||
err := c.store.Set(c.ctx, key, state, 0).Err()
|
err := c.store.Set(c.ctx, key, state, 0).Err()
|
@@ -1,4 +1,4 @@
|
|||||||
package session
|
package sessionstore
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
@@ -22,22 +22,22 @@ type SessionStore struct {
|
|||||||
var SessionStoreObj SessionStore
|
var SessionStoreObj SessionStore
|
||||||
|
|
||||||
// SetUserSession sets the user session in the session store
|
// SetUserSession sets the user session in the session store
|
||||||
func SetUserSession(userId, accessToken, refreshToken string) {
|
func SetUserSession(userId, fingerprint, refreshToken string) {
|
||||||
if SessionStoreObj.RedisMemoryStoreObj != nil {
|
if SessionStoreObj.RedisMemoryStoreObj != nil {
|
||||||
SessionStoreObj.RedisMemoryStoreObj.AddUserSession(userId, accessToken, refreshToken)
|
SessionStoreObj.RedisMemoryStoreObj.AddUserSession(userId, fingerprint, refreshToken)
|
||||||
}
|
}
|
||||||
if SessionStoreObj.InMemoryStoreObj != nil {
|
if SessionStoreObj.InMemoryStoreObj != nil {
|
||||||
SessionStoreObj.InMemoryStoreObj.AddUserSession(userId, accessToken, refreshToken)
|
SessionStoreObj.InMemoryStoreObj.AddUserSession(userId, fingerprint, refreshToken)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// DeleteUserSession deletes the particular user session from the session store
|
// DeleteUserSession deletes the particular user session from the session store
|
||||||
func DeleteUserSession(userId, accessToken string) {
|
func DeleteUserSession(userId, fingerprint string) {
|
||||||
if SessionStoreObj.RedisMemoryStoreObj != nil {
|
if SessionStoreObj.RedisMemoryStoreObj != nil {
|
||||||
SessionStoreObj.RedisMemoryStoreObj.DeleteUserSession(userId, accessToken)
|
SessionStoreObj.RedisMemoryStoreObj.DeleteUserSession(userId, fingerprint)
|
||||||
}
|
}
|
||||||
if SessionStoreObj.InMemoryStoreObj != nil {
|
if SessionStoreObj.InMemoryStoreObj != nil {
|
||||||
SessionStoreObj.InMemoryStoreObj.DeleteUserSession(userId, accessToken)
|
SessionStoreObj.InMemoryStoreObj.DeleteUserSession(userId, fingerprint)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -52,17 +52,29 @@ func DeleteAllUserSession(userId string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// GetUserSession returns the user session from the session store
|
// GetUserSession returns the user session from the session store
|
||||||
func GetUserSession(userId, accessToken string) string {
|
func GetUserSession(userId, fingerprint string) string {
|
||||||
if SessionStoreObj.RedisMemoryStoreObj != nil {
|
if SessionStoreObj.RedisMemoryStoreObj != nil {
|
||||||
return SessionStoreObj.RedisMemoryStoreObj.GetUserSession(userId, accessToken)
|
return SessionStoreObj.RedisMemoryStoreObj.GetUserSession(userId, fingerprint)
|
||||||
}
|
}
|
||||||
if SessionStoreObj.InMemoryStoreObj != nil {
|
if SessionStoreObj.InMemoryStoreObj != nil {
|
||||||
return SessionStoreObj.InMemoryStoreObj.GetUserSession(userId, accessToken)
|
return SessionStoreObj.InMemoryStoreObj.GetUserSession(userId, fingerprint)
|
||||||
}
|
}
|
||||||
|
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetUserSessions returns all the user sessions from the session store
|
||||||
|
func GetUserSessions(userId string) map[string]string {
|
||||||
|
if SessionStoreObj.RedisMemoryStoreObj != nil {
|
||||||
|
return SessionStoreObj.RedisMemoryStoreObj.GetUserSessions(userId)
|
||||||
|
}
|
||||||
|
if SessionStoreObj.InMemoryStoreObj != nil {
|
||||||
|
return SessionStoreObj.InMemoryStoreObj.GetUserSessions(userId)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// ClearStore clears the session store for authorizer tokens
|
// ClearStore clears the session store for authorizer tokens
|
||||||
func ClearStore() {
|
func ClearStore() {
|
||||||
if SessionStoreObj.RedisMemoryStoreObj != nil {
|
if SessionStoreObj.RedisMemoryStoreObj != nil {
|
@@ -7,13 +7,12 @@ import (
|
|||||||
"github.com/authorizerdev/authorizer/server/envstore"
|
"github.com/authorizerdev/authorizer/server/envstore"
|
||||||
"github.com/authorizerdev/authorizer/server/graph/model"
|
"github.com/authorizerdev/authorizer/server/graph/model"
|
||||||
"github.com/authorizerdev/authorizer/server/resolvers"
|
"github.com/authorizerdev/authorizer/server/resolvers"
|
||||||
"github.com/google/uuid"
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
)
|
)
|
||||||
|
|
||||||
func adminSignupTests(t *testing.T, s TestSetup) {
|
func adminSignupTests(t *testing.T, s TestSetup) {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
t.Run(`should complete admin login`, func(t *testing.T) {
|
t.Run(`should complete admin signup`, func(t *testing.T) {
|
||||||
_, ctx := createContext(s)
|
_, ctx := createContext(s)
|
||||||
_, err := resolvers.AdminSignupResolver(ctx, model.AdminSignupInput{
|
_, err := resolvers.AdminSignupResolver(ctx, model.AdminSignupInput{
|
||||||
AdminSecret: "admin",
|
AdminSecret: "admin",
|
||||||
@@ -24,7 +23,7 @@ func adminSignupTests(t *testing.T, s TestSetup) {
|
|||||||
envstore.EnvInMemoryStoreObj.UpdateEnvVariable(constants.StringStoreIdentifier, constants.EnvKeyAdminSecret, "")
|
envstore.EnvInMemoryStoreObj.UpdateEnvVariable(constants.StringStoreIdentifier, constants.EnvKeyAdminSecret, "")
|
||||||
|
|
||||||
_, err = resolvers.AdminSignupResolver(ctx, model.AdminSignupInput{
|
_, err = resolvers.AdminSignupResolver(ctx, model.AdminSignupInput{
|
||||||
AdminSecret: uuid.New().String(),
|
AdminSecret: "admin123",
|
||||||
})
|
})
|
||||||
|
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
|
@@ -14,16 +14,13 @@ func TestEnvs(t *testing.T) {
|
|||||||
env.InitEnv()
|
env.InitEnv()
|
||||||
store := envstore.EnvInMemoryStoreObj.GetEnvStoreClone()
|
store := envstore.EnvInMemoryStoreObj.GetEnvStoreClone()
|
||||||
|
|
||||||
assert.Equal(t, store.StringEnv[constants.EnvKeyAdminSecret], "admin")
|
|
||||||
assert.Equal(t, store.StringEnv[constants.EnvKeyEnv], "production")
|
assert.Equal(t, store.StringEnv[constants.EnvKeyEnv], "production")
|
||||||
assert.False(t, store.BoolEnv[constants.EnvKeyDisableEmailVerification])
|
assert.False(t, store.BoolEnv[constants.EnvKeyDisableEmailVerification])
|
||||||
assert.False(t, store.BoolEnv[constants.EnvKeyDisableMagicLinkLogin])
|
assert.False(t, store.BoolEnv[constants.EnvKeyDisableMagicLinkLogin])
|
||||||
assert.False(t, store.BoolEnv[constants.EnvKeyDisableBasicAuthentication])
|
assert.False(t, store.BoolEnv[constants.EnvKeyDisableBasicAuthentication])
|
||||||
assert.Equal(t, store.StringEnv[constants.EnvKeyJwtType], "HS256")
|
assert.Equal(t, store.StringEnv[constants.EnvKeyJwtType], "HS256")
|
||||||
assert.Equal(t, store.StringEnv[constants.EnvKeyJwtSecret], "random_string")
|
|
||||||
assert.Equal(t, store.StringEnv[constants.EnvKeyJwtRoleClaim], "role")
|
assert.Equal(t, store.StringEnv[constants.EnvKeyJwtRoleClaim], "role")
|
||||||
assert.EqualValues(t, store.SliceEnv[constants.EnvKeyRoles], []string{"user"})
|
assert.EqualValues(t, store.SliceEnv[constants.EnvKeyRoles], []string{"user"})
|
||||||
assert.EqualValues(t, store.SliceEnv[constants.EnvKeyDefaultRoles], []string{"user"})
|
assert.EqualValues(t, store.SliceEnv[constants.EnvKeyDefaultRoles], []string{"user"})
|
||||||
assert.EqualValues(t, store.SliceEnv[constants.EnvKeyProtectedRoles], []string{"admin"})
|
|
||||||
assert.EqualValues(t, store.SliceEnv[constants.EnvKeyAllowedOrigins], []string{"*"})
|
assert.EqualValues(t, store.SliceEnv[constants.EnvKeyAllowedOrigins], []string{"*"})
|
||||||
}
|
}
|
||||||
|
38
server/test/is_valid_jwt_test.go
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
package test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/authorizerdev/authorizer/server/db/models"
|
||||||
|
"github.com/authorizerdev/authorizer/server/graph/model"
|
||||||
|
"github.com/authorizerdev/authorizer/server/resolvers"
|
||||||
|
"github.com/authorizerdev/authorizer/server/token"
|
||||||
|
"github.com/google/uuid"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func isValidJWTTests(t *testing.T, s TestSetup) {
|
||||||
|
t.Helper()
|
||||||
|
_, ctx := createContext(s)
|
||||||
|
expiredToken := "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhbGxvd2VkX3JvbGVzIjpbIiJdLCJiaXJ0aGRhdGUiOm51bGwsImNyZWF0ZWRfYXQiOjAsImVtYWlsIjoiam9obi5kb2VAZ21haWwuY29tIiwiZW1haWxfdmVyaWZpZWQiOmZhbHNlLCJleHAiOjE2NDI5NjEwMTEsImV4dHJhIjp7IngtZXh0cmEtaWQiOiJkMmNhMjQwNy05MzZmLTQwYzQtOTQ2NS05Y2M5MWYxZTJhNDQifSwiZmFtaWx5X25hbWUiOm51bGwsImdlbmRlciI6bnVsbCwiZ2l2ZW5fbmFtZSI6bnVsbCwiaWF0IjoxNjQyOTYwOTgxLCJpZCI6ImQyY2EyNDA3LTkzNmYtNDBjNC05NDY1LTljYzkxZjFlMmE0NCIsIm1pZGRsZV9uYW1lIjpudWxsLCJuaWNrbmFtZSI6bnVsbCwicGhvbmVfbnVtYmVyIjpudWxsLCJwaG9uZV9udW1iZXJfdmVyaWZpZWQiOmZhbHNlLCJwaWN0dXJlIjpudWxsLCJwcmVmZXJyZWRfdXNlcm5hbWUiOiJqb2huLmRvZUBnbWFpbC5jb20iLCJyb2xlIjpbXSwic2lnbnVwX21ldGhvZHMiOiIiLCJ0b2tlbl90eXBlIjoiYWNjZXNzX3Rva2VuIiwidXBkYXRlZF9hdCI6MH0.FrdyeOC5e8uU1SowGj0omFJuwRnh4BrEk89S_fbEkzs"
|
||||||
|
|
||||||
|
t.Run(`should fail for invalid jwt`, func(t *testing.T) {
|
||||||
|
_, err := resolvers.IsValidJwtResolver(ctx, &model.IsValidJWTQueryInput{
|
||||||
|
Jwt: &expiredToken,
|
||||||
|
})
|
||||||
|
assert.NotNil(t, err)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run(`should pass with valid jwt`, func(t *testing.T) {
|
||||||
|
authToken, err := token.CreateAuthToken(models.User{
|
||||||
|
ID: uuid.New().String(),
|
||||||
|
Email: "john.doe@gmail.com",
|
||||||
|
}, []string{})
|
||||||
|
assert.Nil(t, err)
|
||||||
|
res, err := resolvers.IsValidJwtResolver(ctx, &model.IsValidJWTQueryInput{
|
||||||
|
Jwt: &authToken.AccessToken.Token,
|
||||||
|
})
|
||||||
|
assert.Nil(t, err)
|
||||||
|
assert.True(t, res.Valid)
|
||||||
|
})
|
||||||
|
}
|
@@ -2,6 +2,7 @@ package test
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"net/url"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/authorizerdev/authorizer/server/constants"
|
"github.com/authorizerdev/authorizer/server/constants"
|
||||||
@@ -9,6 +10,8 @@ import (
|
|||||||
"github.com/authorizerdev/authorizer/server/envstore"
|
"github.com/authorizerdev/authorizer/server/envstore"
|
||||||
"github.com/authorizerdev/authorizer/server/graph/model"
|
"github.com/authorizerdev/authorizer/server/graph/model"
|
||||||
"github.com/authorizerdev/authorizer/server/resolvers"
|
"github.com/authorizerdev/authorizer/server/resolvers"
|
||||||
|
"github.com/authorizerdev/authorizer/server/sessionstore"
|
||||||
|
"github.com/authorizerdev/authorizer/server/utils"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -27,12 +30,22 @@ func logoutTests(t *testing.T, s TestSetup) {
|
|||||||
Token: verificationRequest.Token,
|
Token: verificationRequest.Token,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
sessions := sessionstore.GetUserSessions(verifyRes.User.ID)
|
||||||
|
fingerPrint := ""
|
||||||
|
refreshToken := ""
|
||||||
|
for key, val := range sessions {
|
||||||
|
fingerPrint = key
|
||||||
|
refreshToken = val
|
||||||
|
}
|
||||||
|
|
||||||
|
fingerPrintHash, _ := utils.EncryptAES([]byte(fingerPrint))
|
||||||
|
|
||||||
token := *verifyRes.AccessToken
|
token := *verifyRes.AccessToken
|
||||||
req.Header.Set("Cookie", fmt.Sprintf("%s=%s", envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyCookieName), token))
|
cookie := fmt.Sprintf("%s=%s;%s=%s;%s=%s", envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyCookieName)+".fingerprint", url.QueryEscape(string(fingerPrintHash)), envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyCookieName)+".refresh_token", refreshToken, envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyCookieName)+".access_token", token)
|
||||||
|
|
||||||
|
req.Header.Set("Cookie", cookie)
|
||||||
_, err = resolvers.LogoutResolver(ctx)
|
_, err = resolvers.LogoutResolver(ctx)
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
_, err = resolvers.ProfileResolver(ctx)
|
|
||||||
assert.NotNil(t, err, "unauthorized")
|
|
||||||
cleanData(email)
|
cleanData(email)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|