Compare commits

..

36 Commits

Author SHA1 Message Date
Lakhan Samani
260f533b41 fix: resolves #115 2022-02-05 10:09:25 +05:30
Lakhan Samani
c09ca3b810 fix: improve messaging on dashboard 2022-02-05 09:15:36 +05:30
Lakhan Samani
f07fb50eff Merge branch 'main' of https://github.com/authorizerdev/authorizer 2022-02-05 09:01:22 +05:30
Lakhan Samani
ea62a20c80 fix: remove test env 2022-02-05 09:01:12 +05:30
Lakhan Samani
2bb0ded20e fix: error log and gin mode 2022-02-05 09:00:56 +05:30
Lakhan Samani
fec43f55f2 chore: update project setup info 2022-02-02 13:18:26 +05:30
Lakhan Samani
271e901398 chore: update project setup info 2022-02-02 13:17:35 +05:30
Lakhan Samani
c6f01ce839 chore: update project setup info in contributing 2022-02-02 13:15:54 +05:30
Lakhan Samani
2dd404c02c chore: update project setup info 2022-02-02 13:15:14 +05:30
Lakhan Samani
2f29bbcee4 fix: oauth callback update user 2022-02-02 12:30:11 +05:30
Lakhan Samani
63a8c82535 chore: update package-lock.json 2022-02-02 11:41:36 +05:30
Lakhan Samani
1f81e45e79 fix: user dashboard created_at + add signup_methods & roles 2022-02-02 11:39:08 +05:30
Lakhan Samani
40dcf67de9 fix: remove logs 2022-01-31 14:53:17 +05:30
Lakhan Samani
32d8b7c038 Merge branch 'main' of https://github.com/authorizerdev/authorizer 2022-01-31 14:30:54 +05:30
Lakhan Samani
2a91f3e7d8 Merge pull request #113 from anik-ghosh-au7/fix/dashboard
Fix/dashboard
2022-01-31 14:30:32 +05:30
Lakhan Samani
36d9861517 Fix getting host 2022-01-31 14:30:13 +05:30
Anik Ghosh
d577a21a9a user dashboard pagination bug removed 2022-01-31 14:26:19 +05:30
Anik Ghosh
115607cb6b Merge branch 'main' of https://github.com/authorizerdev/authorizer into fix/dashboard 2022-01-31 13:29:38 +05:30
Anik Ghosh
adb969ec04 add delete user modal 2022-01-31 13:23:38 +05:30
Lakhan Samani
4e48320cf1 fix: bug with authorizer url 2022-01-31 11:35:24 +05:30
Anik Ghosh
cfe035e96b loader added to user dashboard 2022-01-31 11:33:35 +05:30
Lakhan Samani
34a91f3195 Add log for host url 2022-01-30 23:44:37 +05:30
Lakhan Samani
ea14cc1743 Merge pull request #111 from anik-ghosh-au7/feat/manage-users
Feat/manage users
2022-01-30 23:43:21 +05:30
Anik Ghosh
9d7f5fd9db array input type update 2022-01-30 17:43:38 +05:30
Anik Ghosh
0520056e43 sync with main branch 2022-01-30 10:56:56 +05:30
Anik Ghosh
1821e27692 Merge branch 'main' of https://github.com/authorizerdev/authorizer into feat/manage-users 2022-01-30 10:52:35 +05:30
Anik Ghosh
388530a69c edit user details modal added 2022-01-30 10:39:35 +05:30
Lakhan Samani
1e32d790b3 Remove unsed code 2022-01-30 00:08:51 +05:30
Anik Ghosh
681ffc65f1 user list added 2022-01-29 20:53:53 +05:30
Lakhan Samani
6331ec7b7a Resolves #110 2022-01-29 17:03:21 +05:30
Lakhan Samani
25c9ce03bd fix: default logo 2022-01-27 09:55:05 +05:30
Lakhan Samani
ac416bfc7b fix(dashboard): mutation 2022-01-25 13:06:52 +05:30
Lakhan Samani
0049e1380b Merge pull request #108 from authorizerdev/feat/pagination
feat: add pagination for users & verification_requests
2022-01-25 10:58:42 +05:30
Lakhan Samani
9bd185a9c6 feat: add pagination for users & verification_requests 2022-01-25 10:57:40 +05:30
Lakhan Samani
82bc38923c Feat/manage env vars (#107)
* ui to manage env variables added

* ui to manage env variables updated

* ui to manage env variables updated

* ui to manage env variables updated

* ui to manage env variables updated

* ui updates

* ui updates

* ui updates

* ui updates

* env vars input field updated

* env vars input field updated

Co-authored-by: Anik Ghosh <tech.anikghosh@gmail.com>
2022-01-25 09:34:35 +05:30
Lakhan Samani
8df8010b22 Fix SMTP fields for update env mutation 2022-01-24 21:16:29 +05:30
106 changed files with 6539 additions and 797 deletions

View File

@@ -1,16 +1,2 @@
ENV=production
DATABASE_URL=data.db
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;}

View File

@@ -43,12 +43,14 @@ Please ask as many questions as you need, either directly in the issue or on [Di
### Project Setup for Authorizer core
1. Fork the [authorizer](https://github.com/authorizerdev/authorizer) repository (**Skip this step if you have access to repo**)
2. `git clone https://github.com/authorizerdev/authorizer.git`
3. `cd authorizer`
4. `cp .env.sample .env`. Check all the supported env [here](https://docs.authorizer.dev/core/env/)
5. Build the code `make clean && make`
2. Clone repo: `git clone https://github.com/authorizerdev/authorizer.git` or use the forked url from step 1
3. Change directory to authorizer: `cd authorizer`
5. Create Env file `cp .env.sample .env`. Check all the supported env [here](https://docs.authorizer.dev/core/env/)
6. Build Dashboard `make build-dashboard`
7. Build App `make build-app`
8. Build Server `make clean && make`
> Note: if you don't have [`make`](https://www.ibm.com/docs/en/aix/7.2?topic=concepts-make-command), you can `cd` into `server` dir and build using the `go build` command
6. Run binary `./build/server`
9. Run binary `./build/server`
### Testing

View File

@@ -24,7 +24,9 @@ FROM alpine:latest
WORKDIR /root/
RUN mkdir app dashboard
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/favicon_io dashboard/favicon_io
COPY --from=go-builder /authorizer/build build
COPY templates templates
EXPOSE 8080

View File

@@ -1,6 +1,6 @@
<p align="center">
<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>
</p>
<h1 align="center">
@@ -78,12 +78,14 @@ This guide helps you practice using Authorizer to evaluate it before you use it
### Project Setup
1. Fork the [authorizer](https://github.com/authorizerdev/authorizer) repository (**Skip this step if you have access to repo**)
2. `git clone https://github.com/authorizerdev/authorizer.git`
3. `cd authorizer`
4. `cp .env.sample .env`. Check all the supported env [here](https://docs.authorizer.dev/core/env/)
5. Build the code `make clean && make`
2. Clone repo: `git clone https://github.com/authorizerdev/authorizer.git` or use the forked url from step 1
3. Change directory to authorizer: `cd authorizer`
5. Create Env file `cp .env.sample .env`. Check all the supported env [here](https://docs.authorizer.dev/core/env/)
6. Build Dashboard `make build-dashboard`
7. Build App `make build-app`
8. Build Server `make clean && make`
> Note: if you don't have [`make`](https://www.ibm.com/docs/en/aix/7.2?topic=concepts-make-command), you can `cd` into `server` dir and build using the `go build` command
6. Run binary `./build/server`
9. Run binary `./build/server`
## Install using binaries
@@ -129,18 +131,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`
## 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
<br/><br/>
[![Deploy to Heroku](https://www.herokucdn.com/deploy/button.svg)](https://heroku.com/deploy?template=https://github.com/authorizerdev/authorizer-heroku)
# Install instance on railway
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/>
[![Deploy on Railway](https://railway.app/button.svg)](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)
| **Infra provider** | **One-click link** | **Additional information** |
| :----------------: | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :----------------------------------------------------: |
| Railway.app | <a href="https://railway.app/new/template?template=https://github.com/authorizerdev/authorizer-railway&amp;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) |
| Render | [![Deploy to Render](https://render.com/images/deploy-to-render-button.svg)](https://render.com/deploy?repo=https://github.com/authorizerdev/authorizer-render) | [docs](https://docs.authorizer.dev/deployment/render) |
### Things to consider

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 77 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 528 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

BIN
app/favicon_io/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

826
app/package-lock.json generated
View File

@@ -1,8 +1,832 @@
{
"name": "app",
"version": "1.0.0",
"lockfileVersion": 1,
"lockfileVersion": 2,
"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": {
"@authorizerdev/authorizer-js": {
"version": "0.2.1",

View File

@@ -15,7 +15,7 @@ export default function Dashboard() {
return (
<div>
<h1>Hey 👋,</h1>
<p>Thank you for joining authorizer demo app.</p>
<p>Thank you for using authorizer.</p>
<p>
Your email address is{' '}
<a href={`mailto:${user?.email}`} style={{ color: '#3B82F6' }}>

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 77 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 528 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

File diff suppressed because it is too large Load Diff

View File

@@ -17,9 +17,11 @@
"@types/react": "^17.0.38",
"@types/react-dom": "^17.0.11",
"@types/react-router-dom": "^5.3.2",
"dayjs": "^1.10.7",
"esbuild": "^0.14.9",
"framer-motion": "^5.5.5",
"graphql": "^16.2.0",
"lodash": "^4.17.21",
"react": "^17.0.2",
"react-dom": "^17.0.2",
"react-icons": "^4.3.1",

View File

@@ -12,6 +12,7 @@ const queryClient = createClient({
credentials: 'include',
};
},
requestPolicy: 'network-only',
});
const theme = extendTheme({

View 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;

View 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;

View 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;

View File

@@ -1,7 +1,6 @@
import React, { ReactNode } from 'react';
import {
IconButton,
Avatar,
Box,
CloseButton,
Flex,
@@ -21,9 +20,7 @@ import {
} from '@chakra-ui/react';
import {
FiHome,
FiTrendingUp,
FiCompass,
FiStar,
FiCode,
FiSettings,
FiMenu,
FiUser,
@@ -43,9 +40,9 @@ interface LinkItemProps {
route: string;
}
const LinkItems: Array<LinkItemProps> = [
{ name: 'Home', icon: FiHome, route: '/' },
// { name: 'Home', icon: FiHome, route: '/' },
{ name: 'Environment Variables', icon: FiSettings, route: '/' },
{ name: 'Users', icon: FiUsers, route: '/users' },
{ name: 'Environment Variables', icon: FiSettings, route: '/environment' },
];
interface SidebarProps extends BoxProps {
@@ -90,6 +87,17 @@ export const Sidebar = ({ onClose, ...rest }: SidebarProps) => {
</NavItem>
</NavLink>
))}
<Link
href="/playground"
target="_blank"
style={{
textDecoration: 'none',
}}
_focus={{ _boxShadow: 'none' }}
>
<NavItem icon={FiCode}>API Playground</NavItem>
</Link>
</Box>
);
};
@@ -100,11 +108,6 @@ interface NavItemProps extends FlexProps {
}
export const NavItem = ({ icon, children, ...rest }: NavItemProps) => {
return (
<Link
href="#"
style={{ textDecoration: 'none' }}
_focus={{ boxShadow: 'none' }}
>
<Flex
align="center"
p="3"
@@ -130,7 +133,6 @@ export const NavItem = ({ icon, children, ...rest }: NavItemProps) => {
)}
{children}
</Flex>
</Link>
);
};
@@ -161,6 +163,7 @@ export const MobileNav = ({ onOpen, ...rest }: MobileProps) => {
borderBottomWidth="1px"
borderBottomColor={useColorModeValue('gray.200', 'gray.700')}
justifyContent={{ base: 'space-between', md: 'flex-end' }}
zIndex={99}
{...rest}
>
<IconButton

View File

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

View File

@@ -32,7 +32,7 @@ export const AuthContextProvider = ({ children }: { children: any }) => {
if (fetching) {
return (
<Center>
<Center h="100%">
<Spinner />
</Center>
);

View File

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

View File

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

View File

@@ -73,17 +73,25 @@ export default function Auth() {
fontWeight="bold"
mb="2"
>
Hi there 👋 <br />
Hello Admin 👋 <br />
</Text>
<Text fontSize="large" textAlign="center" color="gray.500" mb="8">
Welcome to Authorizer Administrative Dashboard
Welcome to Admin Dashboard
</Text>
<form onSubmit={handleSubmit}>
<VStack spacing="5" justify="space-between">
<FormControl isRequired>
{/* <FormLabel htmlFor="admin-secret">
{isLogin ? 'Enter' : 'Configure'} Admin Secret
</FormLabel> */}
<FormLabel htmlFor="admin-username">Username</FormLabel>
<Input
size="lg"
id="admin-username"
placeholder="Username"
disabled
value="admin"
/>
</FormControl>
<FormControl isRequired>
<FormLabel htmlFor="admin-secret">Password</FormLabel>
<Input
size="lg"
id="admin-secret"
@@ -111,10 +119,7 @@ export default function Auth() {
</Text>
) : (
<Text color="gray.600" fontSize="sm">
<b>Note:</b> You can also configure admin secret by setting{' '}
<code>ADMIN_SECRET</code> environment variable. For more
information, please refer to the{' '}
<a href="https://docs.authorizer.dev/core/env/">documentation</a>.
<b>Note:</b> Configure the password to start using your dashboard.
</Text>
)}
</VStack>

View File

@@ -1,35 +1,773 @@
import { Box, Divider, Flex } from '@chakra-ui/react';
import React from 'react';
import React, { useEffect } 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() {
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 (
<Box m="5" p="5" bg="white" rounded="md">
<h1>Social Media Logins</h1>
<Divider />- Add horizontal input for clientID and secret for - Google -
Github - Facebook
<h1>Roles</h1>
<Divider />- Add tagged input for roles, default roles, and protected
roles
<h1>JWT Configurations</h1>
<Divider />- Add input for JWT Type (keep this disabled for now with
notice saying, "More JWT types will be enabled in upcoming releases"),JWT
secret, JWT role claim
<h1>Session Storage</h1>
<Divider />- Add input for redis url
<h1>Email Configurations</h1>
<Divider />- Add input for SMTP Host, PORT, Username, Password, From
Email,
<h1>White Listing</h1>
<Divider />- Add input for allowed origins
<h1>Organization Information</h1>
<Divider />- Add input for organization name, and logo
<h1>Custom Scripts</h1>
<Divider />- For now add text area input for CUSTOM_ACCESS_TOKEN_SCRIPT
<h1>Disable Features</h1>
<Divider />
<h1>Danger</h1>
<Divider />- Include changing admin secret
<Box m="5" py="5" px="10" bg="white" rounded="md">
<Text fontSize="md" paddingTop="2%" fontWeight="bold">
Social Media Logins
</Text>
<Stack spacing={6} padding="2% 0%">
<Flex>
<Center
w="50px"
marginRight="1.5%"
border="1px solid #e2e8f0"
borderRadius="5px"
>
<FaGoogle style={{ color: '#8c8c8c' }} />
</Center>
<Center w="45%" marginRight="1.5%">
<InputField
variables={envVariables}
setVariables={setEnvVariables}
inputType={TextInputType.GOOGLE_CLIENT_ID}
placeholder="Google Client ID"
/>
</Center>
<Center w="45%">
<InputField
variables={envVariables}
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>
);
}

View File

@@ -1,6 +1,400 @@
import { Box } from '@chakra-ui/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() {
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>
);
}

View File

@@ -23,9 +23,10 @@ export const AppRoutes = () => {
</DashboardLayout>
}
>
<Route path="/" element={<Home />} />
<Route path="/" element={<Environment />} />
<Route path="users" element={<Users />} />
<Route path="environment" element={<Environment />} />
<Route path="*" element={<Home />} />
</Route>
</Routes>
</Suspense>
@@ -35,6 +36,7 @@ export const AppRoutes = () => {
<Suspense fallback={<></>}>
<Routes>
<Route path="/" element={<Auth />} />
<Route path="*" element={<Auth />} />
</Routes>
</Suspense>
);

View File

@@ -1,6 +1,66 @@
import _ from 'lodash';
export const hasAdminSecret = () => {
return (<any>window)['__authorizer__'].isOnboardingCompleted === true;
};
export const capitalizeFirstLetter = (data: string): string =>
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;
};

View File

@@ -67,6 +67,7 @@
/* Advanced Options */
"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"]
}
}

View File

@@ -13,6 +13,4 @@ const (
DbTypeArangodb = "arangodb"
// DbTypeMongodb is the mongodb database type
DbTypeMongodb = "mongodb"
// DbTypeFaunadb is the faunadb database type
DbTypeFaunadb = "faunadb"
)

View File

@@ -16,6 +16,7 @@ const (
// EnvKeyVersion key for build arg version
EnvKeyVersion = "VERSION"
// EnvKeyAuthorizerURL key for env variable AUTHORIZER_URL
// TODO: remove support AUTHORIZER_URL env
EnvKeyAuthorizerURL = "AUTHORIZER_URL"
// EnvKeyPort key for env variable PORT
EnvKeyPort = "PORT"

View File

@@ -0,0 +1,4 @@
package constants
// DefaultLimit is the default limit for pagination
var DefaultLimit = 10

View File

@@ -13,7 +13,8 @@ import (
func SetAdminCookie(gc *gin.Context, token string) {
secure := true
httpOnly := true
host, _ := utils.GetHostParts(envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyAuthorizerURL))
hostname := utils.GetHost(gc)
host, _ := utils.GetHostParts(hostname)
gc.SetCookie(envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyAdminCookieName), token, 3600, "/", host, secure, httpOnly)
}
@@ -38,7 +39,8 @@ func GetAdminCookie(gc *gin.Context) (string, error) {
func DeleteAdminCookie(gc *gin.Context) {
secure := true
httpOnly := true
host, _ := utils.GetHostParts(envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyAuthorizerURL))
hostname := utils.GetHost(gc)
host, _ := utils.GetHostParts(hostname)
gc.SetCookie(envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyAdminCookieName), "", -1, "/", host, secure, httpOnly)
}

View File

@@ -19,8 +19,9 @@ import (
func SetCookie(gc *gin.Context, accessToken, refreshToken, fingerprintHash string) {
secure := true
httpOnly := true
host, _ := utils.GetHostParts(envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyAuthorizerURL))
domain := utils.GetDomainName(envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyAuthorizerURL))
hostname := utils.GetHost(gc)
host, _ := utils.GetHostParts(hostname)
domain := utils.GetDomainName(hostname)
if domain != "localhost" {
domain = "." + domain
}
@@ -86,9 +87,9 @@ func GetFingerPrintCookie(gc *gin.Context) (string, error) {
func DeleteCookie(gc *gin.Context) {
secure := true
httpOnly := true
host, _ := utils.GetHostParts(envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyAuthorizerURL))
domain := utils.GetDomainName(envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyAuthorizerURL))
hostname := utils.GetHost(gc)
host, _ := utils.GetHostParts(hostname)
domain := utils.GetDomainName(hostname)
if domain != "localhost" {
domain = "." + domain
}

View File

@@ -6,7 +6,6 @@ import (
"github.com/authorizerdev/authorizer/server/constants"
"github.com/authorizerdev/authorizer/server/db/providers"
"github.com/authorizerdev/authorizer/server/db/providers/arangodb"
"github.com/authorizerdev/authorizer/server/db/providers/faunadb"
"github.com/authorizerdev/authorizer/server/db/providers/mongodb"
"github.com/authorizerdev/authorizer/server/db/providers/sql"
"github.com/authorizerdev/authorizer/server/envstore"
@@ -18,10 +17,9 @@ var Provider providers.Provider
func InitDB() {
var err error
isSQL := envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyDatabaseType) != constants.DbTypeArangodb && envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyDatabaseType) != constants.DbTypeMongodb && envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyDatabaseType) != constants.DbTypeFaunadb
isSQL := envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyDatabaseType) != constants.DbTypeArangodb && envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyDatabaseType) != constants.DbTypeMongodb
isArangoDB := envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyDatabaseType) == constants.DbTypeArangodb
isMongoDB := envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyDatabaseType) == constants.DbTypeMongodb
isFaunaDB := envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyDatabaseType) == constants.DbTypeFaunadb
if isSQL {
Provider, err = sql.NewProvider()
@@ -43,11 +41,4 @@ func InitDB() {
log.Fatal("=> error setting arangodb provider:", err)
}
}
if isFaunaDB {
Provider, err = faunadb.NewProvider()
if err != nil {
log.Fatal("=> error setting arangodb provider:", err)
}
}
}

View File

@@ -4,8 +4,8 @@ package models
type Env struct {
Key string `json:"_key,omitempty" bson:"_key"` // for arangodb
ID string `gorm:"primaryKey;type:char(36)" json:"_id" bson:"_id"`
EnvData []byte `gorm:"type:text" json:"env" bson:"env"`
Hash string `gorm:"type:hash" json:"hash" bson:"hash"`
UpdatedAt int64 `gorm:"autoUpdateTime" json:"updated_at" bson:"updated_at"`
CreatedAt int64 `gorm:"autoCreateTime" json:"created_at" bson:"created_at"`
EnvData string `gorm:"type:text" json:"env" bson:"env"`
Hash string `gorm:"type:text" json:"hash" bson:"hash"`
UpdatedAt int64 `json:"updated_at" bson:"updated_at"`
CreatedAt int64 `json:"created_at" bson:"created_at"`
}

View File

@@ -5,9 +5,9 @@ type Session struct {
Key string `json:"_key,omitempty" bson:"_key,omitempty"` // for arangodb
ID string `gorm:"primaryKey;type:char(36)" json:"_id" bson:"_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"`
IP string `json:"ip" bson:"ip"`
CreatedAt int64 `gorm:"autoCreateTime" json:"created_at" bson:"created_at"`
UpdatedAt int64 `gorm:"autoUpdateTime" json:"updated_at" bson:"updated_at"`
CreatedAt int64 `json:"created_at" bson:"created_at"`
UpdatedAt int64 `json:"updated_at" bson:"updated_at"`
}

View File

@@ -25,8 +25,8 @@ type User struct {
PhoneNumberVerifiedAt *int64 `json:"phone_number_verified_at" bson:"phone_number_verified_at"`
Picture *string `gorm:"type:text" json:"picture" bson:"picture"`
Roles string `json:"roles" bson:"roles"`
UpdatedAt int64 `gorm:"autoUpdateTime" json:"updated_at" bson:"updated_at"`
CreatedAt int64 `gorm:"autoCreateTime" json:"created_at" bson:"created_at"`
UpdatedAt int64 `json:"updated_at" bson:"updated_at"`
CreatedAt int64 `json:"created_at" bson:"created_at"`
}
func (user *User) AsAPIUser() *model.User {

View File

@@ -1,5 +1,7 @@
package models
import "github.com/authorizerdev/authorizer/server/graph/model"
// VerificationRequest model for db
type VerificationRequest struct {
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"`
Identifier string `gorm:"uniqueIndex:idx_email_identifier" json:"identifier" bson:"identifier"`
ExpiresAt int64 `json:"expires_at" bson:"expires_at"`
CreatedAt int64 `gorm:"autoCreateTime" json:"created_at" bson:"created_at"`
UpdatedAt int64 `gorm:"autoUpdateTime" json:"updated_at" bson:"updated_at"`
CreatedAt int64 `json:"created_at" bson:"created_at"`
UpdatedAt int64 `json:"updated_at" bson:"updated_at"`
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,
}
}

View File

@@ -1,6 +1,7 @@
package arangodb
import (
"context"
"fmt"
"log"
"strings"
@@ -11,6 +12,7 @@ import (
"github.com/authorizerdev/authorizer/server/constants"
"github.com/authorizerdev/authorizer/server/db/models"
"github.com/authorizerdev/authorizer/server/envstore"
"github.com/authorizerdev/authorizer/server/graph/model"
"github.com/google/uuid"
)
@@ -66,32 +68,40 @@ func (p *provider) DeleteUser(user models.User) error {
}
// ListUsers to get list of users from database
func (p *provider) ListUsers() ([]models.User, error) {
var users []models.User
query := fmt.Sprintf("FOR d in %s RETURN d", models.Collections.User)
func (p *provider) ListUsers(pagination model.Pagination) (*model.Users, error) {
var users []*model.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 {
return users, err
return nil, err
}
defer cursor.Close()
paginationClone := pagination
paginationClone.Total = cursor.Statistics().FullCount()
for {
var user models.User
meta, err := cursor.ReadDocument(nil, &user)
if driver.IsNoMoreDocuments(err) {
if arangoDriver.IsNoMoreDocuments(err) {
break
} else if err != nil {
return users, err
return nil, err
}
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

View File

@@ -1,12 +1,14 @@
package arangodb
import (
"context"
"fmt"
"log"
"time"
"github.com/arangodb/go-driver"
"github.com/authorizerdev/authorizer/server/db/models"
"github.com/authorizerdev/authorizer/server/graph/model"
"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
func (p *provider) ListVerificationRequests() ([]models.VerificationRequest, error) {
var verificationRequests []models.VerificationRequest
func (p *provider) ListVerificationRequests(pagination model.Pagination) (*model.VerificationRequests, error) {
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(nil, query, nil)
cursor, err := p.db.Query(ctx, query, nil)
if err != nil {
return verificationRequests, err
return nil, err
}
defer cursor.Close()
paginationClone := pagination
paginationClone.Total = cursor.Statistics().FullCount()
for {
var verificationRequest models.VerificationRequest
meta, err := cursor.ReadDocument(nil, &verificationRequest)
@@ -111,16 +116,19 @@ func (p *provider) ListVerificationRequests() ([]models.VerificationRequest, err
if driver.IsNoMoreDocuments(err) {
break
} else if err != nil {
return verificationRequests, err
return nil, err
}
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

View File

@@ -1,51 +0,0 @@
package faunadb
import (
"log"
"time"
f "github.com/fauna/faunadb-go/v5/faunadb"
"github.com/google/uuid"
"github.com/authorizerdev/authorizer/server/db/models"
)
// AddEnv to save environment information in database
func (p *provider) AddEnv(env models.Env) (models.Env, error) {
if env.ID == "" {
env.ID = uuid.New().String()
env.Key = env.ID
}
env.CreatedAt = time.Now().Unix()
env.UpdatedAt = time.Now().Unix()
_, err := p.db.Query(
f.Create(
f.Collection(models.Collections.Env),
f.Obj{
"data": env,
},
),
)
if err != nil {
log.Println("error adding env:", err)
return env, err
}
return env, nil
}
// UpdateEnv to update environment information in database
func (p *provider) UpdateEnv(env models.Env) (models.Env, error) {
env.UpdatedAt = time.Now().Unix()
return env, nil
}
// GetEnv to get environment information from database
func (p *provider) GetEnv() (models.Env, error) {
var env models.Env
return env, nil
}

View File

@@ -1,164 +0,0 @@
package faunadb
import (
"errors"
"log"
"strings"
"github.com/authorizerdev/authorizer/server/constants"
"github.com/authorizerdev/authorizer/server/db/models"
"github.com/authorizerdev/authorizer/server/envstore"
f "github.com/fauna/faunadb-go/v5/faunadb"
)
type provider struct {
db *f.FaunaClient
}
// NewProvider returns a new faunadb provider
func NewProvider() (*provider, error) {
secret := ""
dbURL := "https://db.fauna.com"
// secret,url is stored in DATABASE_URL
dbURLSplit := strings.Split(envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyDatabaseURL), ":")
secret = dbURLSplit[0]
if len(dbURLSplit) > 1 {
dbURL = dbURLSplit[1]
}
client := f.NewFaunaClient(secret, f.Endpoint(dbURL))
if client == nil {
return nil, errors.New("failed to create faunadb client")
}
_, err := client.Query(
f.CreateCollection(f.Obj{"name": models.Collections.Env}))
if err != nil {
log.Println("error:", err)
}
_, err = client.Query(
f.CreateIndex(
f.Obj{
"name": "env_id",
"source": f.Collection(models.Collections.Env),
"values": "_id",
"unique": true,
}))
if err != nil {
log.Println("error:", err)
}
_, err = client.Query(
f.CreateIndex(
f.Obj{
"name": "env_key",
"source": f.Collection(models.Collections.Env),
"values": "_key",
"unique": true,
}))
if err != nil {
log.Println("error:", err)
}
_, err = client.Query(
f.CreateCollection(f.Obj{"name": models.Collections.User}))
if err != nil {
log.Println("error:", err)
}
_, err = client.Query(
f.CreateIndex(
f.Obj{
"name": "_id",
"source": f.Collection(models.Collections.User),
"unique": true,
}))
if err != nil {
log.Println("error:", err)
}
_, err = client.Query(
f.CreateIndex(
f.Obj{
"name": "_key",
"source": f.Collection(models.Collections.User),
"unique": true,
}))
if err != nil {
log.Println("error:", err)
}
_, err = client.Query(
f.CreateIndex(
f.Obj{
"name": "email",
"source": f.Collection(models.Collections.User),
"unique": true,
}))
if err != nil {
log.Println("error:", err)
}
_, err = client.Query(
f.CreateCollection(f.Obj{"name": models.Collections.Session}))
if err != nil {
log.Println("error:", err)
}
_, err = client.Query(
f.CreateIndex(
f.Obj{
"name": "_id",
"source": f.Collection(models.Collections.Session),
"unique": true,
}))
if err != nil {
log.Println("error:", err)
}
_, err = client.Query(
f.CreateIndex(
f.Obj{
"name": "_key",
"source": f.Collection(models.Collections.Session),
"unique": true,
}))
if err != nil {
log.Println("error:", err)
}
_, err = client.Query(
f.CreateCollection(f.Obj{"name": models.Collections.VerificationRequest}))
if err != nil {
log.Println("error:", err)
}
_, err = client.Query(
f.CreateIndex(
f.Obj{
"name": "_id",
"source": f.Collection(models.Collections.VerificationRequest),
"unique": true,
}))
if err != nil {
log.Println("error:", err)
}
_, err = client.Query(
f.CreateIndex(
f.Obj{
"name": "_key",
"source": f.Collection(models.Collections.VerificationRequest),
"unique": true,
}))
if err != nil {
log.Println("error:", err)
}
return &provider{
db: client,
}, nil
}

View File

@@ -1,25 +0,0 @@
package faunadb
import (
"time"
"github.com/authorizerdev/authorizer/server/db/models"
"github.com/google/uuid"
)
// AddSession to save session information in database
func (p *provider) AddSession(session models.Session) error {
if session.ID == "" {
session.ID = uuid.New().String()
}
session.CreatedAt = time.Now().Unix()
session.UpdatedAt = time.Now().Unix()
return nil
}
// DeleteSession to delete session information from database
func (p *provider) DeleteSession(userId string) error {
return nil
}

View File

@@ -1,60 +0,0 @@
package faunadb
import (
"strings"
"time"
"github.com/authorizerdev/authorizer/server/constants"
"github.com/authorizerdev/authorizer/server/db/models"
"github.com/authorizerdev/authorizer/server/envstore"
"github.com/google/uuid"
)
// AddUser to save user information in database
func (p *provider) AddUser(user models.User) (models.User, error) {
if user.ID == "" {
user.ID = uuid.New().String()
}
if user.Roles == "" {
user.Roles = strings.Join(envstore.EnvInMemoryStoreObj.GetSliceStoreEnvVariable(constants.EnvKeyDefaultRoles), ",")
}
user.CreatedAt = time.Now().Unix()
user.UpdatedAt = time.Now().Unix()
return user, nil
}
// UpdateUser to update user information in database
func (p *provider) UpdateUser(user models.User) (models.User, error) {
user.UpdatedAt = time.Now().Unix()
return user, nil
}
// DeleteUser to delete user information from database
func (p *provider) DeleteUser(user models.User) error {
return nil
}
// ListUsers to get list of users from database
func (p *provider) ListUsers() ([]models.User, error) {
var users []models.User
return users, nil
}
// GetUserByEmail to get user information from database using email address
func (p *provider) GetUserByEmail(email string) (models.User, error) {
var user models.User
return user, nil
}
// GetUserByID to get user information from database using user ID
func (p *provider) GetUserByID(id string) (models.User, error) {
var user models.User
return user, nil
}

View File

@@ -1,46 +0,0 @@
package faunadb
import (
"time"
"github.com/authorizerdev/authorizer/server/db/models"
"github.com/google/uuid"
)
// AddVerification to save verification request in database
func (p *provider) AddVerificationRequest(verificationRequest models.VerificationRequest) (models.VerificationRequest, error) {
if verificationRequest.ID == "" {
verificationRequest.ID = uuid.New().String()
}
verificationRequest.CreatedAt = time.Now().Unix()
verificationRequest.UpdatedAt = time.Now().Unix()
return verificationRequest, nil
}
// GetVerificationRequestByToken to get verification request from database using token
func (p *provider) GetVerificationRequestByToken(token string) (models.VerificationRequest, error) {
var verificationRequest models.VerificationRequest
return verificationRequest, nil
}
// GetVerificationRequestByEmail to get verification request by email from database
func (p *provider) GetVerificationRequestByEmail(email string, identifier string) (models.VerificationRequest, error) {
var verificationRequest models.VerificationRequest
return verificationRequest, nil
}
// ListVerificationRequests to get list of verification requests from database
func (p *provider) ListVerificationRequests() ([]models.VerificationRequest, error) {
var verificationRequests []models.VerificationRequest
return verificationRequests, nil
}
// DeleteVerificationRequest to delete verification request from database
func (p *provider) DeleteVerificationRequest(verificationRequest models.VerificationRequest) error {
return nil
}

View File

@@ -8,6 +8,7 @@ import (
"github.com/authorizerdev/authorizer/server/constants"
"github.com/authorizerdev/authorizer/server/db/models"
"github.com/authorizerdev/authorizer/server/envstore"
"github.com/authorizerdev/authorizer/server/graph/model"
"github.com/google/uuid"
"go.mongodb.org/mongo-driver/bson"
"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
func (p *provider) ListUsers() ([]models.User, error) {
var users []models.User
func (p *provider) ListUsers(pagination model.Pagination) (*model.Users, error) {
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())
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 {
log.Println("error getting users:", err)
return users, err
return nil, err
}
defer cursor.Close(nil)
@@ -74,12 +91,15 @@ func (p *provider) ListUsers() ([]models.User, error) {
var user models.User
err := cursor.Decode(&user)
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

View File

@@ -5,6 +5,7 @@ import (
"time"
"github.com/authorizerdev/authorizer/server/db/models"
"github.com/authorizerdev/authorizer/server/graph/model"
"github.com/google/uuid"
"go.mongodb.org/mongo-driver/bson"
"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
func (p *provider) ListVerificationRequests() ([]models.VerificationRequest, error) {
var verificationRequests []models.VerificationRequest
func (p *provider) ListVerificationRequests(pagination model.Pagination) (*model.VerificationRequests, error) {
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())
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 {
log.Println("error getting verification requests:", err)
return verificationRequests, err
return nil, err
}
defer cursor.Close(nil)
@@ -70,12 +82,15 @@ func (p *provider) ListVerificationRequests() ([]models.VerificationRequest, err
var verificationRequest models.VerificationRequest
err := cursor.Decode(&verificationRequest)
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

View File

@@ -1,6 +1,9 @@
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 {
// AddUser to save user information in database
@@ -10,7 +13,7 @@ type Provider interface {
// DeleteUser to delete user information from database
DeleteUser(user models.User) error
// 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(email string) (models.User, error)
// 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(email string, identifier string) (models.VerificationRequest, error)
// 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(verificationRequest models.VerificationRequest) error

View File

@@ -15,8 +15,10 @@ func (p *provider) AddEnv(env models.Env) (models.Env, error) {
}
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 {
log.Println("error adding config:", result.Error)
return env, result.Error

View File

@@ -2,6 +2,7 @@ package sql
import (
"log"
"time"
"github.com/authorizerdev/authorizer/server/db/models"
"github.com/google/uuid"
@@ -15,6 +16,8 @@ func (p *provider) AddSession(session models.Session) error {
}
session.Key = session.ID
session.CreatedAt = time.Now().Unix()
session.UpdatedAt = time.Now().Unix()
res := p.db.Clauses(
clause.OnConflict{
DoNothing: true,

View File

@@ -1,6 +1,10 @@
package sql
import (
"log"
"os"
"time"
"github.com/authorizerdev/authorizer/server/constants"
"github.com/authorizerdev/authorizer/server/db/models"
"github.com/authorizerdev/authorizer/server/envstore"
@@ -9,6 +13,7 @@ import (
"gorm.io/driver/sqlite"
"gorm.io/driver/sqlserver"
"gorm.io/gorm"
"gorm.io/gorm/logger"
"gorm.io/gorm/schema"
)
@@ -20,8 +25,18 @@ type provider struct {
func NewProvider() (*provider, error) {
var sqlDB *gorm.DB
var err error
customLogger := logger.New(
log.New(os.Stdout, "\r\n", log.LstdFlags), // io writer
logger.Config{
SlowThreshold: time.Second, // Slow SQL threshold
LogLevel: logger.Silent, // Log level
IgnoreRecordNotFoundError: true, // Ignore ErrRecordNotFound error for logger
Colorful: false, // Disable color
},
)
ormConfig := &gorm.Config{
Logger: customLogger,
NamingStrategy: schema.NamingStrategy{
TablePrefix: models.Prefix,
},

View File

@@ -8,6 +8,7 @@ import (
"github.com/authorizerdev/authorizer/server/constants"
"github.com/authorizerdev/authorizer/server/db/models"
"github.com/authorizerdev/authorizer/server/envstore"
"github.com/authorizerdev/authorizer/server/graph/model"
"github.com/google/uuid"
"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.CreatedAt = time.Now().Unix()
user.UpdatedAt = time.Now().Unix()
user.Key = user.ID
result := p.db.Clauses(
clause.OnConflict{
@@ -64,15 +67,32 @@ func (p *provider) DeleteUser(user models.User) error {
}
// 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
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 {
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

View File

@@ -2,8 +2,10 @@ package sql
import (
"log"
"time"
"github.com/authorizerdev/authorizer/server/db/models"
"github.com/authorizerdev/authorizer/server/graph/model"
"github.com/google/uuid"
"gorm.io/gorm/clause"
)
@@ -15,6 +17,8 @@ func (p *provider) AddVerificationRequest(verificationRequest models.Verificatio
}
verificationRequest.Key = verificationRequest.ID
verificationRequest.CreatedAt = time.Now().Unix()
verificationRequest.UpdatedAt = time.Now().Unix()
result := p.db.Clauses(clause.OnConflict{
Columns: []clause.Column{{Name: "email"}, {Name: "identifier"}},
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
func (p *provider) ListVerificationRequests() ([]models.VerificationRequest, error) {
func (p *provider) ListVerificationRequests(pagination model.Pagination) (*model.VerificationRequests, error) {
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 {
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

View File

@@ -6,10 +6,10 @@ import (
)
// 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)
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

View File

@@ -6,7 +6,7 @@ import (
)
// 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
Receiver := []string{toEmail}
@@ -99,7 +99,7 @@ func SendVerificationMail(toEmail, token string) error {
data := make(map[string]interface{}, 3)
data["org_logo"] = envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyOrganizationLogo)
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")
// bodyMessage := sender.WriteHTMLEmail(Receiver, Subject, message)

10
server/env/env.go vendored
View File

@@ -8,6 +8,7 @@ import (
"github.com/authorizerdev/authorizer/server/constants"
"github.com/authorizerdev/authorizer/server/envstore"
"github.com/authorizerdev/authorizer/server/utils"
"github.com/gin-gonic/gin"
"github.com/google/uuid"
"github.com/joho/godotenv"
)
@@ -25,14 +26,12 @@ func InitEnv() {
if envData.StringEnv[constants.EnvKeyEnv] == "production" {
envData.BoolEnv[constants.EnvKeyIsProd] = true
os.Setenv("GIN_MODE", "release")
gin.SetMode(gin.ReleaseMode)
} else {
envData.BoolEnv[constants.EnvKeyIsProd] = false
}
}
// 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] == "" {
envData.StringEnv[constants.EnvKeyAppURL] = os.Getenv(constants.EnvKeyAppURL)
}
@@ -47,7 +46,7 @@ func InitEnv() {
err := godotenv.Load(envData.StringEnv[constants.EnvKeyEnvPath])
if err != nil {
log.Printf("error loading %s file", envData.StringEnv[constants.EnvKeyEnvPath])
log.Printf("using OS env instead of %s file", envData.StringEnv[constants.EnvKeyEnvPath])
}
if envData.StringEnv[constants.EnvKeyPort] == "" {
@@ -246,12 +245,11 @@ func InitEnv() {
trimVal := strings.TrimSpace(val)
if trimVal != "" {
roles = append(roles, trimVal)
}
if utils.StringSliceContains(defaultRoleSplit, trimVal) {
defaultRoles = append(defaultRoles, trimVal)
}
}
}
if len(roles) > 0 && len(defaultRoles) == 0 && len(defaultRolesEnv) > 0 {
panic(`Invalid DEFAULT_ROLE environment variable. It can be one from give ROLES environment variable value`)

View File

@@ -25,15 +25,19 @@ func PersistEnv() error {
envstore.EnvInMemoryStoreObj.UpdateEnvVariable(constants.StringStoreIdentifier, constants.EnvKeyEncryptionKey, hash)
encodedHash := utils.EncryptB64(hash)
configData, err := json.Marshal(envstore.EnvInMemoryStoreObj.GetEnvStoreClone())
encryptedConfig, err := utils.EncryptEnvData(envstore.EnvInMemoryStoreObj.GetEnvStoreClone())
if err != nil {
return err
}
// configData, err := json.Marshal()
// if err != nil {
// return err
// }
encryptedConfig, err := utils.EncryptAES(configData)
if err != nil {
return err
}
// encryptedConfig, err := utils.EncryptAES(configData)
// if err != nil {
// return err
// }
env = models.Env{
Hash: encodedHash,
@@ -51,7 +55,12 @@ func PersistEnv() error {
}
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 {
return err
}

View File

@@ -35,7 +35,7 @@ var EnvInMemoryStoreObj = &EnvInMemoryStore{
constants.EnvKeyAdminCookieName: "authorizer-admin",
constants.EnvKeyJwtRoleClaim: "role",
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{
constants.EnvKeyDisableBasicAuthentication: false,

View File

@@ -6,8 +6,6 @@ require (
github.com/99designs/gqlgen v0.14.0
github.com/arangodb/go-driver v1.2.1
github.com/coreos/go-oidc/v3 v3.1.0
github.com/fauna/faunadb-go/v5 v5.0.0-beta // indirect
github.com/gin-contrib/location v0.0.2
github.com/gin-gonic/gin v1.7.2
github.com/go-playground/validator/v10 v10.8.0 // indirect
github.com/go-redis/redis/v8 v8.11.0

View File

@@ -79,16 +79,11 @@ github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymF
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/fauna/faunadb-go/v5 v5.0.0-beta h1:qjig7OPEsDPH/DJuHWIrOboreYd5aLQnSCzLgHDpnck=
github.com/fauna/faunadb-go/v5 v5.0.0-beta/go.mod h1:eoEA8JUERBnzK5/8Rxnetzx326ImTZ8c++wi2GQwrEU=
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/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/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/go.mod h1:jD2toBW3GZUr5UMcdrwQA10I7RuaFOl/SGeDjXkfUtY=
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
@@ -102,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/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/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.8.0 h1:1kAa0fCrnpv+QYdkdcRzrRM7AyYs5o8+jZdJCz9xj6k=
github.com/go-playground/validator/v10 v10.8.0/go.mod h1:9JhgTzTaE31GZDpH/HSvHiRJrJ3iKAgqqH0Bl/Ocjdk=
@@ -458,7 +452,6 @@ golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81R
golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20201202161906-c7110b5ffcbb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210614182718-04defd469f4e h1:XpT3nA5TvE525Ne3hInMh6+GETgn27Zfm9dxsThnX2Q=
golang.org/x/net v0.0.0-20210614182718-04defd469f4e/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=

File diff suppressed because it is too large Load Diff

View File

@@ -35,7 +35,6 @@ type Env struct {
JwtType *string `json:"JWT_TYPE"`
JwtSecret *string `json:"JWT_SECRET"`
AllowedOrigins []string `json:"ALLOWED_ORIGINS"`
AuthorizerURL *string `json:"AUTHORIZER_URL"`
AppURL *string `json:"APP_URL"`
RedisURL *string `json:"REDIS_URL"`
CookieName *string `json:"COOKIE_NAME"`
@@ -93,6 +92,22 @@ type Meta struct {
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 {
Email string `json:"email"`
Identifier string `json:"identifier"`
@@ -133,12 +148,12 @@ type UpdateEnvInput struct {
OldAdminSecret *string `json:"OLD_ADMIN_SECRET"`
SMTPHost *string `json:"SMTP_HOST"`
SMTPPort *string `json:"SMTP_PORT"`
SMTPUsername *string `json:"SMTP_USERNAME"`
SMTPPassword *string `json:"SMTP_PASSWORD"`
SenderEmail *string `json:"SENDER_EMAIL"`
SenderPassword *string `json:"SENDER_PASSWORD"`
JwtType *string `json:"JWT_TYPE"`
JwtSecret *string `json:"JWT_SECRET"`
AllowedOrigins []string `json:"ALLOWED_ORIGINS"`
AuthorizerURL *string `json:"AUTHORIZER_URL"`
AppURL *string `json:"APP_URL"`
RedisURL *string `json:"REDIS_URL"`
CookieName *string `json:"COOKIE_NAME"`
@@ -211,6 +226,11 @@ type User struct {
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"`
@@ -226,6 +246,11 @@ type VerificationRequest struct {
UpdatedAt *int64 `json:"updated_at"`
}
type VerificationRequests struct {
Pagination *Pagination `json:"pagination"`
VerificationRequests []*VerificationRequest `json:"verification_requests"`
}
type VerifyEmailInput struct {
Token string `json:"token"`
}

View File

@@ -5,6 +5,13 @@ scalar Int64
scalar Map
scalar Any
type Pagination {
limit: Int64!
page: Int64!
offset: Int64!
total: Int64!
}
type Meta {
version: String!
is_google_login_enabled: Boolean!
@@ -36,6 +43,11 @@ type User {
updated_at: Int64
}
type Users {
pagination: Pagination!
users: [User!]!
}
type VerificationRequest {
id: ID!
identifier: String
@@ -46,6 +58,11 @@ type VerificationRequest {
updated_at: Int64
}
type VerificationRequests {
pagination: Pagination!
verification_requests: [VerificationRequest!]!
}
type Error {
message: String!
reason: String!
@@ -81,7 +98,6 @@ type Env {
JWT_TYPE: String
JWT_SECRET: String
ALLOWED_ORIGINS: [String!]
AUTHORIZER_URL: String
APP_URL: String
REDIS_URL: String
COOKIE_NAME: String
@@ -110,12 +126,12 @@ input UpdateEnvInput {
OLD_ADMIN_SECRET: String
SMTP_HOST: String
SMTP_PORT: String
SMTP_USERNAME: String
SMTP_PASSWORD: String
SENDER_EMAIL: String
SENDER_PASSWORD: String
JWT_TYPE: String
JWT_SECRET: String
ALLOWED_ORIGINS: [String!]
AUTHORIZER_URL: String
APP_URL: String
REDIS_URL: String
COOKIE_NAME: String
@@ -234,6 +250,15 @@ input IsValidJWTQueryInput {
roles: [String!]
}
input PaginationInput {
limit: Int64
page: Int64
}
input PaginatedInput {
pagination: PaginationInput
}
type Mutation {
signup(params: SignUpInput!): AuthResponse!
login(params: LoginInput!): AuthResponse!
@@ -259,8 +284,8 @@ type Query {
is_valid_jwt(params: IsValidJWTQueryInput): ValidJWTResponse!
profile: User!
# admin only apis
_users: [User!]!
_verification_requests: [VerificationRequest!]!
_users(params: PaginatedInput): Users!
_verification_requests(params: PaginatedInput): VerificationRequests!
_admin_session: Response!
_env: Env!
}

View File

@@ -87,12 +87,12 @@ func (r *queryResolver) Profile(ctx context.Context) (*model.User, error) {
return resolvers.ProfileResolver(ctx)
}
func (r *queryResolver) Users(ctx context.Context) ([]*model.User, error) {
return resolvers.UsersResolver(ctx)
func (r *queryResolver) Users(ctx context.Context, params *model.PaginatedInput) (*model.Users, error) {
return resolvers.UsersResolver(ctx, params)
}
func (r *queryResolver) VerificationRequests(ctx context.Context) ([]*model.VerificationRequest, error) {
return resolvers.VerificationRequestsResolver(ctx)
func (r *queryResolver) VerificationRequests(ctx context.Context, params *model.PaginatedInput) (*model.VerificationRequests, error) {
return resolvers.VerificationRequestsResolver(ctx, params)
}
func (r *queryResolver) AdminSession(ctx context.Context) (*model.Response, error) {

View File

@@ -22,14 +22,19 @@ type State struct {
// AppHandler is the handler for the /app route
func AppHandler() gin.HandlerFunc {
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")
var stateObj State
if state == "" {
stateObj.AuthorizerURL = envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyAuthorizerURL)
stateObj.RedirectURL = stateObj.AuthorizerURL + "/app"
stateObj.AuthorizerURL = hostname
stateObj.RedirectURL = hostname + "/app"
} else {
decodedState, err := utils.DecryptB64(state)
if err != nil {
@@ -57,7 +62,7 @@ func AppHandler() gin.HandlerFunc {
}
// 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"})
return
}

15
server/handlers/health.go Normal file
View File

@@ -0,0 +1,15 @@
package handlers
import (
"net/http"
"github.com/gin-gonic/gin"
)
// HealthHandler is the handler for /health route.
// It states if server is in healthy state or not
func HealthHandler() gin.HandlerFunc {
return func(c *gin.Context) {
c.String(http.StatusOK, "OK")
}
}

View File

@@ -96,8 +96,13 @@ func OAuthCallbackHandler() gin.HandlerFunc {
if !strings.Contains(signupMethod, provider) {
signupMethod = signupMethod + "," + provider
}
user = existingUser
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
// 1. user has access to protected roles + roles and trying to login
@@ -131,12 +136,13 @@ func OAuthCallbackHandler() gin.HandlerFunc {
} else {
user.Roles = existingUser.Roles
}
user.Key = existingUser.Key
user.ID = existingUser.ID
user, err = db.Provider.UpdateUser(user)
}
user, _ = db.Provider.GetUserByEmail(user.Email)
user, err = db.Provider.UpdateUser(user)
if err != nil {
c.JSON(500, gin.H{"error": err.Error()})
return
}
}
authToken, _ := token.CreateAuthToken(user, inputRoles)
sessionstore.SetUserSession(user.ID, authToken.FingerPrint, authToken.RefreshToken.Token)

View File

@@ -16,7 +16,7 @@ import (
// OAuthLoginHandler set host in the oauth state that is useful for redirecting to oauth_callback
func OAuthLoginHandler() gin.HandlerFunc {
return func(c *gin.Context) {
// TODO validate redirect URL
hostname := utils.GetHost(c)
redirectURL := c.Query("redirectURL")
roles := c.Query("roles")
@@ -56,7 +56,7 @@ func OAuthLoginHandler() gin.HandlerFunc {
}
sessionstore.SetSocailLoginState(oauthStateString, constants.SignupMethodGoogle)
// 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)
c.Redirect(http.StatusTemporaryRedirect, url)
case constants.SignupMethodGithub:
@@ -65,7 +65,7 @@ func OAuthLoginHandler() gin.HandlerFunc {
break
}
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)
c.Redirect(http.StatusTemporaryRedirect, url)
case constants.SignupMethodFacebook:
@@ -74,7 +74,7 @@ func OAuthLoginHandler() gin.HandlerFunc {
break
}
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)
c.Redirect(http.StatusTemporaryRedirect, url)
default:

View File

@@ -3,19 +3,12 @@ package middlewares
import (
"context"
"github.com/authorizerdev/authorizer/server/constants"
"github.com/authorizerdev/authorizer/server/envstore"
"github.com/gin-contrib/location"
"github.com/gin-gonic/gin"
)
// GinContextToContextMiddleware is a middleware to add gin context in context
func GinContextToContextMiddleware() gin.HandlerFunc {
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)
c.Request = c.Request.WithContext(ctx)
c.Next()

View File

@@ -43,7 +43,7 @@ func InitOAuth() {
OAuthProviders.GoogleConfig = &oauth2.Config{
ClientID: envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyGoogleClientID),
ClientSecret: envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyGoogleClientSecret),
RedirectURL: envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyAuthorizerURL) + "/oauth_callback/google",
RedirectURL: "/oauth_callback/google",
Endpoint: OIDCProviders.GoogleOIDC.Endpoint(),
Scopes: []string{oidc.ScopeOpenID, "profile", "email"},
}
@@ -52,7 +52,7 @@ func InitOAuth() {
OAuthProviders.GithubConfig = &oauth2.Config{
ClientID: envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyGithubClientID),
ClientSecret: envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyGithubClientSecret),
RedirectURL: envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyAuthorizerURL) + "/oauth_callback/github",
RedirectURL: "/oauth_callback/github",
Endpoint: githubOAuth2.Endpoint,
}
}
@@ -60,7 +60,7 @@ func InitOAuth() {
OAuthProviders.FacebookConfig = &oauth2.Config{
ClientID: envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyFacebookClientID),
ClientSecret: envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyFacebookClientSecret),
RedirectURL: envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyAuthorizerURL) + "/oauth_callback/facebook",
RedirectURL: "/oauth_callback/facebook",
Endpoint: facebookOAuth2.Endpoint,
Scopes: []string{"public_profile", "email"},
}

View File

@@ -41,7 +41,6 @@ func EnvResolver(ctx context.Context) (*model.Env, error) {
jwtSecret := store.StringEnv[constants.EnvKeyJwtSecret]
jwtRoleClaim := store.StringEnv[constants.EnvKeyJwtRoleClaim]
allowedOrigins := store.SliceEnv[constants.EnvKeyAllowedOrigins]
authorizerURL := store.StringEnv[constants.EnvKeyAuthorizerURL]
appURL := store.StringEnv[constants.EnvKeyAppURL]
redisURL := store.StringEnv[constants.EnvKeyRedisURL]
cookieName := store.StringEnv[constants.EnvKeyCookieName]
@@ -77,7 +76,6 @@ func EnvResolver(ctx context.Context) (*model.Env, error) {
JwtSecret: &jwtSecret,
JwtRoleClaim: &jwtRoleClaim,
AllowedOrigins: allowedOrigins,
AuthorizerURL: &authorizerURL,
AppURL: &appURL,
RedisURL: &redisURL,
CookieName: &cookieName,

View File

@@ -27,7 +27,6 @@ func ForgotPasswordResolver(ctx context.Context, params model.ForgotPasswordInpu
if envstore.EnvInMemoryStoreObj.GetBoolStoreEnvVariable(constants.EnvKeyDisableBasicAuthentication) {
return res, fmt.Errorf(`basic authentication is disabled for this instance`)
}
host := gc.Request.Host
params.Email = strings.ToLower(params.Email)
if !utils.IsValidEmail(params.Email) {
@@ -39,7 +38,8 @@ func ForgotPasswordResolver(ctx context.Context, params model.ForgotPasswordInpu
return res, fmt.Errorf(`user with this email not found`)
}
verificationToken, err := token.CreateVerificationToken(params.Email, constants.VerificationTypeForgotPassword)
hostname := utils.GetHost(gc)
verificationToken, err := token.CreateVerificationToken(params.Email, constants.VerificationTypeForgotPassword, hostname)
if err != nil {
log.Println(`error generating token`, err)
}
@@ -52,7 +52,7 @@ func ForgotPasswordResolver(ctx context.Context, params model.ForgotPasswordInpu
// exec it as go routin so that we can reduce the api latency
go func() {
email.SendForgotPasswordMail(params.Email, verificationToken, host)
email.SendForgotPasswordMail(params.Email, verificationToken, hostname)
}()
res = &model.Response{

View File

@@ -20,6 +20,10 @@ import (
// MagicLinkLoginResolver is a resolver for magic link login mutation
func MagicLinkLoginResolver(ctx context.Context, params model.MagicLinkLoginInput) (*model.Response, error) {
var res *model.Response
gc, err := utils.GinContextFromContext(ctx)
if err != nil {
return res, err
}
if envstore.EnvInMemoryStoreObj.GetBoolStoreEnvVariable(constants.EnvKeyDisableMagicLinkLogin) {
return res, fmt.Errorf(`magic link login is disabled for this instance`)
@@ -102,10 +106,11 @@ func MagicLinkLoginResolver(ctx context.Context, params model.MagicLinkLoginInpu
}
}
hostname := utils.GetHost(gc)
if !envstore.EnvInMemoryStoreObj.GetBoolStoreEnvVariable(constants.EnvKeyDisableEmailVerification) {
// insert verification request
verificationType := constants.VerificationTypeMagicLinkLogin
verificationToken, err := token.CreateVerificationToken(params.Email, verificationType)
verificationToken, err := token.CreateVerificationToken(params.Email, verificationType, hostname)
if err != nil {
log.Println(`error generating token`, err)
}
@@ -118,7 +123,7 @@ func MagicLinkLoginResolver(ctx context.Context, params model.MagicLinkLoginInpu
// exec it as go routin so that we can reduce the api latency
go func() {
email.SendVerificationMail(params.Email, verificationToken)
email.SendVerificationMail(params.Email, verificationToken, hostname)
}()
}

View File

@@ -18,6 +18,10 @@ import (
// ResendVerifyEmailResolver is a resolver for resend verify email mutation
func ResendVerifyEmailResolver(ctx context.Context, params model.ResendVerifyEmailInput) (*model.Response, error) {
var res *model.Response
gc, err := utils.GinContextFromContext(ctx)
if err != nil {
return res, err
}
params.Email = strings.ToLower(params.Email)
if !utils.IsValidEmail(params.Email) {
@@ -39,7 +43,8 @@ func ResendVerifyEmailResolver(ctx context.Context, params model.ResendVerifyEma
log.Println("error deleting verification request:", err)
}
verificationToken, err := token.CreateVerificationToken(params.Email, params.Identifier)
hostname := utils.GetHost(gc)
verificationToken, err := token.CreateVerificationToken(params.Email, params.Identifier, hostname)
if err != nil {
log.Println(`error generating token`, err)
}
@@ -52,7 +57,7 @@ func ResendVerifyEmailResolver(ctx context.Context, params model.ResendVerifyEma
// exec it as go routin so that we can reduce the api latency
go func() {
email.SendVerificationMail(params.Email, verificationToken)
email.SendVerificationMail(params.Email, verificationToken, hostname)
}()
res = &model.Response{

View File

@@ -119,10 +119,11 @@ func SignupResolver(ctx context.Context, params model.SignUpInput) (*model.AuthR
roles := strings.Split(user.Roles, ",")
userToReturn := user.AsAPIUser()
hostname := utils.GetHost(gc)
if !envstore.EnvInMemoryStoreObj.GetBoolStoreEnvVariable(constants.EnvKeyDisableEmailVerification) {
// insert verification request
verificationType := constants.VerificationTypeBasicAuthSignup
verificationToken, err := token.CreateVerificationToken(params.Email, verificationType)
verificationToken, err := token.CreateVerificationToken(params.Email, verificationType, hostname)
if err != nil {
log.Println(`error generating token`, err)
}
@@ -135,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
go func() {
email.SendVerificationMail(params.Email, verificationToken)
email.SendVerificationMail(params.Email, verificationToken, hostname)
}()
res = &model.AuthResponse{

View File

@@ -13,9 +13,10 @@ import (
"github.com/authorizerdev/authorizer/server/db"
"github.com/authorizerdev/authorizer/server/envstore"
"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"
"golang.org/x/crypto/bcrypt"
)
// UpdateEnvResolver is a resolver for update config mutation
@@ -43,6 +44,23 @@ func UpdateEnvResolver(ctx context.Context, params model.UpdateEnvInput) (*model
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()
for key, value := range data {
if value != nil {
@@ -99,6 +117,8 @@ func UpdateEnvResolver(ctx context.Context, params model.UpdateEnvInput) (*model
// Update local store
envstore.EnvInMemoryStoreObj.UpdateEnvStore(updatedData)
sessionstore.InitSession()
oauth.InitOAuth()
// Fetch the current db store and update it
env, err := db.Provider.GetEnv()
@@ -106,22 +126,7 @@ func UpdateEnvResolver(ctx context.Context, params model.UpdateEnvInput) (*model
return res, err
}
encryptedConfig, err := utils.EncryptEnvData(updatedData)
if err != nil {
return res, 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")
}
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))
if err != nil {
return res, err
@@ -129,6 +134,11 @@ func UpdateEnvResolver(ctx context.Context, params model.UpdateEnvInput) (*model
cookie.SetAdminCookie(gc, hashedKey)
}
encryptedConfig, err := utils.EncryptEnvData(updatedData)
if err != nil {
return res, err
}
env.EnvData = encryptedConfig
_, err = db.Provider.UpdateEnv(env)
if err != nil {

View File

@@ -116,12 +116,13 @@ func UpdateProfileResolver(ctx context.Context, params model.UpdateProfileInput)
sessionstore.DeleteAllUserSession(fmt.Sprintf("%v", user.ID))
cookie.DeleteCookie(gc)
hostname := utils.GetHost(gc)
user.Email = newEmail
user.EmailVerifiedAt = nil
hasEmailChanged = true
// insert verification request
verificationType := constants.VerificationTypeUpdateEmail
verificationToken, err := token.CreateVerificationToken(newEmail, verificationType)
verificationToken, err := token.CreateVerificationToken(newEmail, verificationType, hostname)
if err != nil {
log.Println(`error generating token`, err)
}
@@ -134,7 +135,7 @@ func UpdateProfileResolver(ctx context.Context, params model.UpdateProfileInput)
// exec it as go routin so that we can reduce the api latency
go func() {
email.SendVerificationMail(newEmail, verificationToken)
email.SendVerificationMail(newEmail, verificationToken, hostname)
}()
}

View File

@@ -98,11 +98,12 @@ func UpdateUserResolver(ctx context.Context, params model.UpdateUserInput) (*mod
sessionstore.DeleteAllUserSession(fmt.Sprintf("%v", user.ID))
cookie.DeleteCookie(gc)
hostname := utils.GetHost(gc)
user.Email = newEmail
user.EmailVerifiedAt = nil
// insert verification request
verificationType := constants.VerificationTypeUpdateEmail
verificationToken, err := token.CreateVerificationToken(newEmail, verificationType)
verificationToken, err := token.CreateVerificationToken(newEmail, verificationType, hostname)
if err != nil {
log.Println(`error generating token`, err)
}
@@ -115,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
go func() {
email.SendVerificationMail(newEmail, verificationToken)
email.SendVerificationMail(newEmail, verificationToken, hostname)
}()
}
@@ -127,7 +128,7 @@ func UpdateUserResolver(ctx context.Context, params model.UpdateUserInput) (*mod
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")
}

View File

@@ -12,24 +12,21 @@ import (
// UsersResolver is a resolver for users 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)
var res []*model.User
if err != nil {
return res, err
return nil, err
}
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 {
return res, err
}
for i := 0; i < len(users); i++ {
res = append(res, users[i].AsAPIUser())
return nil, err
}
return res, nil

View File

@@ -12,32 +12,21 @@ import (
// VerificationRequestsResolver is a resolver for verification requests 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)
var res []*model.VerificationRequest
if err != nil {
return res, err
return nil, err
}
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 {
return res, 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 nil, err
}
return res, nil

View File

@@ -1,22 +1,20 @@
package routes
import (
"github.com/authorizerdev/authorizer/server/constants"
"github.com/authorizerdev/authorizer/server/envstore"
"github.com/authorizerdev/authorizer/server/handlers"
"github.com/authorizerdev/authorizer/server/middlewares"
"github.com/gin-contrib/location"
"github.com/gin-gonic/gin"
)
// InitRouter initializes gin router
func InitRouter() *gin.Engine {
router := gin.Default()
router.Use(location.Default())
// router.Use(location.Default())
router.Use(middlewares.GinContextToContextMiddleware())
router.Use(middlewares.CORSMiddleware())
router.GET("/", handlers.RootHandler())
router.GET("/health", handlers.HealthHandler())
router.POST("/graphql", handlers.GraphqlHandler())
router.GET("/playground", handlers.PlaygroundHandler())
router.GET("/oauth_login/:oauth_provider", handlers.OAuthLoginHandler())
@@ -25,21 +23,21 @@ func InitRouter() *gin.Engine {
router.LoadHTMLGlob("templates/*")
// login page app related routes.
if !envstore.EnvInMemoryStoreObj.GetBoolStoreEnvVariable(constants.EnvKeyDisableLoginPage) {
app := router.Group("/app")
{
app.Static("/favicon_io", "app/favicon_io")
app.Static("/build", "app/build")
app.GET("/", handlers.AppHandler())
app.GET("/reset-password", handlers.AppHandler())
}
app.GET("/:page", handlers.AppHandler())
}
// dashboard related routes
app := router.Group("/dashboard")
dashboard := router.Group("/dashboard")
{
app.Static("/build", "dashboard/build")
app.GET("/", handlers.DashboardHandler())
app.GET("/:page", handlers.DashboardHandler())
dashboard.Static("/favicon_io", "dashboard/favicon_io")
dashboard.Static("/build", "dashboard/build")
dashboard.GET("/", handlers.DashboardHandler())
dashboard.GET("/:page", handlers.DashboardHandler())
}
return router
}

View File

@@ -138,7 +138,6 @@ func InitSession() {
}
} else {
log.Println("using in memory store to save sessions")
SessionStoreObj.InMemoryStoreObj = &InMemoryStore{
store: map[string]map[string]string{},
socialLoginState: map[string]string{},

View File

@@ -7,13 +7,12 @@ import (
"github.com/authorizerdev/authorizer/server/envstore"
"github.com/authorizerdev/authorizer/server/graph/model"
"github.com/authorizerdev/authorizer/server/resolvers"
"github.com/google/uuid"
"github.com/stretchr/testify/assert"
)
func adminSignupTests(t *testing.T, s TestSetup) {
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)
_, err := resolvers.AdminSignupResolver(ctx, model.AdminSignupInput{
AdminSecret: "admin",
@@ -24,7 +23,7 @@ func adminSignupTests(t *testing.T, s TestSetup) {
envstore.EnvInMemoryStoreObj.UpdateEnvVariable(constants.StringStoreIdentifier, constants.EnvKeyAdminSecret, "")
_, err = resolvers.AdminSignupResolver(ctx, model.AdminSignupInput{
AdminSecret: uuid.New().String(),
AdminSecret: "admin123",
})
assert.Nil(t, err)

View File

@@ -14,16 +14,13 @@ func TestEnvs(t *testing.T) {
env.InitEnv()
store := envstore.EnvInMemoryStoreObj.GetEnvStoreClone()
assert.Equal(t, store.StringEnv[constants.EnvKeyAdminSecret], "admin")
assert.Equal(t, store.StringEnv[constants.EnvKeyEnv], "production")
assert.False(t, store.BoolEnv[constants.EnvKeyDisableEmailVerification])
assert.False(t, store.BoolEnv[constants.EnvKeyDisableMagicLinkLogin])
assert.False(t, store.BoolEnv[constants.EnvKeyDisableBasicAuthentication])
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.EqualValues(t, store.SliceEnv[constants.EnvKeyRoles], []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{"*"})
}

View File

@@ -1,6 +1,7 @@
package test
import (
"log"
"testing"
"github.com/authorizerdev/authorizer/server/constants"
@@ -12,28 +13,28 @@ import (
func TestResolvers(t *testing.T) {
databases := map[string]string{
constants.DbTypeSqlite: "../../data.db",
// constants.DbTypeArangodb: "http://localhost:8529",
// constants.DbTypeMongodb: "mongodb://localhost:27017",
constants.DbTypeArangodb: "http://localhost:8529",
constants.DbTypeMongodb: "mongodb://localhost:27017",
}
envstore.EnvInMemoryStoreObj.UpdateEnvVariable(constants.StringStoreIdentifier, constants.EnvKeyVersion, "test")
for dbType, dbURL := range databases {
envstore.EnvInMemoryStoreObj.UpdateEnvVariable(constants.StringStoreIdentifier, constants.EnvKeyDatabaseURL, dbURL)
envstore.EnvInMemoryStoreObj.UpdateEnvVariable(constants.StringStoreIdentifier, constants.EnvKeyDatabaseType, dbType)
env.InitEnv()
s := testSetup()
defer s.Server.Close()
db.InitDB()
// clean the persisted config for test to use fresh config
envData, err := db.Provider.GetEnv()
log.Println("=> envData:", envstore.EnvInMemoryStoreObj.GetEnvStoreClone())
if err == nil {
envData.EnvData = []byte{}
envData.EnvData = ""
db.Provider.UpdateEnv(envData)
}
env.PersistEnv()
s := testSetup()
defer s.Server.Close()
t.Run("should pass tests for "+dbType, func(t *testing.T) {
// admin tests
adminSignupTests(t, s)

View File

@@ -14,7 +14,6 @@ import (
"github.com/authorizerdev/authorizer/server/handlers"
"github.com/authorizerdev/authorizer/server/middlewares"
"github.com/authorizerdev/authorizer/server/sessionstore"
"github.com/gin-contrib/location"
"github.com/gin-gonic/gin"
)
@@ -73,13 +72,17 @@ func testSetup() TestSetup {
}
envstore.EnvInMemoryStoreObj.UpdateEnvVariable(constants.StringStoreIdentifier, constants.EnvKeyEnvPath, "../../.env.sample")
envstore.EnvInMemoryStoreObj.UpdateEnvVariable(constants.StringStoreIdentifier, constants.EnvKeySmtpHost, "smtp.yopmail.com")
envstore.EnvInMemoryStoreObj.UpdateEnvVariable(constants.StringStoreIdentifier, constants.EnvKeySmtpPort, "2525")
envstore.EnvInMemoryStoreObj.UpdateEnvVariable(constants.StringStoreIdentifier, constants.EnvKeySmtpUsername, "lakhan@yopmail.com")
envstore.EnvInMemoryStoreObj.UpdateEnvVariable(constants.StringStoreIdentifier, constants.EnvKeySmtpPassword, "test")
envstore.EnvInMemoryStoreObj.UpdateEnvVariable(constants.StringStoreIdentifier, constants.EnvKeySenderEmail, "info@yopmail.com")
envstore.EnvInMemoryStoreObj.UpdateEnvVariable(constants.SliceStoreIdentifier, constants.EnvKeyProtectedRoles, []string{"admin"})
env.InitEnv()
sessionstore.InitSession()
w := httptest.NewRecorder()
c, r := gin.CreateTestContext(w)
r.Use(location.Default())
r.Use(middlewares.GinContextToContextMiddleware())
r.Use(middlewares.CORSMiddleware())

View File

@@ -24,7 +24,7 @@ func updateUserTest(t *testing.T, s TestSetup) {
})
user := *signupRes.User
adminRole := "admin"
adminRole := "supplier"
userRole := "user"
newRoles := []*string{&adminRole, &userRole}
_, err := resolvers.UpdateUserResolver(ctx, model.UpdateUserInput{

View File

@@ -8,9 +8,9 @@ import (
)
func TestGetHostName(t *testing.T) {
authorizer_url := "http://test.herokuapp.com:80"
url := "http://test.herokuapp.com:80"
host, port := utils.GetHostParts(authorizer_url)
host, port := utils.GetHostParts(url)
expectedHost := "test.herokuapp.com"
assert.Equal(t, host, expectedHost, "hostname should be equal")
@@ -18,9 +18,9 @@ func TestGetHostName(t *testing.T) {
}
func TestGetDomainName(t *testing.T) {
authorizer_url := "http://test.herokuapp.com"
url := "http://test.herokuapp.com"
got := utils.GetDomainName(authorizer_url)
got := utils.GetDomainName(url)
want := "herokuapp.com"
assert.Equal(t, got, want, "domain name should be equal")

View File

@@ -2,6 +2,7 @@ package test
import (
"fmt"
"log"
"testing"
"github.com/authorizerdev/authorizer/server/constants"
@@ -23,15 +24,26 @@ func usersTest(t *testing.T, s TestSetup) {
ConfirmPassword: s.TestInfo.Password,
})
users, err := resolvers.UsersResolver(ctx)
limit := int64(10)
page := int64(1)
pagination := &model.PaginatedInput{
Pagination: &model.PaginationInput{
Limit: &limit,
Page: &page,
},
}
usersRes, err := resolvers.UsersResolver(ctx, pagination)
assert.NotNil(t, err, "unauthorized")
h, err := utils.EncryptPassword(envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyAdminSecret))
assert.Nil(t, err)
req.Header.Set("Cookie", fmt.Sprintf("%s=%s", envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyAdminCookieName), h))
users, err = resolvers.UsersResolver(ctx)
usersRes, err = resolvers.UsersResolver(ctx, pagination)
assert.Nil(t, err)
rLen := len(users)
log.Println("=> userRes:", usersRes)
rLen := len(usersRes.Users)
assert.GreaterOrEqual(t, rLen, 1)
cleanData(email)

View File

@@ -25,16 +25,26 @@ func verificationRequestsTest(t *testing.T, s TestSetup) {
ConfirmPassword: s.TestInfo.Password,
})
requests, err := resolvers.VerificationRequestsResolver(ctx)
limit := int64(10)
page := int64(1)
pagination := &model.PaginatedInput{
Pagination: &model.PaginationInput{
Limit: &limit,
Page: &page,
},
}
requests, err := resolvers.VerificationRequestsResolver(ctx, pagination)
assert.NotNil(t, err, "unauthorized")
h, err := utils.EncryptPassword(envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyAdminSecret))
assert.Nil(t, err)
req.Header.Set("Cookie", fmt.Sprintf("%s=%s", envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyAdminCookieName), h))
requests, err = resolvers.VerificationRequestsResolver(ctx)
requests, err = resolvers.VerificationRequestsResolver(ctx, pagination)
assert.Nil(t, err)
rLen := len(requests)
rLen := len(requests.VerificationRequests)
assert.GreaterOrEqual(t, rLen, 1)
cleanData(email)

View File

@@ -2,7 +2,6 @@ package token
import (
"fmt"
"log"
"github.com/authorizerdev/authorizer/server/constants"
"github.com/authorizerdev/authorizer/server/cookie"
@@ -25,7 +24,7 @@ func GetAdminAuthToken(gc *gin.Context) (string, error) {
}
err = bcrypt.CompareHashAndPassword([]byte(token), []byte(envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyAdminSecret)))
log.Println("error comparing hash:", err)
if err != nil {
return "", fmt.Errorf(`unauthorized`)
}

View File

@@ -23,7 +23,7 @@ type CustomClaim struct {
}
// CreateVerificationToken creates a verification JWT token
func CreateVerificationToken(email string, tokenType string) (string, error) {
func CreateVerificationToken(email, tokenType, hostname string) (string, error) {
t := jwt.New(jwt.GetSigningMethod(envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyJwtType)))
t.Claims = &CustomClaim{
@@ -31,7 +31,7 @@ func CreateVerificationToken(email string, tokenType string) (string, error) {
ExpiresAt: time.Now().Add(time.Minute * 30).Unix(),
},
tokenType,
VerificationRequestToken{Email: email, Host: envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyAuthorizerURL), RedirectURL: envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyAppURL)},
VerificationRequestToken{Email: email, Host: hostname, RedirectURL: envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyAppURL)},
}
return t.SignedString([]byte(envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyJwtSecret)))

Some files were not shown because too many files have changed in this diff Show More