Compare commits
107 Commits
feat/add-j
...
0.18.1
Author | SHA1 | Date | |
---|---|---|---|
![]() |
d5f1c5a5eb | ||
![]() |
39947f1753 | ||
![]() |
4fa9f79c3f | ||
![]() |
fe73c2f6f8 | ||
![]() |
4a3e3633ea | ||
![]() |
dbbe36f6b5 | ||
![]() |
819dd57377 | ||
![]() |
044b025ba2 | ||
![]() |
41b5f00b83 | ||
![]() |
3c31b7fdc7 | ||
![]() |
7e91c6ca28 | ||
![]() |
b1b43a41ca | ||
![]() |
f969495178 | ||
![]() |
3c4c128931 | ||
![]() |
003cec4f48 | ||
![]() |
3e488155dc | ||
![]() |
4f4a3a91e1 | ||
![]() |
a3d9783aef | ||
![]() |
7d77396657 | ||
![]() |
7a18fc6312 | ||
![]() |
90e2709eeb | ||
![]() |
4c4743ac24 | ||
![]() |
b2541c8e9a | ||
![]() |
1f3dec6ea6 | ||
![]() |
f356b4728d | ||
![]() |
ec4ef97766 | ||
![]() |
47d67bf3cd | ||
![]() |
0c54da1168 | ||
![]() |
d6f60ce464 | ||
![]() |
3aa888b14e | ||
![]() |
30be32a10b | ||
![]() |
69d781d6cf | ||
![]() |
e4d9c60971 | ||
![]() |
96edb43b67 | ||
![]() |
21fef67c7d | ||
![]() |
9f09823c8b | ||
![]() |
1a64149da7 | ||
![]() |
99b846811a | ||
![]() |
df7837f44d | ||
![]() |
d709f53c47 | ||
![]() |
a257b77501 | ||
![]() |
2213619ed5 | ||
![]() |
f65ea72944 | ||
![]() |
32f8c99a71 | ||
![]() |
8ec52a90f1 | ||
![]() |
2498958295 | ||
![]() |
2913fa0603 | ||
![]() |
e126bfddad | ||
![]() |
83001b859c | ||
![]() |
74a8024131 | ||
![]() |
5e6ee8d9b0 | ||
![]() |
3e7150f872 | ||
![]() |
9a19552f72 | ||
![]() |
ab01ff249d | ||
![]() |
1b387f7564 | ||
![]() |
8e79ab77b2 | ||
![]() |
2bf6b8f91d | ||
![]() |
776c0fba8b | ||
![]() |
dd64aa2e79 | ||
![]() |
157b13baa7 | ||
![]() |
d1e284116d | ||
![]() |
2f9725d8e1 | ||
![]() |
ee7aea7bee | ||
![]() |
5d73df0040 | ||
![]() |
60cd317e67 | ||
![]() |
f5bdc8db39 | ||
![]() |
9eca697a91 | ||
![]() |
7136ee924d | ||
![]() |
fd9eb7c733 | ||
![]() |
917eaeb2ed | ||
![]() |
3bb90acc9e | ||
![]() |
a69b8e290c | ||
![]() |
674eeeea4e | ||
![]() |
8c2bf6ee0d | ||
![]() |
57bc091499 | ||
![]() |
128a2a8f75 | ||
![]() |
7b09a8817c | ||
![]() |
1d61840c6d | ||
![]() |
4b25e8941c | ||
![]() |
136eda15bf | ||
![]() |
eea6349318 | ||
![]() |
513b5d2948 | ||
![]() |
e61dc2f08a | ||
![]() |
07552bc0b1 | ||
![]() |
0787a3b494 | ||
![]() |
2946428ab8 | ||
![]() |
5c7d32ec16 | ||
![]() |
f0f2e0b6c8 | ||
![]() |
5399ea8f32 | ||
![]() |
4830a7e9ac | ||
![]() |
df1c56bb1c | ||
![]() |
b68d9ce661 | ||
![]() |
145091dce1 | ||
![]() |
ad46210112 | ||
![]() |
4e19f73845 | ||
![]() |
332269ecf9 | ||
![]() |
dfa96f09a0 | ||
![]() |
5bf26f7385 | ||
![]() |
1b269dc6db | ||
![]() |
ce9a115a14 | ||
![]() |
f2f4c72aa6 | ||
![]() |
9970eb16c9 | ||
![]() |
23e53286bd | ||
![]() |
47acff05e2 | ||
![]() |
5572928619 | ||
![]() |
85b4cd6339 | ||
![]() |
f0d38ab260 |
15
.github/workflows/release.yaml
vendored
15
.github/workflows/release.yaml
vendored
@@ -1,4 +1,19 @@
|
|||||||
on:
|
on:
|
||||||
|
workflow_dispatch:
|
||||||
|
inputs:
|
||||||
|
logLevel:
|
||||||
|
description: 'Log level'
|
||||||
|
required: true
|
||||||
|
default: 'warning'
|
||||||
|
type: choice
|
||||||
|
options:
|
||||||
|
- info
|
||||||
|
- warning
|
||||||
|
- debug
|
||||||
|
tags:
|
||||||
|
description: 'Tags'
|
||||||
|
required: false
|
||||||
|
type: boolean
|
||||||
release:
|
release:
|
||||||
types: [created]
|
types: [created]
|
||||||
|
|
||||||
|
3
.gitignore
vendored
3
.gitignore
vendored
@@ -11,3 +11,6 @@ data.db
|
|||||||
.DS_Store
|
.DS_Store
|
||||||
.env.local
|
.env.local
|
||||||
*.tar.gz
|
*.tar.gz
|
||||||
|
.vscode/
|
||||||
|
.yalc
|
||||||
|
yalc.lock
|
82
README.md
82
README.md
@@ -26,18 +26,16 @@
|
|||||||
- ✅ Sign-in / Sign-up with email ID and password
|
- ✅ Sign-in / Sign-up with email ID and password
|
||||||
- ✅ Secure session management
|
- ✅ Secure session management
|
||||||
- ✅ Email verification
|
- ✅ Email verification
|
||||||
|
- ✅ OAuth2 and OpenID compatible APIs
|
||||||
- ✅ APIs to update profile securely
|
- ✅ APIs to update profile securely
|
||||||
- ✅ Forgot password flow using email
|
- ✅ Forgot password flow using email
|
||||||
- ✅ Social logins (Google, Github, Facebook, more coming soon)
|
- ✅ Social logins (Google, Github, Facebook, more coming soon)
|
||||||
- ✅ Role-based access management
|
- ✅ Role-based access management
|
||||||
- ✅ Password-less login with email and magic link
|
- ✅ Password-less login with magic link login
|
||||||
|
|
||||||
## Roadmap
|
## Roadmap
|
||||||
|
|
||||||
- Support more JWT encryption algorithms (Currently supporting HS256)
|
|
||||||
- 2 Factor authentication
|
- 2 Factor authentication
|
||||||
- Back office (Admin dashboard to manage user)
|
|
||||||
- Support more database
|
|
||||||
- VueJS SDK
|
- VueJS SDK
|
||||||
- Svelte SDK
|
- Svelte SDK
|
||||||
- React Native SDK
|
- React Native SDK
|
||||||
@@ -59,35 +57,42 @@
|
|||||||
|
|
||||||
# Getting Started
|
# Getting Started
|
||||||
|
|
||||||
## Trying out Authorizer
|
## Step 1: Get Authorizer Instance
|
||||||
|
|
||||||
|
### Deploy Production Ready Instance
|
||||||
|
|
||||||
|
Deploy production ready Authorizer instance using one click deployment options available below
|
||||||
|
|
||||||
|
| **Infra provider** | **One-click link** | **Additional information** |
|
||||||
|
| :----------------: | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :----------------------------------------------------: |
|
||||||
|
| Railway.app | <a href="https://railway.app/new/template?template=https://github.com/authorizerdev/authorizer-railway&plugins=postgresql,redis"><img src="https://railway.app/button.svg" style="height: 44px" alt="Deploy on Railway"></a> | [docs](https://docs.authorizer.dev/deployment/railway) |
|
||||||
|
| Heroku | <a href="https://heroku.com/deploy?template=https://github.com/authorizerdev/authorizer-heroku"><img src="https://www.herokucdn.com/deploy/button.svg" alt="Deploy to Heroku" style="height: 44px;"></a> | [docs](https://docs.authorizer.dev/deployment/heroku) |
|
||||||
|
| Render | [](https://render.com/deploy?repo=https://github.com/authorizerdev/authorizer-render) | [docs](https://docs.authorizer.dev/deployment/render) |
|
||||||
|
|
||||||
|
### Deploy Authorizer Using Source Code
|
||||||
|
|
||||||
This guide helps you practice using Authorizer to evaluate it before you use it in a production environment. It includes instructions for installing the Authorizer server in local or standalone mode.
|
This guide helps you practice using Authorizer to evaluate it before you use it in a production environment. It includes instructions for installing the Authorizer server in local or standalone mode.
|
||||||
|
|
||||||
- [Install using source code](#install-using-source-code)
|
#### Install using source code
|
||||||
- [Install using binaries](#install-using-binaries)
|
|
||||||
- [Install instance on heroku](#install-instance-on-Heroku)
|
|
||||||
- [Install instance on railway.app](#install-instance-on-railway)
|
|
||||||
|
|
||||||
## Install using source code
|
#### Prerequisites
|
||||||
|
|
||||||
### Prerequisites
|
|
||||||
|
|
||||||
- OS: Linux or macOS or windows
|
- OS: Linux or macOS or windows
|
||||||
- Go: (Golang)(https://golang.org/dl/) >= v1.15
|
- Go: (Golang)(https://golang.org/dl/) >= v1.15
|
||||||
|
|
||||||
### Project Setup
|
#### Project Setup
|
||||||
|
|
||||||
1. Fork the [authorizer](https://github.com/authorizerdev/authorizer) repository (**Skip this step if you have access to repo**)
|
1. Fork the [authorizer](https://github.com/authorizerdev/authorizer) repository (**Skip this step if you have access to repo**)
|
||||||
2. Clone repo: `git clone https://github.com/authorizerdev/authorizer.git` or use the forked url from step 1
|
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`
|
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/)
|
4. 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`
|
5. Build Dashboard `make build-dashboard`
|
||||||
7. Build App `make build-app`
|
6. Build App `make build-app`
|
||||||
8. Build Server `make clean && make`
|
7. 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
|
> 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
|
||||||
9. Run binary `./build/server`
|
8. Run binary `./build/server`
|
||||||
|
|
||||||
## Install using binaries
|
### Deploy Authorizer using binaries
|
||||||
|
|
||||||
Deploy / Try Authorizer using binaries. With each [Authorizer Release](https://github.com/authorizerdev/authorizer/releases)
|
Deploy / Try Authorizer using binaries. With each [Authorizer Release](https://github.com/authorizerdev/authorizer/releases)
|
||||||
binaries are baked with required deployment files and bundled. You can download a specific version of it for the following operating systems:
|
binaries are baked with required deployment files and bundled. You can download a specific version of it for the following operating systems:
|
||||||
@@ -95,7 +100,7 @@ binaries are baked with required deployment files and bundled. You can download
|
|||||||
- Mac OSX
|
- Mac OSX
|
||||||
- Linux
|
- Linux
|
||||||
|
|
||||||
### Step 1: Download and unzip bundle
|
#### Download and unzip bundle
|
||||||
|
|
||||||
- Download the Bundle for the specific OS from the [release page](https://github.com/authorizerdev/authorizer/releases)
|
- Download the Bundle for the specific OS from the [release page](https://github.com/authorizerdev/authorizer/releases)
|
||||||
|
|
||||||
@@ -115,11 +120,7 @@ binaries are baked with required deployment files and bundled. You can download
|
|||||||
cd authorizer
|
cd authorizer
|
||||||
```
|
```
|
||||||
|
|
||||||
### Step 2: Configure environment variables
|
#### Step 3: Start Authorizer
|
||||||
|
|
||||||
Required environment variables are pre-configured in `.env` file. But based on the production requirements, please configure more environment variables. You can refer to [environment variables docs](/core/env) for more information.
|
|
||||||
|
|
||||||
### Step 3: Start Authorizer
|
|
||||||
|
|
||||||
- Run following command to start authorizer
|
- Run following command to start authorizer
|
||||||
|
|
||||||
@@ -131,20 +132,20 @@ Required environment variables are pre-configured in `.env` file. But based on t
|
|||||||
|
|
||||||
> Note: For mac users, you might have to give binary the permission to execute. Here is the command you can use to grant permission `xattr -d com.apple.quarantine build/server`
|
> Note: For mac users, you might have to give binary the permission to execute. Here is the command you can use to grant permission `xattr -d com.apple.quarantine build/server`
|
||||||
|
|
||||||
Deploy production ready Authorizer instance using one click deployment options available below
|
## Step 2: Setup Instance
|
||||||
|
|
||||||
| **Infra provider** | **One-click link** | **Additional information** |
|
- Open authorizer instance endpoint in browser
|
||||||
| :----------------: | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :----------------------------------------------------: |
|
- Sign up as an admin with a secure password
|
||||||
| Railway.app | <a href="https://railway.app/new/template?template=https://github.com/authorizerdev/authorizer-railway&plugins=postgresql,redis"><img src="https://railway.app/button.svg" style="height: 44px" alt="Deploy on Railway"></a> | [docs](https://docs.authorizer.dev/deployment/railway) |
|
- Configure environment variables from authorizer dashboard. Check env [docs](/core/env) for more information
|
||||||
| 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 | [](https://render.com/deploy?repo=https://github.com/authorizerdev/authorizer-render) | [docs](https://docs.authorizer.dev/deployment/render) |
|
> Note: `DATABASE_URL`, `DATABASE_TYPE` and `DATABASE_NAME` are only configurable via platform envs
|
||||||
|
|
||||||
### Things to consider
|
### Things to consider
|
||||||
|
|
||||||
- For social logins, you will need respective social platform key and secret
|
- For social logins, you will need respective social platform key and secret
|
||||||
- For having verified users, you will need an SMTP server with an email address and password using which system can send emails. The system will send a verification link to an email address. Once an email is verified then, only able to access it.
|
- For having verified users, you will need an SMTP server with an email address and password using which system can send emails. The system will send a verification link to an email address. Once an email is verified then, only able to access it.
|
||||||
> Note: One can always disable the email verification to allow open sign up, which is not recommended for production as anyone can use anyone's email address 😅
|
> Note: One can always disable the email verification to allow open sign up, which is not recommended for production as anyone can use anyone's email address 😅
|
||||||
- For persisting user sessions, you will need Redis URL (not in case of railway.app). If you do not configure a Redis server, sessions will be persisted until the instance is up or not restarted. For better response time on authorization requests/middleware, we recommend deploying Redis on the same infra/network as your authorizer server.
|
- For persisting user sessions, you will need Redis URL (not in case of railway app). If you do not configure a Redis server, sessions will be persisted until the instance is up or not restarted. For better response time on authorization requests/middleware, we recommend deploying Redis on the same infra/network as your authorizer server.
|
||||||
|
|
||||||
## Testing
|
## Testing
|
||||||
|
|
||||||
@@ -163,8 +164,9 @@ This example demonstrates how you can use [`@authorizerdev/authorizer-js`](/auth
|
|||||||
|
|
||||||
<script type="text/javascript">
|
<script type="text/javascript">
|
||||||
const authorizerRef = new authorizerdev.Authorizer({
|
const authorizerRef = new authorizerdev.Authorizer({
|
||||||
authorizerURL: `AUTHORIZER_URL`,
|
authorizerURL: `YOUR_AUTHORIZER_INSTANCE_URL`,
|
||||||
redirectURL: window.location.origin,
|
redirectURL: window.location.origin,
|
||||||
|
clientID: 'YOUR_CLIENT_ID', // obtain your client id from authorizer dashboard
|
||||||
});
|
});
|
||||||
|
|
||||||
// use the button selector as per your application
|
// use the button selector as per your application
|
||||||
@@ -175,15 +177,19 @@ This example demonstrates how you can use [`@authorizerdev/authorizer-js`](/auth
|
|||||||
});
|
});
|
||||||
|
|
||||||
async function onLoad() {
|
async function onLoad() {
|
||||||
const res = await authorizerRef.browserLogin();
|
const res = await authorizerRef.authorize({
|
||||||
if (res && res.user) {
|
response_type: 'code',
|
||||||
|
use_refresh_token: false,
|
||||||
|
});
|
||||||
|
if (res && res.access_token) {
|
||||||
// you can use user information here, eg:
|
// you can use user information here, eg:
|
||||||
/**
|
const user = await authorizerRef.getProfile({
|
||||||
|
Authorization: `Bearer ${res.access_token}`,
|
||||||
|
});
|
||||||
const userSection = document.getElementById('user');
|
const userSection = document.getElementById('user');
|
||||||
const logoutSection = document.getElementById('logout-section');
|
const logoutSection = document.getElementById('logout-section');
|
||||||
logoutSection.classList.toggle('hide');
|
logoutSection.classList.toggle('hide');
|
||||||
userSection.innerHTML = `Welcome, ${res.user.email}`;
|
userSection.innerHTML = `Welcome, ${user.email}`;
|
||||||
*/
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
onLoad();
|
onLoad();
|
||||||
|
30
app/package-lock.json
generated
30
app/package-lock.json
generated
@@ -9,7 +9,7 @@
|
|||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@authorizerdev/authorizer-react": "latest",
|
"@authorizerdev/authorizer-react": "^0.17.0",
|
||||||
"@types/react": "^17.0.15",
|
"@types/react": "^17.0.15",
|
||||||
"@types/react-dom": "^17.0.9",
|
"@types/react-dom": "^17.0.9",
|
||||||
"esbuild": "^0.12.17",
|
"esbuild": "^0.12.17",
|
||||||
@@ -24,9 +24,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@authorizerdev/authorizer-js": {
|
"node_modules/@authorizerdev/authorizer-js": {
|
||||||
"version": "0.2.1",
|
"version": "0.10.0",
|
||||||
"resolved": "https://registry.npmjs.org/@authorizerdev/authorizer-js/-/authorizer-js-0.2.1.tgz",
|
"resolved": "https://registry.npmjs.org/@authorizerdev/authorizer-js/-/authorizer-js-0.10.0.tgz",
|
||||||
"integrity": "sha512-5lQlh+nc5xTsPongfTyCSX24A1WESu/BjhmZwUNuScEOGady0qPoDHE3RBf46dpi5v05wbHCDN1IFEalX5zssQ==",
|
"integrity": "sha512-REM8FLD/Ej9gzA2zDGDAke6QFss33ubePlTDmLDmIYUuQmpHFlO5mCCS6nVsKkN7F/Bcwkmp+eUNQjkdGCaKLg==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"node-fetch": "^2.6.1"
|
"node-fetch": "^2.6.1"
|
||||||
},
|
},
|
||||||
@@ -35,11 +35,11 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@authorizerdev/authorizer-react": {
|
"node_modules/@authorizerdev/authorizer-react": {
|
||||||
"version": "0.4.3",
|
"version": "0.17.0",
|
||||||
"resolved": "https://registry.npmjs.org/@authorizerdev/authorizer-react/-/authorizer-react-0.4.3.tgz",
|
"resolved": "https://registry.npmjs.org/@authorizerdev/authorizer-react/-/authorizer-react-0.17.0.tgz",
|
||||||
"integrity": "sha512-o/wWe9zZ3ARYdjbDfhGfvOxe1YQrE1YQ+UN9pcq85YSDkbfBkOfcnJ4YxlxWdL0Obd/ErDIeQ3vskyrfvRf3sA==",
|
"integrity": "sha512-7WcNCU7hDFkVfFb8LcJXFwWiLYd8aY78z1AbNPxCa2Cw5G85PaRkzjKybP6h01ITVOHO6M03lLwPj8p6Sr6fEg==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@authorizerdev/authorizer-js": "^0.2.1",
|
"@authorizerdev/authorizer-js": "^0.10.0",
|
||||||
"final-form": "^4.20.2",
|
"final-form": "^4.20.2",
|
||||||
"react-final-form": "^6.5.3",
|
"react-final-form": "^6.5.3",
|
||||||
"styled-components": "^5.3.0"
|
"styled-components": "^5.3.0"
|
||||||
@@ -829,19 +829,19 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@authorizerdev/authorizer-js": {
|
"@authorizerdev/authorizer-js": {
|
||||||
"version": "0.2.1",
|
"version": "0.10.0",
|
||||||
"resolved": "https://registry.npmjs.org/@authorizerdev/authorizer-js/-/authorizer-js-0.2.1.tgz",
|
"resolved": "https://registry.npmjs.org/@authorizerdev/authorizer-js/-/authorizer-js-0.10.0.tgz",
|
||||||
"integrity": "sha512-5lQlh+nc5xTsPongfTyCSX24A1WESu/BjhmZwUNuScEOGady0qPoDHE3RBf46dpi5v05wbHCDN1IFEalX5zssQ==",
|
"integrity": "sha512-REM8FLD/Ej9gzA2zDGDAke6QFss33ubePlTDmLDmIYUuQmpHFlO5mCCS6nVsKkN7F/Bcwkmp+eUNQjkdGCaKLg==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"node-fetch": "^2.6.1"
|
"node-fetch": "^2.6.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@authorizerdev/authorizer-react": {
|
"@authorizerdev/authorizer-react": {
|
||||||
"version": "0.4.3",
|
"version": "0.17.0",
|
||||||
"resolved": "https://registry.npmjs.org/@authorizerdev/authorizer-react/-/authorizer-react-0.4.3.tgz",
|
"resolved": "https://registry.npmjs.org/@authorizerdev/authorizer-react/-/authorizer-react-0.17.0.tgz",
|
||||||
"integrity": "sha512-o/wWe9zZ3ARYdjbDfhGfvOxe1YQrE1YQ+UN9pcq85YSDkbfBkOfcnJ4YxlxWdL0Obd/ErDIeQ3vskyrfvRf3sA==",
|
"integrity": "sha512-7WcNCU7hDFkVfFb8LcJXFwWiLYd8aY78z1AbNPxCa2Cw5G85PaRkzjKybP6h01ITVOHO6M03lLwPj8p6Sr6fEg==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"@authorizerdev/authorizer-js": "^0.2.1",
|
"@authorizerdev/authorizer-js": "^0.10.0",
|
||||||
"final-form": "^4.20.2",
|
"final-form": "^4.20.2",
|
||||||
"react-final-form": "^6.5.3",
|
"react-final-form": "^6.5.3",
|
||||||
"styled-components": "^5.3.0"
|
"styled-components": "^5.3.0"
|
||||||
|
@@ -11,7 +11,7 @@
|
|||||||
"author": "Lakhan Samani",
|
"author": "Lakhan Samani",
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@authorizerdev/authorizer-react": "latest",
|
"@authorizerdev/authorizer-react": "^0.17.0",
|
||||||
"@types/react": "^17.0.15",
|
"@types/react": "^17.0.15",
|
||||||
"@types/react-dom": "^17.0.9",
|
"@types/react-dom": "^17.0.9",
|
||||||
"esbuild": "^0.12.17",
|
"esbuild": "^0.12.17",
|
||||||
|
@@ -2,10 +2,33 @@ import React from 'react';
|
|||||||
import { BrowserRouter } from 'react-router-dom';
|
import { BrowserRouter } from 'react-router-dom';
|
||||||
import { AuthorizerProvider } from '@authorizerdev/authorizer-react';
|
import { AuthorizerProvider } from '@authorizerdev/authorizer-react';
|
||||||
import Root from './Root';
|
import Root from './Root';
|
||||||
|
import { createRandomString } from './utils/common';
|
||||||
|
|
||||||
export default function App() {
|
export default function App() {
|
||||||
|
const searchParams = new URLSearchParams(window.location.search);
|
||||||
|
const state = searchParams.get('state') || createRandomString();
|
||||||
|
const scope = searchParams.get('scope')
|
||||||
|
? searchParams.get('scope')?.toString().split(' ')
|
||||||
|
: `openid profile email`;
|
||||||
|
|
||||||
|
const urlProps: Record<string, any> = {
|
||||||
|
state,
|
||||||
|
scope,
|
||||||
|
};
|
||||||
|
|
||||||
|
const redirectURL =
|
||||||
|
searchParams.get('redirect_uri') || searchParams.get('redirectURL');
|
||||||
|
if (redirectURL) {
|
||||||
|
urlProps.redirectURL = redirectURL;
|
||||||
|
} else {
|
||||||
|
urlProps.redirectURL = window.location.origin + '/app';
|
||||||
|
}
|
||||||
|
const globalState: Record<string, string> = {
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
const globalState: Record<string, string> = window['__authorizer__'];
|
...window['__authorizer__'],
|
||||||
|
...urlProps,
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
style={{
|
style={{
|
||||||
@@ -30,15 +53,7 @@ export default function App() {
|
|||||||
/>
|
/>
|
||||||
<h1>{globalState.organizationName}</h1>
|
<h1>{globalState.organizationName}</h1>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div className="container">
|
||||||
style={{
|
|
||||||
width: 400,
|
|
||||||
margin: `10px auto`,
|
|
||||||
border: `1px solid #D1D5DB`,
|
|
||||||
padding: `25px 20px`,
|
|
||||||
borderRadius: 5,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<BrowserRouter>
|
<BrowserRouter>
|
||||||
<AuthorizerProvider
|
<AuthorizerProvider
|
||||||
config={{
|
config={{
|
||||||
@@ -46,7 +61,7 @@ export default function App() {
|
|||||||
redirectURL: globalState.redirectURL,
|
redirectURL: globalState.redirectURL,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Root />
|
<Root globalState={globalState} />
|
||||||
</AuthorizerProvider>
|
</AuthorizerProvider>
|
||||||
</BrowserRouter>
|
</BrowserRouter>
|
||||||
</div>
|
</div>
|
||||||
|
@@ -1,19 +1,36 @@
|
|||||||
import React, { useEffect, lazy, Suspense } from 'react';
|
import React, { useEffect, lazy, Suspense } from 'react';
|
||||||
import { Switch, Route } from 'react-router-dom';
|
import { Switch, Route } from 'react-router-dom';
|
||||||
import { useAuthorizer } from '@authorizerdev/authorizer-react';
|
import { useAuthorizer } from '@authorizerdev/authorizer-react';
|
||||||
|
import SetupPassword from './pages/setup-password';
|
||||||
|
|
||||||
const ResetPassword = lazy(() => import('./pages/rest-password'));
|
const ResetPassword = lazy(() => import('./pages/rest-password'));
|
||||||
const Login = lazy(() => import('./pages/login'));
|
const Login = lazy(() => import('./pages/login'));
|
||||||
const Dashboard = lazy(() => import('./pages/dashboard'));
|
const Dashboard = lazy(() => import('./pages/dashboard'));
|
||||||
|
|
||||||
export default function Root() {
|
export default function Root({
|
||||||
|
globalState,
|
||||||
|
}: {
|
||||||
|
globalState: Record<string, string>;
|
||||||
|
}) {
|
||||||
const { token, loading, config } = useAuthorizer();
|
const { token, loading, config } = useAuthorizer();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (token) {
|
if (token) {
|
||||||
const url = new URL(config.redirectURL || '/app');
|
let redirectURL = config.redirectURL || '/app';
|
||||||
|
let params = `access_token=${token.access_token}&id_token=${token.id_token}&expires_in=${token.expires_in}&state=${globalState.state}`;
|
||||||
|
if (token.refresh_token) {
|
||||||
|
params += `&refresh_token=${token.refresh_token}`;
|
||||||
|
}
|
||||||
|
const url = new URL(redirectURL);
|
||||||
|
if (redirectURL.includes('?')) {
|
||||||
|
redirectURL = `${redirectURL}&${params}`;
|
||||||
|
} else {
|
||||||
|
redirectURL = `${redirectURL}?${params}`;
|
||||||
|
}
|
||||||
|
|
||||||
if (url.origin !== window.location.origin) {
|
if (url.origin !== window.location.origin) {
|
||||||
window.location.href = config.redirectURL || '/app';
|
sessionStorage.removeItem('authorizer_state');
|
||||||
|
window.location.replace(redirectURL);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return () => {};
|
return () => {};
|
||||||
@@ -44,6 +61,9 @@ export default function Root() {
|
|||||||
<Route path="/app/reset-password">
|
<Route path="/app/reset-password">
|
||||||
<ResetPassword />
|
<ResetPassword />
|
||||||
</Route>
|
</Route>
|
||||||
|
<Route path="/app/setup-password">
|
||||||
|
<SetupPassword />
|
||||||
|
</Route>
|
||||||
</Switch>
|
</Switch>
|
||||||
</Suspense>
|
</Suspense>
|
||||||
);
|
);
|
||||||
|
@@ -1,5 +1,5 @@
|
|||||||
body {
|
body {
|
||||||
margin: 0;
|
margin: 10;
|
||||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
|
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
|
||||||
'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
|
'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
|
||||||
sans-serif;
|
sans-serif;
|
||||||
@@ -14,3 +14,17 @@ body {
|
|||||||
*:after {
|
*:after {
|
||||||
box-sizing: inherit;
|
box-sizing: inherit;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.container {
|
||||||
|
box-sizing: content-box;
|
||||||
|
border: 1px solid #d1d5db;
|
||||||
|
padding: 25px 20px;
|
||||||
|
border-radius: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media only screen and (min-width: 768px) {
|
||||||
|
.container {
|
||||||
|
width: 400px;
|
||||||
|
margin: 0 auto;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
12
app/src/pages/setup-password.tsx
Normal file
12
app/src/pages/setup-password.tsx
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
import React, { Fragment } from 'react';
|
||||||
|
import { AuthorizerResetPassword } from '@authorizerdev/authorizer-react';
|
||||||
|
|
||||||
|
export default function SetupPassword() {
|
||||||
|
return (
|
||||||
|
<Fragment>
|
||||||
|
<h1 style={{ textAlign: 'center' }}>Setup new Password</h1>
|
||||||
|
<br />
|
||||||
|
<AuthorizerResetPassword />
|
||||||
|
</Fragment>
|
||||||
|
);
|
||||||
|
}
|
22
app/src/utils/common.ts
Normal file
22
app/src/utils/common.ts
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
export const getCrypto = () => {
|
||||||
|
//ie 11.x uses msCrypto
|
||||||
|
return (window.crypto || (window as any).msCrypto) as Crypto;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const createRandomString = () => {
|
||||||
|
const charset =
|
||||||
|
'0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz-_~.';
|
||||||
|
let random = '';
|
||||||
|
const randomValues = Array.from(
|
||||||
|
getCrypto().getRandomValues(new Uint8Array(43))
|
||||||
|
);
|
||||||
|
randomValues.forEach((v) => (random += charset[v % charset.length]));
|
||||||
|
return random;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const createQueryParams = (params: any) => {
|
||||||
|
return Object.keys(params)
|
||||||
|
.filter((k) => typeof params[k] !== 'undefined')
|
||||||
|
.map((k) => encodeURIComponent(k) + '=' + encodeURIComponent(params[k]))
|
||||||
|
.join('&');
|
||||||
|
};
|
71
dashboard/package-lock.json
generated
71
dashboard/package-lock.json
generated
@@ -22,6 +22,7 @@
|
|||||||
"lodash": "^4.17.21",
|
"lodash": "^4.17.21",
|
||||||
"react": "^17.0.2",
|
"react": "^17.0.2",
|
||||||
"react-dom": "^17.0.2",
|
"react-dom": "^17.0.2",
|
||||||
|
"react-dropzone": "^12.0.4",
|
||||||
"react-icons": "^4.3.1",
|
"react-icons": "^4.3.1",
|
||||||
"react-router-dom": "^6.2.1",
|
"react-router-dom": "^6.2.1",
|
||||||
"typescript": "^4.5.4",
|
"typescript": "^4.5.4",
|
||||||
@@ -1251,6 +1252,14 @@
|
|||||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz",
|
"resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz",
|
||||||
"integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg=="
|
"integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg=="
|
||||||
},
|
},
|
||||||
|
"node_modules/attr-accept": {
|
||||||
|
"version": "2.2.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/attr-accept/-/attr-accept-2.2.2.tgz",
|
||||||
|
"integrity": "sha512-7prDjvt9HmqiZ0cl5CRjtS84sEyhsHP2coDkaZKRKVfCDo9s7iw7ChVmar78Gu9pC4SoR/28wFu/G5JJhTnqEg==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=4"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/babel-plugin-macros": {
|
"node_modules/babel-plugin-macros": {
|
||||||
"version": "2.8.0",
|
"version": "2.8.0",
|
||||||
"resolved": "https://registry.npmjs.org/babel-plugin-macros/-/babel-plugin-macros-2.8.0.tgz",
|
"resolved": "https://registry.npmjs.org/babel-plugin-macros/-/babel-plugin-macros-2.8.0.tgz",
|
||||||
@@ -1631,6 +1640,17 @@
|
|||||||
"url": "https://github.com/sponsors/sindresorhus"
|
"url": "https://github.com/sponsors/sindresorhus"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/file-selector": {
|
||||||
|
"version": "0.4.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/file-selector/-/file-selector-0.4.0.tgz",
|
||||||
|
"integrity": "sha512-iACCiXeMYOvZqlF1kTiYINzgepRBymz1wwjiuup9u9nayhb6g4fSwiyJ/6adli+EPwrWtpgQAh2PoS7HukEGEg==",
|
||||||
|
"dependencies": {
|
||||||
|
"tslib": "^2.0.3"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 10"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/find-root": {
|
"node_modules/find-root": {
|
||||||
"version": "1.1.0",
|
"version": "1.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/find-root/-/find-root-1.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/find-root/-/find-root-1.1.0.tgz",
|
||||||
@@ -1914,9 +1934,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/prop-types": {
|
"node_modules/prop-types": {
|
||||||
"version": "15.8.0",
|
"version": "15.8.1",
|
||||||
"resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.0.tgz",
|
"resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz",
|
||||||
"integrity": "sha512-fDGekdaHh65eI3lMi5OnErU6a8Ighg2KjcjQxO7m8VHyWjcPyj5kiOgV1LQDOOOgVy3+5FgjXvdSSX7B8/5/4g==",
|
"integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"loose-envify": "^1.4.0",
|
"loose-envify": "^1.4.0",
|
||||||
"object-assign": "^4.1.1",
|
"object-assign": "^4.1.1",
|
||||||
@@ -1959,6 +1979,22 @@
|
|||||||
"react": "17.0.2"
|
"react": "17.0.2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/react-dropzone": {
|
||||||
|
"version": "12.0.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/react-dropzone/-/react-dropzone-12.0.4.tgz",
|
||||||
|
"integrity": "sha512-fcqHEYe1MzAghU6/Hz86lHDlBNsA+lO48nAcm7/wA+kIzwS6uuJbUG33tBZjksj7GAZ1iUQ6NHwjUURPmSGang==",
|
||||||
|
"dependencies": {
|
||||||
|
"attr-accept": "^2.2.2",
|
||||||
|
"file-selector": "^0.4.0",
|
||||||
|
"prop-types": "^15.8.1"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 10.13"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": ">= 16.8"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/react-fast-compare": {
|
"node_modules/react-fast-compare": {
|
||||||
"version": "3.2.0",
|
"version": "3.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/react-fast-compare/-/react-fast-compare-3.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/react-fast-compare/-/react-fast-compare-3.2.0.tgz",
|
||||||
@@ -3226,6 +3262,11 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"attr-accept": {
|
||||||
|
"version": "2.2.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/attr-accept/-/attr-accept-2.2.2.tgz",
|
||||||
|
"integrity": "sha512-7prDjvt9HmqiZ0cl5CRjtS84sEyhsHP2coDkaZKRKVfCDo9s7iw7ChVmar78Gu9pC4SoR/28wFu/G5JJhTnqEg=="
|
||||||
|
},
|
||||||
"babel-plugin-macros": {
|
"babel-plugin-macros": {
|
||||||
"version": "2.8.0",
|
"version": "2.8.0",
|
||||||
"resolved": "https://registry.npmjs.org/babel-plugin-macros/-/babel-plugin-macros-2.8.0.tgz",
|
"resolved": "https://registry.npmjs.org/babel-plugin-macros/-/babel-plugin-macros-2.8.0.tgz",
|
||||||
@@ -3478,6 +3519,14 @@
|
|||||||
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz",
|
||||||
"integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA=="
|
"integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA=="
|
||||||
},
|
},
|
||||||
|
"file-selector": {
|
||||||
|
"version": "0.4.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/file-selector/-/file-selector-0.4.0.tgz",
|
||||||
|
"integrity": "sha512-iACCiXeMYOvZqlF1kTiYINzgepRBymz1wwjiuup9u9nayhb6g4fSwiyJ/6adli+EPwrWtpgQAh2PoS7HukEGEg==",
|
||||||
|
"requires": {
|
||||||
|
"tslib": "^2.0.3"
|
||||||
|
}
|
||||||
|
},
|
||||||
"find-root": {
|
"find-root": {
|
||||||
"version": "1.1.0",
|
"version": "1.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/find-root/-/find-root-1.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/find-root/-/find-root-1.1.0.tgz",
|
||||||
@@ -3707,9 +3756,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"prop-types": {
|
"prop-types": {
|
||||||
"version": "15.8.0",
|
"version": "15.8.1",
|
||||||
"resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.0.tgz",
|
"resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz",
|
||||||
"integrity": "sha512-fDGekdaHh65eI3lMi5OnErU6a8Ighg2KjcjQxO7m8VHyWjcPyj5kiOgV1LQDOOOgVy3+5FgjXvdSSX7B8/5/4g==",
|
"integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"loose-envify": "^1.4.0",
|
"loose-envify": "^1.4.0",
|
||||||
"object-assign": "^4.1.1",
|
"object-assign": "^4.1.1",
|
||||||
@@ -3743,6 +3792,16 @@
|
|||||||
"scheduler": "^0.20.2"
|
"scheduler": "^0.20.2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"react-dropzone": {
|
||||||
|
"version": "12.0.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/react-dropzone/-/react-dropzone-12.0.4.tgz",
|
||||||
|
"integrity": "sha512-fcqHEYe1MzAghU6/Hz86lHDlBNsA+lO48nAcm7/wA+kIzwS6uuJbUG33tBZjksj7GAZ1iUQ6NHwjUURPmSGang==",
|
||||||
|
"requires": {
|
||||||
|
"attr-accept": "^2.2.2",
|
||||||
|
"file-selector": "^0.4.0",
|
||||||
|
"prop-types": "^15.8.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
"react-fast-compare": {
|
"react-fast-compare": {
|
||||||
"version": "3.2.0",
|
"version": "3.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/react-fast-compare/-/react-fast-compare-3.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/react-fast-compare/-/react-fast-compare-3.2.0.tgz",
|
||||||
|
@@ -24,6 +24,7 @@
|
|||||||
"lodash": "^4.17.21",
|
"lodash": "^4.17.21",
|
||||||
"react": "^17.0.2",
|
"react": "^17.0.2",
|
||||||
"react-dom": "^17.0.2",
|
"react-dom": "^17.0.2",
|
||||||
|
"react-dropzone": "^12.0.4",
|
||||||
"react-icons": "^4.3.1",
|
"react-icons": "^4.3.1",
|
||||||
"react-router-dom": "^6.2.1",
|
"react-router-dom": "^6.2.1",
|
||||||
"typescript": "^4.5.4",
|
"typescript": "^4.5.4",
|
||||||
|
1
dashboard/public/sample.csv
Normal file
1
dashboard/public/sample.csv
Normal file
@@ -0,0 +1 @@
|
|||||||
|
foo@bar.com,test@authorizer.dev
|
|
@@ -10,6 +10,9 @@ const queryClient = createClient({
|
|||||||
fetchOptions: () => {
|
fetchOptions: () => {
|
||||||
return {
|
return {
|
||||||
credentials: 'include',
|
credentials: 'include',
|
||||||
|
headers: {
|
||||||
|
'x-authorizer-url': window.location.origin,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
requestPolicy: 'network-only',
|
requestPolicy: 'network-only',
|
||||||
|
247
dashboard/src/components/GenerateKeysModal.tsx
Normal file
247
dashboard/src/components/GenerateKeysModal.tsx
Normal file
@@ -0,0 +1,247 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import {
|
||||||
|
Button,
|
||||||
|
Center,
|
||||||
|
Flex,
|
||||||
|
Modal,
|
||||||
|
ModalBody,
|
||||||
|
ModalCloseButton,
|
||||||
|
ModalContent,
|
||||||
|
ModalFooter,
|
||||||
|
ModalHeader,
|
||||||
|
ModalOverlay,
|
||||||
|
useDisclosure,
|
||||||
|
Text,
|
||||||
|
useToast,
|
||||||
|
Input,
|
||||||
|
Spinner,
|
||||||
|
} from '@chakra-ui/react';
|
||||||
|
import { useClient } from 'urql';
|
||||||
|
import { FaSave } from 'react-icons/fa';
|
||||||
|
import {
|
||||||
|
ECDSAEncryptionType,
|
||||||
|
HMACEncryptionType,
|
||||||
|
RSAEncryptionType,
|
||||||
|
SelectInputType,
|
||||||
|
TextAreaInputType,
|
||||||
|
} from '../constants';
|
||||||
|
import InputField from './InputField';
|
||||||
|
import { GenerateKeys, UpdateEnvVariables } from '../graphql/mutation';
|
||||||
|
|
||||||
|
interface propTypes {
|
||||||
|
jwtType: string;
|
||||||
|
getData: Function;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface stateVarTypes {
|
||||||
|
JWT_TYPE: string;
|
||||||
|
JWT_SECRET: string;
|
||||||
|
JWT_PRIVATE_KEY: string;
|
||||||
|
JWT_PUBLIC_KEY: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const initState: stateVarTypes = {
|
||||||
|
JWT_TYPE: '',
|
||||||
|
JWT_SECRET: '',
|
||||||
|
JWT_PRIVATE_KEY: '',
|
||||||
|
JWT_PUBLIC_KEY: '',
|
||||||
|
};
|
||||||
|
|
||||||
|
const GenerateKeysModal = ({ jwtType, getData }: propTypes) => {
|
||||||
|
const client = useClient();
|
||||||
|
const toast = useToast();
|
||||||
|
const { isOpen, onOpen, onClose } = useDisclosure();
|
||||||
|
const [stateVariables, setStateVariables] = React.useState<stateVarTypes>({
|
||||||
|
...initState,
|
||||||
|
});
|
||||||
|
const [isLoading, setIsLoading] = React.useState(false);
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
if (isOpen) {
|
||||||
|
setStateVariables({ ...initState, JWT_TYPE: jwtType });
|
||||||
|
}
|
||||||
|
}, [isOpen]);
|
||||||
|
|
||||||
|
const fetchKeys = async () => {
|
||||||
|
setIsLoading(true);
|
||||||
|
try {
|
||||||
|
const res = await client
|
||||||
|
.mutation(GenerateKeys, { params: { type: stateVariables.JWT_TYPE } })
|
||||||
|
.toPromise();
|
||||||
|
if (res?.error) {
|
||||||
|
toast({
|
||||||
|
title: 'Error occurred generating jwt keys',
|
||||||
|
isClosable: true,
|
||||||
|
status: 'error',
|
||||||
|
position: 'bottom-right',
|
||||||
|
});
|
||||||
|
closeHandler();
|
||||||
|
} else {
|
||||||
|
setStateVariables({
|
||||||
|
...stateVariables,
|
||||||
|
JWT_SECRET: res?.data?._generate_jwt_keys?.secret || '',
|
||||||
|
JWT_PRIVATE_KEY: res?.data?._generate_jwt_keys?.private_key || '',
|
||||||
|
JWT_PUBLIC_KEY: res?.data?._generate_jwt_keys?.public_key || '',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.log(error);
|
||||||
|
} finally {
|
||||||
|
setIsLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
if (isOpen && stateVariables.JWT_TYPE) {
|
||||||
|
fetchKeys();
|
||||||
|
}
|
||||||
|
}, [stateVariables.JWT_TYPE]);
|
||||||
|
|
||||||
|
const saveHandler = async () => {
|
||||||
|
const res = await client
|
||||||
|
.mutation(UpdateEnvVariables, { params: { ...stateVariables } })
|
||||||
|
.toPromise();
|
||||||
|
|
||||||
|
if (res.error) {
|
||||||
|
toast({
|
||||||
|
title: 'Error occurred setting jwt keys',
|
||||||
|
isClosable: true,
|
||||||
|
status: 'error',
|
||||||
|
position: 'bottom-right',
|
||||||
|
});
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
toast({
|
||||||
|
title: 'JWT keys updated successfully',
|
||||||
|
isClosable: true,
|
||||||
|
status: 'success',
|
||||||
|
position: 'bottom-right',
|
||||||
|
});
|
||||||
|
closeHandler();
|
||||||
|
};
|
||||||
|
|
||||||
|
const closeHandler = () => {
|
||||||
|
setStateVariables({ ...initState });
|
||||||
|
getData();
|
||||||
|
onClose();
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Button
|
||||||
|
colorScheme="blue"
|
||||||
|
h="1.75rem"
|
||||||
|
size="sm"
|
||||||
|
variant="ghost"
|
||||||
|
onClick={onOpen}
|
||||||
|
>
|
||||||
|
Generate new keys
|
||||||
|
</Button>
|
||||||
|
<Modal isOpen={isOpen} onClose={closeHandler}>
|
||||||
|
<ModalOverlay />
|
||||||
|
<ModalContent>
|
||||||
|
<ModalHeader>New JWT keys</ModalHeader>
|
||||||
|
<ModalCloseButton />
|
||||||
|
<ModalBody>
|
||||||
|
<Flex>
|
||||||
|
<Flex w="30%" justifyContent="start" alignItems="center">
|
||||||
|
<Text fontSize="sm">JWT Type:</Text>
|
||||||
|
</Flex>
|
||||||
|
<InputField
|
||||||
|
variables={stateVariables}
|
||||||
|
setVariables={setStateVariables}
|
||||||
|
inputType={SelectInputType.JWT_TYPE}
|
||||||
|
value={SelectInputType.JWT_TYPE}
|
||||||
|
options={{
|
||||||
|
...HMACEncryptionType,
|
||||||
|
...RSAEncryptionType,
|
||||||
|
...ECDSAEncryptionType,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Flex>
|
||||||
|
{isLoading ? (
|
||||||
|
<Center minH="25vh">
|
||||||
|
<Spinner />
|
||||||
|
</Center>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
{Object.values(HMACEncryptionType).includes(
|
||||||
|
stateVariables.JWT_TYPE
|
||||||
|
) ? (
|
||||||
|
<Flex marginTop="8">
|
||||||
|
<Flex w="23%" justifyContent="start" alignItems="center">
|
||||||
|
<Text fontSize="sm">JWT Secret</Text>
|
||||||
|
</Flex>
|
||||||
|
<Center w="77%">
|
||||||
|
<Input
|
||||||
|
size="sm"
|
||||||
|
value={stateVariables.JWT_SECRET}
|
||||||
|
onChange={(event: any) =>
|
||||||
|
setStateVariables({
|
||||||
|
...stateVariables,
|
||||||
|
JWT_SECRET: event.target.value,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
readOnly
|
||||||
|
/>
|
||||||
|
</Center>
|
||||||
|
</Flex>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<Flex marginTop="8">
|
||||||
|
<Flex w="23%" justifyContent="start" alignItems="center">
|
||||||
|
<Text fontSize="sm">Public Key</Text>
|
||||||
|
</Flex>
|
||||||
|
<Center w="77%">
|
||||||
|
<InputField
|
||||||
|
variables={stateVariables}
|
||||||
|
setVariables={setStateVariables}
|
||||||
|
inputType={TextAreaInputType.JWT_PUBLIC_KEY}
|
||||||
|
placeholder="Add public key here"
|
||||||
|
minH="25vh"
|
||||||
|
readOnly
|
||||||
|
/>
|
||||||
|
</Center>
|
||||||
|
</Flex>
|
||||||
|
<Flex marginTop="8">
|
||||||
|
<Flex w="23%" justifyContent="start" alignItems="center">
|
||||||
|
<Text fontSize="sm">Private Key</Text>
|
||||||
|
</Flex>
|
||||||
|
<Center w="77%">
|
||||||
|
<InputField
|
||||||
|
variables={stateVariables}
|
||||||
|
setVariables={setStateVariables}
|
||||||
|
inputType={TextAreaInputType.JWT_PRIVATE_KEY}
|
||||||
|
placeholder="Add private key here"
|
||||||
|
minH="25vh"
|
||||||
|
readOnly
|
||||||
|
/>
|
||||||
|
</Center>
|
||||||
|
</Flex>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</ModalBody>
|
||||||
|
|
||||||
|
<ModalFooter>
|
||||||
|
<Button
|
||||||
|
leftIcon={<FaSave />}
|
||||||
|
colorScheme="blue"
|
||||||
|
variant="solid"
|
||||||
|
onClick={saveHandler}
|
||||||
|
isDisabled={isLoading}
|
||||||
|
>
|
||||||
|
<Center h="100%" pt="5%">
|
||||||
|
Apply
|
||||||
|
</Center>
|
||||||
|
</Button>
|
||||||
|
</ModalFooter>
|
||||||
|
</ModalContent>
|
||||||
|
</Modal>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default GenerateKeysModal;
|
@@ -259,17 +259,6 @@ const InputField = ({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
if (Object.values(SelectInputType).includes(inputType)) {
|
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;
|
const { options, ...rest } = props;
|
||||||
return (
|
return (
|
||||||
<Select
|
<Select
|
||||||
@@ -293,10 +282,18 @@ const InputField = ({
|
|||||||
<Textarea
|
<Textarea
|
||||||
{...props}
|
{...props}
|
||||||
size="lg"
|
size="lg"
|
||||||
value={inputData[inputType]}
|
fontSize={14}
|
||||||
onChange={(e: any) => {
|
value={variables[inputType] ? variables[inputType] : ''}
|
||||||
setInputData({ ...inputData, [inputType]: e.target.value });
|
onChange={(
|
||||||
}}
|
event: Event & {
|
||||||
|
target: HTMLInputElement;
|
||||||
|
}
|
||||||
|
) =>
|
||||||
|
setVariables({
|
||||||
|
...variables,
|
||||||
|
[inputType]: event.target.value,
|
||||||
|
})
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
369
dashboard/src/components/InviteMembersModal.tsx
Normal file
369
dashboard/src/components/InviteMembersModal.tsx
Normal file
@@ -0,0 +1,369 @@
|
|||||||
|
import React, { useState, useCallback, useEffect } from 'react';
|
||||||
|
import {
|
||||||
|
Button,
|
||||||
|
Center,
|
||||||
|
Flex,
|
||||||
|
Modal,
|
||||||
|
ModalBody,
|
||||||
|
ModalCloseButton,
|
||||||
|
ModalContent,
|
||||||
|
ModalFooter,
|
||||||
|
ModalHeader,
|
||||||
|
ModalOverlay,
|
||||||
|
useDisclosure,
|
||||||
|
useToast,
|
||||||
|
Tabs,
|
||||||
|
TabList,
|
||||||
|
Tab,
|
||||||
|
TabPanels,
|
||||||
|
TabPanel,
|
||||||
|
InputGroup,
|
||||||
|
Input,
|
||||||
|
InputRightElement,
|
||||||
|
Text,
|
||||||
|
Link,
|
||||||
|
} from '@chakra-ui/react';
|
||||||
|
import { useClient } from 'urql';
|
||||||
|
import { FaUserPlus, FaMinusCircle, FaPlus, FaUpload } from 'react-icons/fa';
|
||||||
|
import { useDropzone } from 'react-dropzone';
|
||||||
|
import { validateEmail, validateURI } from '../utils';
|
||||||
|
import { InviteMembers } from '../graphql/mutation';
|
||||||
|
import { ArrayInputOperations } from '../constants';
|
||||||
|
import parseCSV from '../utils/parseCSV';
|
||||||
|
|
||||||
|
interface stateDataTypes {
|
||||||
|
value: string;
|
||||||
|
isInvalid: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface requestParamTypes {
|
||||||
|
emails: string[];
|
||||||
|
redirect_uri?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const initData: stateDataTypes = {
|
||||||
|
value: '',
|
||||||
|
isInvalid: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
const InviteMembersModal = ({
|
||||||
|
updateUserList,
|
||||||
|
disabled = true,
|
||||||
|
}: {
|
||||||
|
updateUserList: Function;
|
||||||
|
disabled: boolean;
|
||||||
|
}) => {
|
||||||
|
const client = useClient();
|
||||||
|
const toast = useToast();
|
||||||
|
const { isOpen, onOpen, onClose } = useDisclosure();
|
||||||
|
const [tabIndex, setTabIndex] = useState<number>(0);
|
||||||
|
const [redirectURI, setRedirectURI] = useState<stateDataTypes>({
|
||||||
|
...initData,
|
||||||
|
});
|
||||||
|
const [emails, setEmails] = useState<stateDataTypes[]>([{ ...initData }]);
|
||||||
|
const [disableSendButton, setDisableSendButton] = useState<boolean>(false);
|
||||||
|
const [loading, setLoading] = React.useState<boolean>(false);
|
||||||
|
useEffect(() => {
|
||||||
|
if (redirectURI.isInvalid) {
|
||||||
|
setDisableSendButton(true);
|
||||||
|
} else if (emails.some((emailData) => emailData.isInvalid)) {
|
||||||
|
setDisableSendButton(true);
|
||||||
|
} else {
|
||||||
|
setDisableSendButton(false);
|
||||||
|
}
|
||||||
|
}, [redirectURI, emails]);
|
||||||
|
useEffect(() => {
|
||||||
|
return () => {
|
||||||
|
setRedirectURI({ ...initData });
|
||||||
|
setEmails([{ ...initData }]);
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
const sendInviteHandler = async () => {
|
||||||
|
setLoading(true);
|
||||||
|
try {
|
||||||
|
const emailList = emails
|
||||||
|
.filter((emailData) => !emailData.isInvalid)
|
||||||
|
.map((emailData) => emailData.value);
|
||||||
|
const params: requestParamTypes = {
|
||||||
|
emails: emailList,
|
||||||
|
};
|
||||||
|
if (redirectURI.value !== '' && !redirectURI.isInvalid) {
|
||||||
|
params.redirect_uri = redirectURI.value;
|
||||||
|
}
|
||||||
|
if (emailList.length > 0) {
|
||||||
|
const res = await client
|
||||||
|
.mutation(InviteMembers, {
|
||||||
|
params,
|
||||||
|
})
|
||||||
|
.toPromise();
|
||||||
|
if (res.error) {
|
||||||
|
throw new Error('Internal server error');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
toast({
|
||||||
|
title: 'Invites sent successfully!',
|
||||||
|
isClosable: true,
|
||||||
|
status: 'success',
|
||||||
|
position: 'bottom-right',
|
||||||
|
});
|
||||||
|
setLoading(false);
|
||||||
|
updateUserList();
|
||||||
|
} else {
|
||||||
|
throw new Error('Please add emails');
|
||||||
|
}
|
||||||
|
} catch (error: any) {
|
||||||
|
toast({
|
||||||
|
title: error?.message || 'Error occurred, try again!',
|
||||||
|
isClosable: true,
|
||||||
|
status: 'error',
|
||||||
|
position: 'bottom-right',
|
||||||
|
});
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
closeModalHandler();
|
||||||
|
};
|
||||||
|
const updateEmailListHandler = (operation: string, index: number = 0) => {
|
||||||
|
switch (operation) {
|
||||||
|
case ArrayInputOperations.APPEND:
|
||||||
|
setEmails([...emails, { ...initData }]);
|
||||||
|
break;
|
||||||
|
case ArrayInputOperations.REMOVE:
|
||||||
|
const updatedEmailList = [...emails];
|
||||||
|
updatedEmailList.splice(index, 1);
|
||||||
|
setEmails(updatedEmailList);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const inputChangeHandler = (value: string, index: number) => {
|
||||||
|
const updatedEmailList = [...emails];
|
||||||
|
updatedEmailList[index].value = value;
|
||||||
|
updatedEmailList[index].isInvalid = !validateEmail(value);
|
||||||
|
setEmails(updatedEmailList);
|
||||||
|
};
|
||||||
|
const changeTabsHandler = (index: number) => {
|
||||||
|
setTabIndex(index);
|
||||||
|
};
|
||||||
|
const onDrop = useCallback(async (acceptedFiles) => {
|
||||||
|
const result = await parseCSV(acceptedFiles[0], ',');
|
||||||
|
setEmails(result);
|
||||||
|
changeTabsHandler(0);
|
||||||
|
}, []);
|
||||||
|
const setRedirectURIHandler = (value: string) => {
|
||||||
|
const updatedRedirectURI: stateDataTypes = {
|
||||||
|
value: '',
|
||||||
|
isInvalid: false,
|
||||||
|
};
|
||||||
|
updatedRedirectURI.value = value;
|
||||||
|
updatedRedirectURI.isInvalid = !validateURI(value);
|
||||||
|
setRedirectURI(updatedRedirectURI);
|
||||||
|
};
|
||||||
|
const { getRootProps, getInputProps, isDragActive } = useDropzone({
|
||||||
|
onDrop,
|
||||||
|
accept: 'text/csv',
|
||||||
|
});
|
||||||
|
const closeModalHandler = () => {
|
||||||
|
setRedirectURI({
|
||||||
|
value: '',
|
||||||
|
isInvalid: false,
|
||||||
|
});
|
||||||
|
setEmails([
|
||||||
|
{
|
||||||
|
value: '',
|
||||||
|
isInvalid: false,
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
onClose();
|
||||||
|
};
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Button
|
||||||
|
leftIcon={<FaUserPlus />}
|
||||||
|
colorScheme="blue"
|
||||||
|
variant="solid"
|
||||||
|
onClick={onOpen}
|
||||||
|
isDisabled={disabled}
|
||||||
|
size="sm"
|
||||||
|
>
|
||||||
|
<Center h="100%">Invite Members</Center>
|
||||||
|
</Button>
|
||||||
|
<Modal isOpen={isOpen} onClose={closeModalHandler} size="xl">
|
||||||
|
<ModalOverlay />
|
||||||
|
<ModalContent>
|
||||||
|
<ModalHeader>Invite Members</ModalHeader>
|
||||||
|
<ModalCloseButton />
|
||||||
|
<ModalBody>
|
||||||
|
<Tabs
|
||||||
|
isFitted
|
||||||
|
variant="enclosed"
|
||||||
|
index={tabIndex}
|
||||||
|
onChange={changeTabsHandler}
|
||||||
|
>
|
||||||
|
<TabList>
|
||||||
|
<Tab>Enter emails</Tab>
|
||||||
|
<Tab>Upload CSV</Tab>
|
||||||
|
</TabList>
|
||||||
|
<TabPanels
|
||||||
|
border="1px"
|
||||||
|
borderTop="0"
|
||||||
|
borderBottomRadius="5px"
|
||||||
|
borderColor="inherit"
|
||||||
|
>
|
||||||
|
<TabPanel>
|
||||||
|
<Flex flexDirection="column">
|
||||||
|
<Flex
|
||||||
|
width="100%"
|
||||||
|
justifyContent="start"
|
||||||
|
alignItems="center"
|
||||||
|
marginBottom="2%"
|
||||||
|
>
|
||||||
|
<Flex marginLeft="2.5%">Redirect URI</Flex>
|
||||||
|
</Flex>
|
||||||
|
<Flex
|
||||||
|
width="100%"
|
||||||
|
justifyContent="space-between"
|
||||||
|
alignItems="center"
|
||||||
|
marginBottom="2%"
|
||||||
|
>
|
||||||
|
<InputGroup size="md" marginBottom="2.5%">
|
||||||
|
<Input
|
||||||
|
pr="4.5rem"
|
||||||
|
type="text"
|
||||||
|
placeholder="https://domain.com/sign-up"
|
||||||
|
value={redirectURI.value}
|
||||||
|
isInvalid={redirectURI.isInvalid}
|
||||||
|
onChange={(e) =>
|
||||||
|
setRedirectURIHandler(e.currentTarget.value)
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</InputGroup>
|
||||||
|
</Flex>
|
||||||
|
<Flex
|
||||||
|
width="100%"
|
||||||
|
justifyContent="space-between"
|
||||||
|
alignItems="center"
|
||||||
|
marginBottom="2%"
|
||||||
|
>
|
||||||
|
<Flex marginLeft="2.5%">Emails</Flex>
|
||||||
|
<Flex>
|
||||||
|
<Button
|
||||||
|
leftIcon={<FaPlus />}
|
||||||
|
colorScheme="blue"
|
||||||
|
h="1.75rem"
|
||||||
|
size="sm"
|
||||||
|
variant="ghost"
|
||||||
|
onClick={() =>
|
||||||
|
updateEmailListHandler(ArrayInputOperations.APPEND)
|
||||||
|
}
|
||||||
|
>
|
||||||
|
Add more emails
|
||||||
|
</Button>
|
||||||
|
</Flex>
|
||||||
|
</Flex>
|
||||||
|
<Flex flexDirection="column" maxH={250} overflowY="scroll">
|
||||||
|
{emails.map((emailData, index) => (
|
||||||
|
<Flex
|
||||||
|
key={`email-data-${index}`}
|
||||||
|
justifyContent="center"
|
||||||
|
alignItems="center"
|
||||||
|
>
|
||||||
|
<InputGroup size="md" marginBottom="2.5%">
|
||||||
|
<Input
|
||||||
|
pr="4.5rem"
|
||||||
|
type="text"
|
||||||
|
placeholder="name@domain.com"
|
||||||
|
value={emailData.value}
|
||||||
|
isInvalid={emailData.isInvalid}
|
||||||
|
onChange={(e) =>
|
||||||
|
inputChangeHandler(e.currentTarget.value, index)
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<InputRightElement width="3rem">
|
||||||
|
<Button
|
||||||
|
h="1.75rem"
|
||||||
|
size="sm"
|
||||||
|
colorScheme="blackAlpha"
|
||||||
|
variant="ghost"
|
||||||
|
onClick={() =>
|
||||||
|
updateEmailListHandler(
|
||||||
|
ArrayInputOperations.REMOVE,
|
||||||
|
index
|
||||||
|
)
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<FaMinusCircle />
|
||||||
|
</Button>
|
||||||
|
</InputRightElement>
|
||||||
|
</InputGroup>
|
||||||
|
</Flex>
|
||||||
|
))}
|
||||||
|
</Flex>
|
||||||
|
</Flex>
|
||||||
|
</TabPanel>
|
||||||
|
<TabPanel>
|
||||||
|
<Flex
|
||||||
|
justify="center"
|
||||||
|
align="center"
|
||||||
|
textAlign="center"
|
||||||
|
bg="#f0f0f0"
|
||||||
|
h={230}
|
||||||
|
p={50}
|
||||||
|
m={2}
|
||||||
|
borderRadius={5}
|
||||||
|
{...getRootProps()}
|
||||||
|
>
|
||||||
|
<input {...getInputProps()} />
|
||||||
|
{isDragActive ? (
|
||||||
|
<Text>Drop the files here...</Text>
|
||||||
|
) : (
|
||||||
|
<Flex
|
||||||
|
flexDirection="column"
|
||||||
|
justifyContent="center"
|
||||||
|
alignItems="center"
|
||||||
|
>
|
||||||
|
<Center boxSize="20" color="blackAlpha.500">
|
||||||
|
<FaUpload fontSize="40" />
|
||||||
|
</Center>
|
||||||
|
<Text>
|
||||||
|
Drag 'n' drop the csv file here, or click to select.
|
||||||
|
</Text>
|
||||||
|
<Text size="xs">
|
||||||
|
Download{' '}
|
||||||
|
<Link
|
||||||
|
href={`/dashboard/public/sample.csv`}
|
||||||
|
download="sample.csv"
|
||||||
|
color="blue.600"
|
||||||
|
onClick={(e) => e.stopPropagation()}
|
||||||
|
>
|
||||||
|
{' '}
|
||||||
|
sample.csv
|
||||||
|
</Link>{' '}
|
||||||
|
and modify it.{' '}
|
||||||
|
</Text>
|
||||||
|
</Flex>
|
||||||
|
)}
|
||||||
|
</Flex>
|
||||||
|
</TabPanel>
|
||||||
|
</TabPanels>
|
||||||
|
</Tabs>
|
||||||
|
</ModalBody>
|
||||||
|
<ModalFooter>
|
||||||
|
<Button
|
||||||
|
colorScheme="blue"
|
||||||
|
variant="solid"
|
||||||
|
onClick={sendInviteHandler}
|
||||||
|
isDisabled={disableSendButton || loading}
|
||||||
|
>
|
||||||
|
<Center h="100%" pt="5%">
|
||||||
|
Send
|
||||||
|
</Center>
|
||||||
|
</Button>
|
||||||
|
</ModalFooter>
|
||||||
|
</ModalContent>
|
||||||
|
</Modal>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default InviteMembersModal;
|
@@ -29,10 +29,11 @@ import {
|
|||||||
} from 'react-icons/fi';
|
} from 'react-icons/fi';
|
||||||
import { IconType } from 'react-icons';
|
import { IconType } from 'react-icons';
|
||||||
import { ReactText } from 'react';
|
import { ReactText } from 'react';
|
||||||
import { useMutation } from 'urql';
|
import { useMutation, useQuery } from 'urql';
|
||||||
import { NavLink, useNavigate, useLocation } from 'react-router-dom';
|
import { NavLink, useNavigate, useLocation } from 'react-router-dom';
|
||||||
import { useAuthContext } from '../contexts/AuthContext';
|
import { useAuthContext } from '../contexts/AuthContext';
|
||||||
import { AdminLogout } from '../graphql/mutation';
|
import { AdminLogout } from '../graphql/mutation';
|
||||||
|
import { MetaQuery } from '../graphql/queries';
|
||||||
|
|
||||||
interface LinkItemProps {
|
interface LinkItemProps {
|
||||||
name: string;
|
name: string;
|
||||||
@@ -51,6 +52,7 @@ interface SidebarProps extends BoxProps {
|
|||||||
|
|
||||||
export const Sidebar = ({ onClose, ...rest }: SidebarProps) => {
|
export const Sidebar = ({ onClose, ...rest }: SidebarProps) => {
|
||||||
const { pathname } = useLocation();
|
const { pathname } = useLocation();
|
||||||
|
const [{ fetching, data }] = useQuery({ query: MetaQuery });
|
||||||
return (
|
return (
|
||||||
<Box
|
<Box
|
||||||
transition="3s ease"
|
transition="3s ease"
|
||||||
@@ -98,6 +100,19 @@ export const Sidebar = ({ onClose, ...rest }: SidebarProps) => {
|
|||||||
>
|
>
|
||||||
<NavItem icon={FiCode}>API Playground</NavItem>
|
<NavItem icon={FiCode}>API Playground</NavItem>
|
||||||
</Link>
|
</Link>
|
||||||
|
|
||||||
|
{data?.meta?.version && (
|
||||||
|
<Text
|
||||||
|
color="gray.600"
|
||||||
|
fontSize="sm"
|
||||||
|
textAlign="center"
|
||||||
|
position="absolute"
|
||||||
|
bottom="5"
|
||||||
|
left="7"
|
||||||
|
>
|
||||||
|
Current Version: {data.meta.version}
|
||||||
|
</Text>
|
||||||
|
)}
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@@ -2,6 +2,8 @@ export const LOGO_URL =
|
|||||||
'https://user-images.githubusercontent.com/6964334/147834043-fc384cab-e7ca-40f8-9663-38fc25fd5f3a.png';
|
'https://user-images.githubusercontent.com/6964334/147834043-fc384cab-e7ca-40f8-9663-38fc25fd5f3a.png';
|
||||||
|
|
||||||
export const TextInputType = {
|
export const TextInputType = {
|
||||||
|
ACCESS_TOKEN_EXPIRY_TIME: 'ACCESS_TOKEN_EXPIRY_TIME',
|
||||||
|
CLIENT_ID: 'CLIENT_ID',
|
||||||
GOOGLE_CLIENT_ID: 'GOOGLE_CLIENT_ID',
|
GOOGLE_CLIENT_ID: 'GOOGLE_CLIENT_ID',
|
||||||
GITHUB_CLIENT_ID: 'GITHUB_CLIENT_ID',
|
GITHUB_CLIENT_ID: 'GITHUB_CLIENT_ID',
|
||||||
FACEBOOK_CLIENT_ID: 'FACEBOOK_CLIENT_ID',
|
FACEBOOK_CLIENT_ID: 'FACEBOOK_CLIENT_ID',
|
||||||
@@ -25,6 +27,7 @@ export const TextInputType = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const HiddenInputType = {
|
export const HiddenInputType = {
|
||||||
|
CLIENT_SECRET: 'CLIENT_SECRET',
|
||||||
GOOGLE_CLIENT_SECRET: 'GOOGLE_CLIENT_SECRET',
|
GOOGLE_CLIENT_SECRET: 'GOOGLE_CLIENT_SECRET',
|
||||||
GITHUB_CLIENT_SECRET: 'GITHUB_CLIENT_SECRET',
|
GITHUB_CLIENT_SECRET: 'GITHUB_CLIENT_SECRET',
|
||||||
FACEBOOK_CLIENT_SECRET: 'FACEBOOK_CLIENT_SECRET',
|
FACEBOOK_CLIENT_SECRET: 'FACEBOOK_CLIENT_SECRET',
|
||||||
@@ -49,6 +52,8 @@ export const SelectInputType = {
|
|||||||
|
|
||||||
export const TextAreaInputType = {
|
export const TextAreaInputType = {
|
||||||
CUSTOM_ACCESS_TOKEN_SCRIPT: 'CUSTOM_ACCESS_TOKEN_SCRIPT',
|
CUSTOM_ACCESS_TOKEN_SCRIPT: 'CUSTOM_ACCESS_TOKEN_SCRIPT',
|
||||||
|
JWT_PRIVATE_KEY: 'JWT_PRIVATE_KEY',
|
||||||
|
JWT_PUBLIC_KEY: 'JWT_PUBLIC_KEY',
|
||||||
};
|
};
|
||||||
|
|
||||||
export const SwitchInputType = {
|
export const SwitchInputType = {
|
||||||
@@ -56,6 +61,7 @@ export const SwitchInputType = {
|
|||||||
DISABLE_MAGIC_LINK_LOGIN: 'DISABLE_MAGIC_LINK_LOGIN',
|
DISABLE_MAGIC_LINK_LOGIN: 'DISABLE_MAGIC_LINK_LOGIN',
|
||||||
DISABLE_EMAIL_VERIFICATION: 'DISABLE_EMAIL_VERIFICATION',
|
DISABLE_EMAIL_VERIFICATION: 'DISABLE_EMAIL_VERIFICATION',
|
||||||
DISABLE_BASIC_AUTHENTICATION: 'DISABLE_BASIC_AUTHENTICATION',
|
DISABLE_BASIC_AUTHENTICATION: 'DISABLE_BASIC_AUTHENTICATION',
|
||||||
|
DISABLE_SIGN_UP: 'DISABLE_SIGN_UP',
|
||||||
};
|
};
|
||||||
|
|
||||||
export const DateInputType = {
|
export const DateInputType = {
|
||||||
@@ -66,3 +72,59 @@ export const ArrayInputOperations = {
|
|||||||
APPEND: 'APPEND',
|
APPEND: 'APPEND',
|
||||||
REMOVE: 'REMOVE',
|
REMOVE: 'REMOVE',
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const HMACEncryptionType = {
|
||||||
|
HS256: 'HS256',
|
||||||
|
HS384: 'HS384',
|
||||||
|
HS512: 'HS512',
|
||||||
|
};
|
||||||
|
|
||||||
|
export const RSAEncryptionType = {
|
||||||
|
RS256: 'RS256',
|
||||||
|
RS384: 'RS384',
|
||||||
|
RS512: 'RS512',
|
||||||
|
};
|
||||||
|
|
||||||
|
export const ECDSAEncryptionType = {
|
||||||
|
ES256: 'ES256',
|
||||||
|
ES384: 'ES384',
|
||||||
|
ES512: 'ES512',
|
||||||
|
};
|
||||||
|
|
||||||
|
export 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;
|
||||||
|
JWT_PRIVATE_KEY: string;
|
||||||
|
JWT_PUBLIC_KEY: 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;
|
||||||
|
DISABLE_SIGN_UP: boolean;
|
||||||
|
OLD_ADMIN_SECRET: string;
|
||||||
|
DATABASE_NAME: string;
|
||||||
|
DATABASE_TYPE: string;
|
||||||
|
DATABASE_URL: string;
|
||||||
|
ACCESS_TOKEN_EXPIRY_TIME: string;
|
||||||
|
}
|
||||||
|
@@ -45,3 +45,37 @@ export const DeleteUser = `
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
export const InviteMembers = `
|
||||||
|
mutation inviteMembers($params: InviteMemberInput!) {
|
||||||
|
_invite_members(params: $params) {
|
||||||
|
message
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
export const RevokeAccess = `
|
||||||
|
mutation revokeAccess($param: UpdateAccessInput!) {
|
||||||
|
_revoke_access(param: $param) {
|
||||||
|
message
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
export const EnableAccess = `
|
||||||
|
mutation revokeAccess($param: UpdateAccessInput!) {
|
||||||
|
_enable_access(param: $param) {
|
||||||
|
message
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
export const GenerateKeys = `
|
||||||
|
mutation generateKeys($params: GenerateJWTKeysInput!) {
|
||||||
|
_generate_jwt_keys(params: $params) {
|
||||||
|
secret
|
||||||
|
public_key
|
||||||
|
private_key
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
@@ -1,3 +1,12 @@
|
|||||||
|
export const MetaQuery = `
|
||||||
|
query MetaQuery {
|
||||||
|
meta {
|
||||||
|
version
|
||||||
|
client_id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
export const AdminSessionQuery = `
|
export const AdminSessionQuery = `
|
||||||
query {
|
query {
|
||||||
_admin_session{
|
_admin_session{
|
||||||
@@ -9,6 +18,8 @@ export const AdminSessionQuery = `
|
|||||||
export const EnvVariablesQuery = `
|
export const EnvVariablesQuery = `
|
||||||
query {
|
query {
|
||||||
_env{
|
_env{
|
||||||
|
CLIENT_ID,
|
||||||
|
CLIENT_SECRET,
|
||||||
GOOGLE_CLIENT_ID,
|
GOOGLE_CLIENT_ID,
|
||||||
GOOGLE_CLIENT_SECRET,
|
GOOGLE_CLIENT_SECRET,
|
||||||
GITHUB_CLIENT_ID,
|
GITHUB_CLIENT_ID,
|
||||||
@@ -21,6 +32,8 @@ export const EnvVariablesQuery = `
|
|||||||
JWT_TYPE,
|
JWT_TYPE,
|
||||||
JWT_SECRET,
|
JWT_SECRET,
|
||||||
JWT_ROLE_CLAIM,
|
JWT_ROLE_CLAIM,
|
||||||
|
JWT_PRIVATE_KEY,
|
||||||
|
JWT_PUBLIC_KEY,
|
||||||
REDIS_URL,
|
REDIS_URL,
|
||||||
SMTP_HOST,
|
SMTP_HOST,
|
||||||
SMTP_PORT,
|
SMTP_PORT,
|
||||||
@@ -35,10 +48,12 @@ export const EnvVariablesQuery = `
|
|||||||
DISABLE_MAGIC_LINK_LOGIN,
|
DISABLE_MAGIC_LINK_LOGIN,
|
||||||
DISABLE_EMAIL_VERIFICATION,
|
DISABLE_EMAIL_VERIFICATION,
|
||||||
DISABLE_BASIC_AUTHENTICATION,
|
DISABLE_BASIC_AUTHENTICATION,
|
||||||
|
DISABLE_SIGN_UP,
|
||||||
CUSTOM_ACCESS_TOKEN_SCRIPT,
|
CUSTOM_ACCESS_TOKEN_SCRIPT,
|
||||||
DATABASE_NAME,
|
DATABASE_NAME,
|
||||||
DATABASE_TYPE,
|
DATABASE_TYPE,
|
||||||
DATABASE_URL,
|
DATABASE_URL,
|
||||||
|
ACCESS_TOKEN_EXPIRY_TIME,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
@@ -67,7 +82,16 @@ export const UserDetailsQuery = `
|
|||||||
signup_methods
|
signup_methods
|
||||||
roles
|
roles
|
||||||
created_at
|
created_at
|
||||||
|
revoked_timestamp
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
export const EmailVerificationQuery = `
|
||||||
|
query {
|
||||||
|
_env{
|
||||||
|
DISABLE_EMAIL_VERIFICATION
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
@@ -1,8 +1,10 @@
|
|||||||
import { Box, Center, Flex, Image, Text } from '@chakra-ui/react';
|
import { Box, Flex, Image, Text, Spinner } from '@chakra-ui/react';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { LOGO_URL } from '../constants';
|
import { useQuery } from 'urql';
|
||||||
|
import { MetaQuery } from '../graphql/queries';
|
||||||
|
|
||||||
export function AuthLayout({ children }: { children: React.ReactNode }) {
|
export function AuthLayout({ children }: { children: React.ReactNode }) {
|
||||||
|
const [{ fetching, data }] = useQuery({ query: MetaQuery });
|
||||||
return (
|
return (
|
||||||
<Flex
|
<Flex
|
||||||
flexWrap="wrap"
|
flexWrap="wrap"
|
||||||
@@ -23,9 +25,18 @@ export function AuthLayout({ children }: { children: React.ReactNode }) {
|
|||||||
</Text>
|
</Text>
|
||||||
</Flex>
|
</Flex>
|
||||||
|
|
||||||
|
{fetching ? (
|
||||||
|
<Spinner />
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
<Box p="6" m="5" rounded="5" bg="white" w="500px" shadow="xl">
|
<Box p="6" m="5" rounded="5" bg="white" w="500px" shadow="xl">
|
||||||
{children}
|
{children}
|
||||||
</Box>
|
</Box>
|
||||||
|
<Text color="gray.600" fontSize="sm">
|
||||||
|
Current Version: {data.meta.version}
|
||||||
|
</Text>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
</Flex>
|
</Flex>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@@ -6,7 +6,6 @@ import {
|
|||||||
useToast,
|
useToast,
|
||||||
VStack,
|
VStack,
|
||||||
Text,
|
Text,
|
||||||
Divider,
|
|
||||||
} from '@chakra-ui/react';
|
} from '@chakra-ui/react';
|
||||||
import React, { useEffect } from 'react';
|
import React, { useEffect } from 'react';
|
||||||
import { useMutation } from 'urql';
|
import { useMutation } from 'urql';
|
||||||
|
@@ -31,43 +31,14 @@ import {
|
|||||||
TextInputType,
|
TextInputType,
|
||||||
TextAreaInputType,
|
TextAreaInputType,
|
||||||
SwitchInputType,
|
SwitchInputType,
|
||||||
|
HMACEncryptionType,
|
||||||
|
RSAEncryptionType,
|
||||||
|
ECDSAEncryptionType,
|
||||||
|
envVarTypes,
|
||||||
} from '../constants';
|
} from '../constants';
|
||||||
import { UpdateEnvVariables } from '../graphql/mutation';
|
import { UpdateEnvVariables } from '../graphql/mutation';
|
||||||
import { getObjectDiff, capitalizeFirstLetter } from '../utils';
|
import { getObjectDiff, capitalizeFirstLetter } from '../utils';
|
||||||
|
import GenerateKeysModal from '../components/GenerateKeysModal';
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function Environment() {
|
export default function Environment() {
|
||||||
const client = useClient();
|
const client = useClient();
|
||||||
@@ -92,6 +63,8 @@ export default function Environment() {
|
|||||||
JWT_TYPE: '',
|
JWT_TYPE: '',
|
||||||
JWT_SECRET: '',
|
JWT_SECRET: '',
|
||||||
JWT_ROLE_CLAIM: '',
|
JWT_ROLE_CLAIM: '',
|
||||||
|
JWT_PRIVATE_KEY: '',
|
||||||
|
JWT_PUBLIC_KEY: '',
|
||||||
REDIS_URL: '',
|
REDIS_URL: '',
|
||||||
SMTP_HOST: '',
|
SMTP_HOST: '',
|
||||||
SMTP_PORT: '',
|
SMTP_PORT: '',
|
||||||
@@ -107,10 +80,12 @@ export default function Environment() {
|
|||||||
DISABLE_MAGIC_LINK_LOGIN: false,
|
DISABLE_MAGIC_LINK_LOGIN: false,
|
||||||
DISABLE_EMAIL_VERIFICATION: false,
|
DISABLE_EMAIL_VERIFICATION: false,
|
||||||
DISABLE_BASIC_AUTHENTICATION: false,
|
DISABLE_BASIC_AUTHENTICATION: false,
|
||||||
|
DISABLE_SIGN_UP: false,
|
||||||
OLD_ADMIN_SECRET: '',
|
OLD_ADMIN_SECRET: '',
|
||||||
DATABASE_NAME: '',
|
DATABASE_NAME: '',
|
||||||
DATABASE_TYPE: '',
|
DATABASE_TYPE: '',
|
||||||
DATABASE_URL: '',
|
DATABASE_URL: '',
|
||||||
|
ACCESS_TOKEN_EXPIRY_TIME: '',
|
||||||
});
|
});
|
||||||
|
|
||||||
const [fieldVisibility, setFieldVisibility] = React.useState<
|
const [fieldVisibility, setFieldVisibility] = React.useState<
|
||||||
@@ -125,14 +100,10 @@ export default function Environment() {
|
|||||||
OLD_ADMIN_SECRET: false,
|
OLD_ADMIN_SECRET: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
let isMounted = true;
|
|
||||||
async function getData() {
|
async function getData() {
|
||||||
const {
|
const {
|
||||||
data: { _env: envData },
|
data: { _env: envData },
|
||||||
} = await client.query(EnvVariablesQuery).toPromise();
|
} = await client.query(EnvVariablesQuery).toPromise();
|
||||||
|
|
||||||
if (isMounted) {
|
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
setEnvVariables({
|
setEnvVariables({
|
||||||
...envData,
|
...envData,
|
||||||
@@ -144,13 +115,9 @@ export default function Environment() {
|
|||||||
disableInputField: true,
|
disableInputField: true,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
getData();
|
getData();
|
||||||
|
|
||||||
return () => {
|
|
||||||
isMounted = false;
|
|
||||||
};
|
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const validateAdminSecretHandler = (event: any) => {
|
const validateAdminSecretHandler = (event: any) => {
|
||||||
@@ -177,7 +144,6 @@ export default function Environment() {
|
|||||||
const {
|
const {
|
||||||
data: { _env: envData },
|
data: { _env: envData },
|
||||||
} = await client.query(EnvVariablesQuery).toPromise();
|
} = await client.query(EnvVariablesQuery).toPromise();
|
||||||
|
|
||||||
const diff = getObjectDiff(envVariables, envData);
|
const diff = getObjectDiff(envVariables, envData);
|
||||||
const updatedEnvVariables = diff.reduce(
|
const updatedEnvVariables = diff.reduce(
|
||||||
(acc: any, property: string) => ({
|
(acc: any, property: string) => ({
|
||||||
@@ -222,6 +188,8 @@ export default function Environment() {
|
|||||||
disableInputField: true,
|
disableInputField: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
getData();
|
||||||
|
|
||||||
toast({
|
toast({
|
||||||
title: `Successfully updated ${
|
title: `Successfully updated ${
|
||||||
Object.keys(updatedEnvVariables).length
|
Object.keys(updatedEnvVariables).length
|
||||||
@@ -234,6 +202,42 @@ export default function Environment() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Box m="5" py="5" px="10" bg="white" rounded="md">
|
<Box m="5" py="5" px="10" bg="white" rounded="md">
|
||||||
|
<Text fontSize="md" paddingTop="2%" fontWeight="bold">
|
||||||
|
Your instance information
|
||||||
|
</Text>
|
||||||
|
<Stack spacing={6} padding="2% 0%">
|
||||||
|
<Flex>
|
||||||
|
<Flex w="30%" justifyContent="start" alignItems="center">
|
||||||
|
<Text fontSize="sm">Client ID</Text>
|
||||||
|
</Flex>
|
||||||
|
<Center w="70%">
|
||||||
|
<InputField
|
||||||
|
variables={envVariables}
|
||||||
|
setVariables={() => {}}
|
||||||
|
inputType={TextInputType.CLIENT_ID}
|
||||||
|
placeholder="Client ID"
|
||||||
|
readOnly={true}
|
||||||
|
/>
|
||||||
|
</Center>
|
||||||
|
</Flex>
|
||||||
|
<Flex>
|
||||||
|
<Flex w="30%" justifyContent="start" alignItems="center">
|
||||||
|
<Text fontSize="sm">Client Secret</Text>
|
||||||
|
</Flex>
|
||||||
|
<Center w="70%">
|
||||||
|
<InputField
|
||||||
|
variables={envVariables}
|
||||||
|
setVariables={setEnvVariables}
|
||||||
|
fieldVisibility={fieldVisibility}
|
||||||
|
setFieldVisibility={setFieldVisibility}
|
||||||
|
inputType={HiddenInputType.CLIENT_SECRET}
|
||||||
|
placeholder="Client Secret"
|
||||||
|
readOnly={true}
|
||||||
|
/>
|
||||||
|
</Center>
|
||||||
|
</Flex>
|
||||||
|
</Stack>
|
||||||
|
<Divider marginTop="2%" marginBottom="2%" />
|
||||||
<Text fontSize="md" paddingTop="2%" fontWeight="bold">
|
<Text fontSize="md" paddingTop="2%" fontWeight="bold">
|
||||||
Social Media Logins
|
Social Media Logins
|
||||||
</Text>
|
</Text>
|
||||||
@@ -366,33 +370,42 @@ export default function Environment() {
|
|||||||
</Flex>
|
</Flex>
|
||||||
</Stack>
|
</Stack>
|
||||||
<Divider marginTop="2%" marginBottom="2%" />
|
<Divider marginTop="2%" marginBottom="2%" />
|
||||||
<Text fontSize="md" paddingTop="2%" fontWeight="bold">
|
<Flex
|
||||||
|
width="100%"
|
||||||
|
justifyContent="space-between"
|
||||||
|
alignItems="center"
|
||||||
|
paddingTop="2%"
|
||||||
|
>
|
||||||
|
<Text fontSize="md" fontWeight="bold">
|
||||||
JWT (JSON Web Tokens) Configurations
|
JWT (JSON Web Tokens) Configurations
|
||||||
</Text>
|
</Text>
|
||||||
|
<Flex>
|
||||||
|
<GenerateKeysModal
|
||||||
|
jwtType={envVariables.JWT_TYPE}
|
||||||
|
getData={getData}
|
||||||
|
/>
|
||||||
|
</Flex>
|
||||||
|
</Flex>
|
||||||
<Stack spacing={6} padding="2% 0%">
|
<Stack spacing={6} padding="2% 0%">
|
||||||
<Flex>
|
<Flex>
|
||||||
<Flex w="30%" justifyContent="start" alignItems="center">
|
<Flex w="30%" justifyContent="start" alignItems="center">
|
||||||
<Text fontSize="sm">JWT Type:</Text>
|
<Text fontSize="sm">JWT Type:</Text>
|
||||||
</Flex>
|
</Flex>
|
||||||
<Center w="70%">
|
<Flex w="70%">
|
||||||
<Flex w="100%" justifyContent="space-between">
|
|
||||||
<Flex flex="2">
|
|
||||||
<InputField
|
<InputField
|
||||||
variables={envVariables}
|
variables={envVariables}
|
||||||
setVariables={setEnvVariables}
|
setVariables={setEnvVariables}
|
||||||
inputType={SelectInputType.JWT_TYPE}
|
inputType={SelectInputType.JWT_TYPE}
|
||||||
isDisabled={true}
|
value={SelectInputType.JWT_TYPE}
|
||||||
defaultValue={SelectInputType.JWT_TYPE}
|
options={{
|
||||||
|
...HMACEncryptionType,
|
||||||
|
...RSAEncryptionType,
|
||||||
|
...ECDSAEncryptionType,
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
</Flex>
|
</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>
|
||||||
|
{Object.values(HMACEncryptionType).includes(envVariables.JWT_TYPE) ? (
|
||||||
<Flex>
|
<Flex>
|
||||||
<Flex w="30%" justifyContent="start" alignItems="center">
|
<Flex w="30%" justifyContent="start" alignItems="center">
|
||||||
<Text fontSize="sm">JWT Secret</Text>
|
<Text fontSize="sm">JWT Secret</Text>
|
||||||
@@ -407,6 +420,38 @@ export default function Environment() {
|
|||||||
/>
|
/>
|
||||||
</Center>
|
</Center>
|
||||||
</Flex>
|
</Flex>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<Flex>
|
||||||
|
<Flex w="30%" justifyContent="start" alignItems="center">
|
||||||
|
<Text fontSize="sm">Public Key</Text>
|
||||||
|
</Flex>
|
||||||
|
<Center w="70%">
|
||||||
|
<InputField
|
||||||
|
variables={envVariables}
|
||||||
|
setVariables={setEnvVariables}
|
||||||
|
inputType={TextAreaInputType.JWT_PUBLIC_KEY}
|
||||||
|
placeholder="Add public key here"
|
||||||
|
minH="25vh"
|
||||||
|
/>
|
||||||
|
</Center>
|
||||||
|
</Flex>
|
||||||
|
<Flex>
|
||||||
|
<Flex w="30%" justifyContent="start" alignItems="center">
|
||||||
|
<Text fontSize="sm">Private Key</Text>
|
||||||
|
</Flex>
|
||||||
|
<Center w="70%">
|
||||||
|
<InputField
|
||||||
|
variables={envVariables}
|
||||||
|
setVariables={setEnvVariables}
|
||||||
|
inputType={TextAreaInputType.JWT_PRIVATE_KEY}
|
||||||
|
placeholder="Add private key here"
|
||||||
|
minH="25vh"
|
||||||
|
/>
|
||||||
|
</Center>
|
||||||
|
</Flex>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
<Flex>
|
<Flex>
|
||||||
<Flex w="30%" justifyContent="start" alignItems="center">
|
<Flex w="30%" justifyContent="start" alignItems="center">
|
||||||
<Text fontSize="sm">JWT Role Claim:</Text>
|
<Text fontSize="sm">JWT Role Claim:</Text>
|
||||||
@@ -556,11 +601,28 @@ export default function Environment() {
|
|||||||
</Stack>
|
</Stack>
|
||||||
<Divider marginTop="2%" marginBottom="2%" />
|
<Divider marginTop="2%" marginBottom="2%" />
|
||||||
<Text fontSize="md" paddingTop="2%" fontWeight="bold">
|
<Text fontSize="md" paddingTop="2%" fontWeight="bold">
|
||||||
Custom Access Token Scripts
|
Access Token
|
||||||
</Text>
|
</Text>
|
||||||
<Stack spacing={6} padding="2% 0%">
|
<Stack spacing={6} padding="2% 0%">
|
||||||
<Flex>
|
<Flex>
|
||||||
<Center w="100%">
|
<Flex w="30%" justifyContent="start" alignItems="center">
|
||||||
|
<Text fontSize="sm">Access Token Expiry Time:</Text>
|
||||||
|
</Flex>
|
||||||
|
<Flex w="70%">
|
||||||
|
<InputField
|
||||||
|
variables={envVariables}
|
||||||
|
setVariables={setEnvVariables}
|
||||||
|
inputType={TextInputType.ACCESS_TOKEN_EXPIRY_TIME}
|
||||||
|
placeholder="0h15m0s"
|
||||||
|
/>
|
||||||
|
</Flex>
|
||||||
|
</Flex>
|
||||||
|
<Flex>
|
||||||
|
<Flex w="30%" justifyContent="start" direction="column">
|
||||||
|
<Text fontSize="sm">Custom Scripts:</Text>
|
||||||
|
<Text fontSize="sm">Used to add custom fields in ID token</Text>
|
||||||
|
</Flex>
|
||||||
|
<Flex w="70%">
|
||||||
<InputField
|
<InputField
|
||||||
variables={envVariables}
|
variables={envVariables}
|
||||||
setVariables={setEnvVariables}
|
setVariables={setEnvVariables}
|
||||||
@@ -568,7 +630,7 @@ export default function Environment() {
|
|||||||
placeholder="Add script here"
|
placeholder="Add script here"
|
||||||
minH="25vh"
|
minH="25vh"
|
||||||
/>
|
/>
|
||||||
</Center>
|
</Flex>
|
||||||
</Flex>
|
</Flex>
|
||||||
</Stack>
|
</Stack>
|
||||||
<Divider marginTop="2%" marginBottom="2%" />
|
<Divider marginTop="2%" marginBottom="2%" />
|
||||||
@@ -624,6 +686,18 @@ export default function Environment() {
|
|||||||
/>
|
/>
|
||||||
</Flex>
|
</Flex>
|
||||||
</Flex>
|
</Flex>
|
||||||
|
<Flex>
|
||||||
|
<Flex w="30%" justifyContent="start" alignItems="center">
|
||||||
|
<Text fontSize="sm">Disable Sign Up:</Text>
|
||||||
|
</Flex>
|
||||||
|
<Flex justifyContent="start" w="70%">
|
||||||
|
<InputField
|
||||||
|
variables={envVariables}
|
||||||
|
setVariables={setEnvVariables}
|
||||||
|
inputType={SwitchInputType.DISABLE_SIGN_UP}
|
||||||
|
/>
|
||||||
|
</Flex>
|
||||||
|
</Flex>
|
||||||
</Stack>
|
</Stack>
|
||||||
<Divider marginTop="2%" marginBottom="2%" />
|
<Divider marginTop="2%" marginBottom="2%" />
|
||||||
<Text fontSize="md" paddingTop="2%" fontWeight="bold">
|
<Text fontSize="md" paddingTop="2%" fontWeight="bold">
|
||||||
|
@@ -38,10 +38,11 @@ import {
|
|||||||
FaExclamationCircle,
|
FaExclamationCircle,
|
||||||
FaAngleDown,
|
FaAngleDown,
|
||||||
} from 'react-icons/fa';
|
} from 'react-icons/fa';
|
||||||
import { UserDetailsQuery } from '../graphql/queries';
|
import { EmailVerificationQuery, UserDetailsQuery } from '../graphql/queries';
|
||||||
import { UpdateUser } from '../graphql/mutation';
|
import { EnableAccess, RevokeAccess, UpdateUser } from '../graphql/mutation';
|
||||||
import EditUserModal from '../components/EditUserModal';
|
import EditUserModal from '../components/EditUserModal';
|
||||||
import DeleteUserModal from '../components/DeleteUserModal';
|
import DeleteUserModal from '../components/DeleteUserModal';
|
||||||
|
import InviteMembersModal from '../components/InviteMembersModal';
|
||||||
|
|
||||||
interface paginationPropTypes {
|
interface paginationPropTypes {
|
||||||
limit: number;
|
limit: number;
|
||||||
@@ -66,6 +67,12 @@ interface userDataTypes {
|
|||||||
signup_methods: string;
|
signup_methods: string;
|
||||||
roles: [string];
|
roles: [string];
|
||||||
created_at: number;
|
created_at: number;
|
||||||
|
revoked_timestamp: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
const enum updateAccessActions {
|
||||||
|
REVOKE = 'REVOKE',
|
||||||
|
ENABLE = 'ENABLE',
|
||||||
}
|
}
|
||||||
|
|
||||||
const getMaxPages = (pagination: paginationPropTypes) => {
|
const getMaxPages = (pagination: paginationPropTypes) => {
|
||||||
@@ -101,6 +108,8 @@ export default function Users() {
|
|||||||
});
|
});
|
||||||
const [userList, setUserList] = React.useState<userDataTypes[]>([]);
|
const [userList, setUserList] = React.useState<userDataTypes[]>([]);
|
||||||
const [loading, setLoading] = React.useState<boolean>(false);
|
const [loading, setLoading] = React.useState<boolean>(false);
|
||||||
|
const [disableInviteMembers, setDisableInviteMembers] =
|
||||||
|
React.useState<boolean>(true);
|
||||||
const updateUserList = async () => {
|
const updateUserList = async () => {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
const { data } = await client
|
const { data } = await client
|
||||||
@@ -132,8 +141,18 @@ export default function Users() {
|
|||||||
}
|
}
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
};
|
};
|
||||||
|
const checkEmailVerification = async () => {
|
||||||
|
setLoading(true);
|
||||||
|
const { data } = await client.query(EmailVerificationQuery).toPromise();
|
||||||
|
if (data?._env) {
|
||||||
|
const { DISABLE_EMAIL_VERIFICATION } = data._env;
|
||||||
|
setDisableInviteMembers(DISABLE_EMAIL_VERIFICATION);
|
||||||
|
}
|
||||||
|
setLoading(false);
|
||||||
|
};
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
updateUserList();
|
updateUserList();
|
||||||
|
checkEmailVerification();
|
||||||
}, []);
|
}, []);
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
updateUserList();
|
updateUserList();
|
||||||
@@ -171,12 +190,77 @@ export default function Users() {
|
|||||||
}
|
}
|
||||||
updateUserList();
|
updateUserList();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const updateAccessHandler = async (
|
||||||
|
id: string,
|
||||||
|
action: updateAccessActions
|
||||||
|
) => {
|
||||||
|
switch (action) {
|
||||||
|
case updateAccessActions.ENABLE:
|
||||||
|
const enableAccessRes = await client
|
||||||
|
.mutation(EnableAccess, {
|
||||||
|
param: {
|
||||||
|
user_id: id,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.toPromise();
|
||||||
|
if (enableAccessRes.error) {
|
||||||
|
toast({
|
||||||
|
title: 'User access enable failed',
|
||||||
|
isClosable: true,
|
||||||
|
status: 'error',
|
||||||
|
position: 'bottom-right',
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
toast({
|
||||||
|
title: 'User access enabled successfully',
|
||||||
|
isClosable: true,
|
||||||
|
status: 'success',
|
||||||
|
position: 'bottom-right',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
updateUserList();
|
||||||
|
break;
|
||||||
|
case updateAccessActions.REVOKE:
|
||||||
|
const revokeAccessRes = await client
|
||||||
|
.mutation(RevokeAccess, {
|
||||||
|
param: {
|
||||||
|
user_id: id,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.toPromise();
|
||||||
|
if (revokeAccessRes.error) {
|
||||||
|
toast({
|
||||||
|
title: 'User access revoke failed',
|
||||||
|
isClosable: true,
|
||||||
|
status: 'error',
|
||||||
|
position: 'bottom-right',
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
toast({
|
||||||
|
title: 'User access revoked successfully',
|
||||||
|
isClosable: true,
|
||||||
|
status: 'success',
|
||||||
|
position: 'bottom-right',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
updateUserList();
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box m="5" py="5" px="10" bg="white" rounded="md">
|
<Box m="5" py="5" px="10" bg="white" rounded="md">
|
||||||
<Flex margin="2% 0" justifyContent="space-between" alignItems="center">
|
<Flex margin="2% 0" justifyContent="space-between" alignItems="center">
|
||||||
<Text fontSize="md" fontWeight="bold">
|
<Text fontSize="md" fontWeight="bold">
|
||||||
Users
|
Users
|
||||||
</Text>
|
</Text>
|
||||||
|
<InviteMembersModal
|
||||||
|
disabled={disableInviteMembers}
|
||||||
|
updateUserList={updateUserList}
|
||||||
|
/>
|
||||||
</Flex>
|
</Flex>
|
||||||
{!loading ? (
|
{!loading ? (
|
||||||
userList.length > 0 ? (
|
userList.length > 0 ? (
|
||||||
@@ -188,6 +272,7 @@ export default function Users() {
|
|||||||
<Th>Signup Methods</Th>
|
<Th>Signup Methods</Th>
|
||||||
<Th>Roles</Th>
|
<Th>Roles</Th>
|
||||||
<Th>Verified</Th>
|
<Th>Verified</Th>
|
||||||
|
<Th>Access</Th>
|
||||||
<Th>Actions</Th>
|
<Th>Actions</Th>
|
||||||
</Tr>
|
</Tr>
|
||||||
</Thead>
|
</Thead>
|
||||||
@@ -196,7 +281,7 @@ export default function Users() {
|
|||||||
const { email_verified, created_at, ...rest }: any = user;
|
const { email_verified, created_at, ...rest }: any = user;
|
||||||
return (
|
return (
|
||||||
<Tr key={user.id} style={{ fontSize: 14 }}>
|
<Tr key={user.id} style={{ fontSize: 14 }}>
|
||||||
<Td>{user.email}</Td>
|
<Td maxW="300">{user.email}</Td>
|
||||||
<Td>
|
<Td>
|
||||||
{dayjs(user.created_at * 1000).format('MMM DD, YYYY')}
|
{dayjs(user.created_at * 1000).format('MMM DD, YYYY')}
|
||||||
</Td>
|
</Td>
|
||||||
@@ -211,6 +296,15 @@ export default function Users() {
|
|||||||
{user.email_verified.toString()}
|
{user.email_verified.toString()}
|
||||||
</Tag>
|
</Tag>
|
||||||
</Td>
|
</Td>
|
||||||
|
<Td>
|
||||||
|
<Tag
|
||||||
|
size="sm"
|
||||||
|
variant="outline"
|
||||||
|
colorScheme={user.revoked_timestamp ? 'red' : 'green'}
|
||||||
|
>
|
||||||
|
{user.revoked_timestamp ? 'Revoked' : 'Enabled'}
|
||||||
|
</Tag>
|
||||||
|
</Td>
|
||||||
<Td>
|
<Td>
|
||||||
<Menu>
|
<Menu>
|
||||||
<MenuButton as={Button} variant="unstyled" size="sm">
|
<MenuButton as={Button} variant="unstyled" size="sm">
|
||||||
@@ -240,6 +334,29 @@ export default function Users() {
|
|||||||
user={rest}
|
user={rest}
|
||||||
updateUserList={updateUserList}
|
updateUserList={updateUserList}
|
||||||
/>
|
/>
|
||||||
|
{user.revoked_timestamp ? (
|
||||||
|
<MenuItem
|
||||||
|
onClick={() =>
|
||||||
|
updateAccessHandler(
|
||||||
|
user.id,
|
||||||
|
updateAccessActions.ENABLE
|
||||||
|
)
|
||||||
|
}
|
||||||
|
>
|
||||||
|
Enable Access
|
||||||
|
</MenuItem>
|
||||||
|
) : (
|
||||||
|
<MenuItem
|
||||||
|
onClick={() =>
|
||||||
|
updateAccessHandler(
|
||||||
|
user.id,
|
||||||
|
updateAccessActions.REVOKE
|
||||||
|
)
|
||||||
|
}
|
||||||
|
>
|
||||||
|
Revoke Access
|
||||||
|
</MenuItem>
|
||||||
|
)}
|
||||||
</MenuList>
|
</MenuList>
|
||||||
</Menu>
|
</Menu>
|
||||||
</Td>
|
</Td>
|
||||||
|
@@ -64,3 +64,25 @@ export const getObjectDiff = (obj1: any, obj2: any) => {
|
|||||||
|
|
||||||
return diff;
|
return diff;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const validateEmail = (email: string) => {
|
||||||
|
if (!email || email === '') return true;
|
||||||
|
return email
|
||||||
|
.toLowerCase()
|
||||||
|
.match(
|
||||||
|
/^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/
|
||||||
|
)
|
||||||
|
? true
|
||||||
|
: false;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const validateURI = (uri: string) => {
|
||||||
|
if (!uri || uri === '') return true;
|
||||||
|
return uri
|
||||||
|
.toLowerCase()
|
||||||
|
.match(
|
||||||
|
/(?:^|\s)((https?:\/\/)?(?:localhost|[\w-]+(?:\.[\w-]+)+)(:\d+)?(\/\S*)?)/
|
||||||
|
)
|
||||||
|
? true
|
||||||
|
: false;
|
||||||
|
};
|
||||||
|
39
dashboard/src/utils/parseCSV.ts
Normal file
39
dashboard/src/utils/parseCSV.ts
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
import _flatten from 'lodash/flatten';
|
||||||
|
import { validateEmail } from '.';
|
||||||
|
|
||||||
|
interface dataTypes {
|
||||||
|
value: string;
|
||||||
|
isInvalid: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
const parseCSV = (file: File, delimiter: string): Promise<dataTypes[]> => {
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
const reader = new FileReader();
|
||||||
|
|
||||||
|
// When the FileReader has loaded the file...
|
||||||
|
reader.onload = (e: any) => {
|
||||||
|
// Split the result to an array of lines
|
||||||
|
const lines = e.target.result.split('\n');
|
||||||
|
// Split the lines themselves by the specified
|
||||||
|
// delimiter, such as a comma
|
||||||
|
let result = lines.map((line: string) => line.split(delimiter));
|
||||||
|
// As the FileReader reads asynchronously,
|
||||||
|
// we can't just return the result; instead,
|
||||||
|
// we're passing it to a callback function
|
||||||
|
result = _flatten(result);
|
||||||
|
resolve(
|
||||||
|
result.map((email: string) => {
|
||||||
|
return {
|
||||||
|
value: email.trim(),
|
||||||
|
isInvalid: !validateEmail(email.trim()),
|
||||||
|
};
|
||||||
|
})
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Read the file content as a single string
|
||||||
|
reader.readAsText(file);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export default parseCSV;
|
@@ -15,4 +15,6 @@ const (
|
|||||||
DbTypeMongodb = "mongodb"
|
DbTypeMongodb = "mongodb"
|
||||||
// DbTypeYugabyte is the yugabyte database type
|
// DbTypeYugabyte is the yugabyte database type
|
||||||
DbTypeYugabyte = "yugabyte"
|
DbTypeYugabyte = "yugabyte"
|
||||||
|
// DbTypeMariaDB is the mariadb database type
|
||||||
|
DbTypeMariaDB = "mariadb"
|
||||||
)
|
)
|
||||||
|
@@ -1,5 +1,7 @@
|
|||||||
package constants
|
package constants
|
||||||
|
|
||||||
|
var VERSION = "0.0.1"
|
||||||
|
|
||||||
const (
|
const (
|
||||||
// Envstore identifier
|
// Envstore identifier
|
||||||
// StringStore string store identifier
|
// StringStore string store identifier
|
||||||
@@ -13,14 +15,13 @@ const (
|
|||||||
EnvKeyEnv = "ENV"
|
EnvKeyEnv = "ENV"
|
||||||
// EnvKeyEnvPath key for cli arg variable ENV_PATH
|
// EnvKeyEnvPath key for cli arg variable ENV_PATH
|
||||||
EnvKeyEnvPath = "ENV_PATH"
|
EnvKeyEnvPath = "ENV_PATH"
|
||||||
// EnvKeyVersion key for build arg version
|
|
||||||
EnvKeyVersion = "VERSION"
|
|
||||||
// EnvKeyAuthorizerURL key for env variable AUTHORIZER_URL
|
// EnvKeyAuthorizerURL key for env variable AUTHORIZER_URL
|
||||||
// TODO: remove support AUTHORIZER_URL env
|
|
||||||
EnvKeyAuthorizerURL = "AUTHORIZER_URL"
|
EnvKeyAuthorizerURL = "AUTHORIZER_URL"
|
||||||
// EnvKeyPort key for env variable PORT
|
// EnvKeyPort key for env variable PORT
|
||||||
EnvKeyPort = "PORT"
|
EnvKeyPort = "PORT"
|
||||||
|
|
||||||
|
// EnvKeyAccessTokenExpiryTime key for env variable ACCESS_TOKEN_EXPIRY_TIME
|
||||||
|
EnvKeyAccessTokenExpiryTime = "ACCESS_TOKEN_EXPIRY_TIME"
|
||||||
// EnvKeyAdminSecret key for env variable ADMIN_SECRET
|
// EnvKeyAdminSecret key for env variable ADMIN_SECRET
|
||||||
EnvKeyAdminSecret = "ADMIN_SECRET"
|
EnvKeyAdminSecret = "ADMIN_SECRET"
|
||||||
// EnvKeyDatabaseType key for env variable DATABASE_TYPE
|
// EnvKeyDatabaseType key for env variable DATABASE_TYPE
|
||||||
@@ -59,8 +60,6 @@ const (
|
|||||||
EnvKeyAdminCookieName = "ADMIN_COOKIE_NAME"
|
EnvKeyAdminCookieName = "ADMIN_COOKIE_NAME"
|
||||||
// EnvKeyResetPasswordURL key for env variable RESET_PASSWORD_URL
|
// EnvKeyResetPasswordURL key for env variable RESET_PASSWORD_URL
|
||||||
EnvKeyResetPasswordURL = "RESET_PASSWORD_URL"
|
EnvKeyResetPasswordURL = "RESET_PASSWORD_URL"
|
||||||
// EnvKeyEncryptionKey key for env variable ENCRYPTION_KEY
|
|
||||||
EnvKeyEncryptionKey = "ENCRYPTION_KEY"
|
|
||||||
// EnvKeyDisableEmailVerification key for env variable DISABLE_EMAIL_VERIFICATION
|
// EnvKeyDisableEmailVerification key for env variable DISABLE_EMAIL_VERIFICATION
|
||||||
EnvKeyDisableEmailVerification = "DISABLE_EMAIL_VERIFICATION"
|
EnvKeyDisableEmailVerification = "DISABLE_EMAIL_VERIFICATION"
|
||||||
// EnvKeyDisableBasicAuthentication key for env variable DISABLE_BASIC_AUTH
|
// EnvKeyDisableBasicAuthentication key for env variable DISABLE_BASIC_AUTH
|
||||||
@@ -69,6 +68,8 @@ const (
|
|||||||
EnvKeyDisableMagicLinkLogin = "DISABLE_MAGIC_LINK_LOGIN"
|
EnvKeyDisableMagicLinkLogin = "DISABLE_MAGIC_LINK_LOGIN"
|
||||||
// EnvKeyDisableLoginPage key for env variable DISABLE_LOGIN_PAGE
|
// EnvKeyDisableLoginPage key for env variable DISABLE_LOGIN_PAGE
|
||||||
EnvKeyDisableLoginPage = "DISABLE_LOGIN_PAGE"
|
EnvKeyDisableLoginPage = "DISABLE_LOGIN_PAGE"
|
||||||
|
// EnvKeyDisableSignUp key for env variable DISABLE_SIGN_UP
|
||||||
|
EnvKeyDisableSignUp = "DISABLE_SIGN_UP"
|
||||||
// EnvKeyRoles key for env variable ROLES
|
// EnvKeyRoles key for env variable ROLES
|
||||||
EnvKeyRoles = "ROLES"
|
EnvKeyRoles = "ROLES"
|
||||||
// EnvKeyProtectedRoles key for env variable PROTECTED_ROLES
|
// EnvKeyProtectedRoles key for env variable PROTECTED_ROLES
|
||||||
@@ -93,8 +94,18 @@ const (
|
|||||||
EnvKeyOrganizationName = "ORGANIZATION_NAME"
|
EnvKeyOrganizationName = "ORGANIZATION_NAME"
|
||||||
// EnvKeyOrganizationLogo key for env variable ORGANIZATION_LOGO
|
// EnvKeyOrganizationLogo key for env variable ORGANIZATION_LOGO
|
||||||
EnvKeyOrganizationLogo = "ORGANIZATION_LOGO"
|
EnvKeyOrganizationLogo = "ORGANIZATION_LOGO"
|
||||||
// EnvKeyIsProd key for env variable IS_PROD
|
|
||||||
EnvKeyIsProd = "IS_PROD"
|
|
||||||
// EnvKeyCustomAccessTokenScript key for env variable CUSTOM_ACCESS_TOKEN_SCRIPT
|
// EnvKeyCustomAccessTokenScript key for env variable CUSTOM_ACCESS_TOKEN_SCRIPT
|
||||||
EnvKeyCustomAccessTokenScript = "CUSTOM_ACCESS_TOKEN_SCRIPT"
|
EnvKeyCustomAccessTokenScript = "CUSTOM_ACCESS_TOKEN_SCRIPT"
|
||||||
|
|
||||||
|
// Not Exposed Keys
|
||||||
|
// EnvKeyClientID key for env variable CLIENT_ID
|
||||||
|
EnvKeyClientID = "CLIENT_ID"
|
||||||
|
// EnvKeyClientSecret key for env variable CLIENT_SECRET
|
||||||
|
EnvKeyClientSecret = "CLIENT_SECRET"
|
||||||
|
// EnvKeyEncryptionKey key for env variable ENCRYPTION_KEY
|
||||||
|
EnvKeyEncryptionKey = "ENCRYPTION_KEY"
|
||||||
|
// EnvKeyJWK key for env variable JWK
|
||||||
|
EnvKeyJWK = "JWK"
|
||||||
|
// EnvKeyIsProd key for env variable IS_PROD
|
||||||
|
EnvKeyIsProd = "IS_PROD"
|
||||||
)
|
)
|
||||||
|
@@ -5,4 +5,6 @@ const (
|
|||||||
TokenTypeRefreshToken = "refresh_token"
|
TokenTypeRefreshToken = "refresh_token"
|
||||||
// TokenTypeAccessToken is the access_token token type
|
// TokenTypeAccessToken is the access_token token type
|
||||||
TokenTypeAccessToken = "access_token"
|
TokenTypeAccessToken = "access_token"
|
||||||
|
// TokenTypeIdentityToken is the identity_token token type
|
||||||
|
TokenTypeIdentityToken = "id_token"
|
||||||
)
|
)
|
||||||
|
@@ -16,12 +16,12 @@ func SetAdminCookie(gc *gin.Context, token string) {
|
|||||||
hostname := utils.GetHost(gc)
|
hostname := utils.GetHost(gc)
|
||||||
host, _ := utils.GetHostParts(hostname)
|
host, _ := utils.GetHostParts(hostname)
|
||||||
|
|
||||||
gc.SetCookie(envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyAdminCookieName), token, 3600, "/", host, secure, httpOnly)
|
gc.SetCookie(envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyAdminCookieName), token, 3600, "/", host, secure, httpOnly)
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetAdminCookie gets the admin cookie from the request
|
// GetAdminCookie gets the admin cookie from the request
|
||||||
func GetAdminCookie(gc *gin.Context) (string, error) {
|
func GetAdminCookie(gc *gin.Context) (string, error) {
|
||||||
cookie, err := gc.Request.Cookie(envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyAdminCookieName))
|
cookie, err := gc.Request.Cookie(envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyAdminCookieName))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
@@ -42,5 +42,5 @@ func DeleteAdminCookie(gc *gin.Context) {
|
|||||||
hostname := utils.GetHost(gc)
|
hostname := utils.GetHost(gc)
|
||||||
host, _ := utils.GetHostParts(hostname)
|
host, _ := utils.GetHostParts(hostname)
|
||||||
|
|
||||||
gc.SetCookie(envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyAdminCookieName), "", -1, "/", host, secure, httpOnly)
|
gc.SetCookie(envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyAdminCookieName), "", -1, "/", host, secure, httpOnly)
|
||||||
}
|
}
|
||||||
|
@@ -10,13 +10,8 @@ import (
|
|||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
)
|
)
|
||||||
|
|
||||||
// SetCookie sets the cookie in the response. It sets 4 cookies
|
// SetSession sets the session cookie in the response
|
||||||
// 1 COOKIE_NAME.access_token jwt token for the host (temp.abc.com)
|
func SetSession(gc *gin.Context, sessionID string) {
|
||||||
// 2 COOKIE_NAME.access_token.domain jwt token for the domain (abc.com).
|
|
||||||
// 3 COOKIE_NAME.fingerprint fingerprint hash for the refresh token verification.
|
|
||||||
// 4 COOKIE_NAME.refresh_token refresh token
|
|
||||||
// Note all sites don't allow 2nd type of cookie
|
|
||||||
func SetCookie(gc *gin.Context, accessToken, refreshToken, fingerprintHash string) {
|
|
||||||
secure := true
|
secure := true
|
||||||
httpOnly := true
|
httpOnly := true
|
||||||
hostname := utils.GetHost(gc)
|
hostname := utils.GetHost(gc)
|
||||||
@@ -26,77 +21,45 @@ func SetCookie(gc *gin.Context, accessToken, refreshToken, fingerprintHash strin
|
|||||||
domain = "." + domain
|
domain = "." + domain
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO allow configuring from dashboard
|
||||||
year := 60 * 60 * 24 * 365
|
year := 60 * 60 * 24 * 365
|
||||||
thirtyMin := 60 * 30
|
|
||||||
|
|
||||||
gc.SetSameSite(http.SameSiteNoneMode)
|
gc.SetSameSite(http.SameSiteNoneMode)
|
||||||
// set cookie for host
|
gc.SetCookie(envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyCookieName)+"_session", sessionID, year, "/", host, secure, httpOnly)
|
||||||
gc.SetCookie(envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyCookieName)+".access_token", accessToken, thirtyMin, "/", host, secure, httpOnly)
|
gc.SetCookie(envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyCookieName)+"_session_domain", sessionID, year, "/", domain, secure, httpOnly)
|
||||||
|
|
||||||
// in case of subdomain, set cookie for domain
|
|
||||||
gc.SetCookie(envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyCookieName)+".access_token.domain", accessToken, thirtyMin, "/", domain, secure, httpOnly)
|
|
||||||
|
|
||||||
// set finger print cookie (this should be accessed via cookie only)
|
|
||||||
gc.SetCookie(envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyCookieName)+".fingerprint", fingerprintHash, year, "/", host, secure, httpOnly)
|
|
||||||
|
|
||||||
// set refresh token cookie (this should be accessed via cookie only)
|
|
||||||
gc.SetCookie(envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyCookieName)+".refresh_token", refreshToken, year, "/", host, secure, httpOnly)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetAccessTokenCookie to get access token cookie from the request
|
// DeleteSession sets session cookies to expire
|
||||||
func GetAccessTokenCookie(gc *gin.Context) (string, error) {
|
func DeleteSession(gc *gin.Context) {
|
||||||
cookie, err := gc.Request.Cookie(envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyCookieName) + ".access_token")
|
secure := true
|
||||||
|
httpOnly := true
|
||||||
|
hostname := utils.GetHost(gc)
|
||||||
|
host, _ := utils.GetHostParts(hostname)
|
||||||
|
domain := utils.GetDomainName(hostname)
|
||||||
|
if domain != "localhost" {
|
||||||
|
domain = "." + domain
|
||||||
|
}
|
||||||
|
|
||||||
|
gc.SetSameSite(http.SameSiteNoneMode)
|
||||||
|
gc.SetCookie(envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyCookieName)+"_session", "", -1, "/", host, secure, httpOnly)
|
||||||
|
gc.SetCookie(envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyCookieName)+"_session_domain", "", -1, "/", domain, secure, httpOnly)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetSession gets the session cookie from context
|
||||||
|
func GetSession(gc *gin.Context) (string, error) {
|
||||||
|
var cookie *http.Cookie
|
||||||
|
var err error
|
||||||
|
cookie, err = gc.Request.Cookie(envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyCookieName) + "_session")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
cookie, err = gc.Request.Cookie(envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyCookieName) + ".access_token.domain")
|
cookie, err = gc.Request.Cookie(envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyCookieName) + "_session_domain")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return cookie.Value, nil
|
decodedValue, err := url.PathUnescape(cookie.Value)
|
||||||
}
|
|
||||||
|
|
||||||
// GetRefreshTokenCookie to get refresh token cookie
|
|
||||||
func GetRefreshTokenCookie(gc *gin.Context) (string, error) {
|
|
||||||
cookie, err := gc.Request.Cookie(envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyCookieName) + ".refresh_token")
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
return cookie.Value, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetFingerPrintCookie to get fingerprint cookie
|
|
||||||
func GetFingerPrintCookie(gc *gin.Context) (string, error) {
|
|
||||||
cookie, err := gc.Request.Cookie(envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyCookieName) + ".fingerprint")
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
// cookie escapes special characters like $
|
|
||||||
// hence we need to unescape before comparing
|
|
||||||
decodedValue, err := url.QueryUnescape(cookie.Value)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
return decodedValue, nil
|
return decodedValue, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// DeleteCookie sets response cookies to expire
|
|
||||||
func DeleteCookie(gc *gin.Context) {
|
|
||||||
secure := true
|
|
||||||
httpOnly := true
|
|
||||||
hostname := utils.GetHost(gc)
|
|
||||||
host, _ := utils.GetHostParts(hostname)
|
|
||||||
domain := utils.GetDomainName(hostname)
|
|
||||||
if domain != "localhost" {
|
|
||||||
domain = "." + domain
|
|
||||||
}
|
|
||||||
|
|
||||||
gc.SetSameSite(http.SameSiteNoneMode)
|
|
||||||
gc.SetCookie(envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyCookieName)+".access_token", "", -1, "/", host, secure, httpOnly)
|
|
||||||
gc.SetCookie(envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyCookieName)+".access_token.domain", "", -1, "/", domain, secure, httpOnly)
|
|
||||||
gc.SetCookie(envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyCookieName)+".fingerprint", "", -1, "/", host, secure, httpOnly)
|
|
||||||
gc.SetCookie(envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyCookieName)+".refresh_token", "", -1, "/", host, secure, httpOnly)
|
|
||||||
}
|
|
||||||
|
@@ -1,35 +1,52 @@
|
|||||||
package utils
|
package crypto
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto/aes"
|
"crypto/aes"
|
||||||
"crypto/cipher"
|
"crypto/cipher"
|
||||||
"crypto/rand"
|
"crypto/rand"
|
||||||
"encoding/base64"
|
|
||||||
"encoding/json"
|
|
||||||
"io"
|
"io"
|
||||||
|
|
||||||
"github.com/authorizerdev/authorizer/server/constants"
|
"github.com/authorizerdev/authorizer/server/constants"
|
||||||
"github.com/authorizerdev/authorizer/server/envstore"
|
"github.com/authorizerdev/authorizer/server/envstore"
|
||||||
"golang.org/x/crypto/bcrypt"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// EncryptB64 encrypts data into base64 string
|
var bytes = []byte{35, 46, 57, 24, 85, 35, 24, 74, 87, 35, 88, 98, 66, 32, 14, 0o5}
|
||||||
func EncryptB64(text string) string {
|
|
||||||
return base64.StdEncoding.EncodeToString([]byte(text))
|
|
||||||
}
|
|
||||||
|
|
||||||
// DecryptB64 decrypts from base64 string to readable string
|
// EncryptAES method is to encrypt or hide any classified text
|
||||||
func DecryptB64(s string) (string, error) {
|
func EncryptAES(text string) (string, error) {
|
||||||
data, err := base64.StdEncoding.DecodeString(s)
|
key := []byte(envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyEncryptionKey))
|
||||||
|
block, err := aes.NewCipher(key)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
return string(data), nil
|
plainText := []byte(text)
|
||||||
|
cfb := cipher.NewCFBEncrypter(block, bytes)
|
||||||
|
cipherText := make([]byte, len(plainText))
|
||||||
|
cfb.XORKeyStream(cipherText, plainText)
|
||||||
|
return EncryptB64(string(cipherText)), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// EncryptAES encrypts data using AES algorithm
|
// DecryptAES method is to extract back the encrypted text
|
||||||
func EncryptAES(text []byte) ([]byte, error) {
|
func DecryptAES(text string) (string, error) {
|
||||||
key := []byte(envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyEncryptionKey))
|
key := []byte(envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyEncryptionKey))
|
||||||
|
block, err := aes.NewCipher(key)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
cipherText, err := DecryptB64(text)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
cfb := cipher.NewCFBDecrypter(block, bytes)
|
||||||
|
plainText := make([]byte, len(cipherText))
|
||||||
|
cfb.XORKeyStream(plainText, []byte(cipherText))
|
||||||
|
return string(plainText), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// EncryptAESEnv encrypts data using AES algorithm
|
||||||
|
// kept for the backward compatibility of env data encryption
|
||||||
|
func EncryptAESEnv(text []byte) ([]byte, error) {
|
||||||
|
key := []byte(envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyEncryptionKey))
|
||||||
c, err := aes.NewCipher(key)
|
c, err := aes.NewCipher(key)
|
||||||
var res []byte
|
var res []byte
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -62,8 +79,9 @@ func EncryptAES(text []byte) ([]byte, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// DecryptAES decrypts data using AES algorithm
|
// DecryptAES decrypts data using AES algorithm
|
||||||
func DecryptAES(ciphertext []byte) ([]byte, error) {
|
// Kept for the backward compatibility of env data decryption
|
||||||
key := []byte(envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyEncryptionKey))
|
func DecryptAESEnv(ciphertext []byte) ([]byte, error) {
|
||||||
|
key := []byte(envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyEncryptionKey))
|
||||||
c, err := aes.NewCipher(key)
|
c, err := aes.NewCipher(key)
|
||||||
var res []byte
|
var res []byte
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -88,39 +106,3 @@ func DecryptAES(ciphertext []byte) ([]byte, error) {
|
|||||||
|
|
||||||
return plaintext, nil
|
return plaintext, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// EncryptEnvData is used to encrypt the env data
|
|
||||||
func EncryptEnvData(data envstore.Store) (string, error) {
|
|
||||||
jsonBytes, err := json.Marshal(data)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
envStoreObj := envstore.EnvInMemoryStoreObj.GetEnvStoreClone()
|
|
||||||
|
|
||||||
err = json.Unmarshal(jsonBytes, &envStoreObj)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
configData, err := json.Marshal(envStoreObj)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
encryptedConfig, err := EncryptAES(configData)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
return EncryptB64(string(encryptedConfig)), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// EncryptPassword is used for encrypting password
|
|
||||||
func EncryptPassword(password string) (string, error) {
|
|
||||||
pw, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
return string(pw), nil
|
|
||||||
}
|
|
17
server/crypto/b64.go
Normal file
17
server/crypto/b64.go
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
package crypto
|
||||||
|
|
||||||
|
import "encoding/base64"
|
||||||
|
|
||||||
|
// EncryptB64 encrypts data into base64 string
|
||||||
|
func EncryptB64(text string) string {
|
||||||
|
return base64.StdEncoding.EncodeToString([]byte(text))
|
||||||
|
}
|
||||||
|
|
||||||
|
// DecryptB64 decrypts from base64 string to readable string
|
||||||
|
func DecryptB64(s string) (string, error) {
|
||||||
|
data, err := base64.StdEncoding.DecodeString(s)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return string(data), nil
|
||||||
|
}
|
114
server/crypto/common.go
Normal file
114
server/crypto/common.go
Normal file
@@ -0,0 +1,114 @@
|
|||||||
|
package crypto
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/x509"
|
||||||
|
"encoding/json"
|
||||||
|
|
||||||
|
"github.com/authorizerdev/authorizer/server/constants"
|
||||||
|
"github.com/authorizerdev/authorizer/server/envstore"
|
||||||
|
"golang.org/x/crypto/bcrypt"
|
||||||
|
"gopkg.in/square/go-jose.v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
// GetPubJWK returns JWK for given keys
|
||||||
|
func GetPubJWK(algo, keyID string, publicKey interface{}) (string, error) {
|
||||||
|
jwk := &jose.JSONWebKeySet{
|
||||||
|
Keys: []jose.JSONWebKey{
|
||||||
|
{
|
||||||
|
Algorithm: algo,
|
||||||
|
Key: publicKey,
|
||||||
|
Use: "sig",
|
||||||
|
KeyID: keyID,
|
||||||
|
Certificates: []*x509.Certificate{},
|
||||||
|
CertificateThumbprintSHA1: []uint8{},
|
||||||
|
CertificateThumbprintSHA256: []uint8{},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
jwkPublicKey, err := jwk.Keys[0].MarshalJSON()
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return string(jwkPublicKey), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GenerateJWKBasedOnEnv generates JWK based on env
|
||||||
|
// make sure clientID, jwtType, jwtSecret / public & private key pair is set
|
||||||
|
// this is called while initializing app / when env is updated
|
||||||
|
func GenerateJWKBasedOnEnv() (string, error) {
|
||||||
|
jwk := ""
|
||||||
|
algo := envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyJwtType)
|
||||||
|
clientID := envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyClientID)
|
||||||
|
|
||||||
|
var err error
|
||||||
|
// check if jwt secret is provided
|
||||||
|
if IsHMACA(algo) {
|
||||||
|
jwk, err = GetPubJWK(algo, clientID, []byte(envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyJwtSecret)))
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if IsRSA(algo) {
|
||||||
|
publicKeyInstance, err := ParseRsaPublicKeyFromPemStr(envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyJwtPublicKey))
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
jwk, err = GetPubJWK(algo, clientID, publicKeyInstance)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if IsECDSA(algo) {
|
||||||
|
publicKeyInstance, err := ParseEcdsaPublicKeyFromPemStr(envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyJwtPublicKey))
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
jwk, err = GetPubJWK(algo, clientID, publicKeyInstance)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return jwk, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// EncryptEnvData is used to encrypt the env data
|
||||||
|
func EncryptEnvData(data envstore.Store) (string, error) {
|
||||||
|
jsonBytes, err := json.Marshal(data)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
storeData := envstore.EnvStoreObj.GetEnvStoreClone()
|
||||||
|
|
||||||
|
err = json.Unmarshal(jsonBytes, &storeData)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
configData, err := json.Marshal(storeData)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
encryptedConfig, err := EncryptAESEnv(configData)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
return EncryptB64(string(encryptedConfig)), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// EncryptPassword is used for encrypting password
|
||||||
|
func EncryptPassword(password string) (string, error) {
|
||||||
|
pw, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
return string(pw), nil
|
||||||
|
}
|
154
server/crypto/ecdsa.go
Normal file
154
server/crypto/ecdsa.go
Normal file
@@ -0,0 +1,154 @@
|
|||||||
|
package crypto
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/ecdsa"
|
||||||
|
"crypto/elliptic"
|
||||||
|
"crypto/rand"
|
||||||
|
"crypto/x509"
|
||||||
|
"encoding/pem"
|
||||||
|
"errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
// NewECDSAKey to generate new ECDSA Key if env is not set
|
||||||
|
// returns key instance, private key string, public key string, jwk string, error
|
||||||
|
func NewECDSAKey(algo, keyID string) (*ecdsa.PrivateKey, string, string, string, error) {
|
||||||
|
var curve elliptic.Curve
|
||||||
|
switch algo {
|
||||||
|
case "ES256":
|
||||||
|
curve = elliptic.P256()
|
||||||
|
case "ES384":
|
||||||
|
curve = elliptic.P384()
|
||||||
|
case "ES512":
|
||||||
|
curve = elliptic.P521()
|
||||||
|
default:
|
||||||
|
return nil, "", "", "", errors.New("Invalid algo")
|
||||||
|
}
|
||||||
|
key, err := ecdsa.GenerateKey(curve, rand.Reader)
|
||||||
|
if err != nil {
|
||||||
|
return nil, "", "", "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
privateKey, publicKey, err := AsECDSAStr(key, &key.PublicKey)
|
||||||
|
if err != nil {
|
||||||
|
return nil, "", "", "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
jwkPublicKey, err := GetPubJWK(algo, keyID, &key.PublicKey)
|
||||||
|
if err != nil {
|
||||||
|
return nil, "", "", "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
return key, privateKey, publicKey, string(jwkPublicKey), err
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsECDSA checks if given string is valid ECDSA algo
|
||||||
|
func IsECDSA(algo string) bool {
|
||||||
|
switch algo {
|
||||||
|
case "ES256", "ES384", "ES512":
|
||||||
|
return true
|
||||||
|
default:
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ExportEcdsaPrivateKeyAsPemStr to get ECDSA private key as pem string
|
||||||
|
func ExportEcdsaPrivateKeyAsPemStr(privkey *ecdsa.PrivateKey) (string, error) {
|
||||||
|
privkeyBytes, err := x509.MarshalECPrivateKey(privkey)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
privkeyPem := pem.EncodeToMemory(
|
||||||
|
&pem.Block{
|
||||||
|
Type: "ECDSA PRIVATE KEY",
|
||||||
|
Bytes: privkeyBytes,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
return string(privkeyPem), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ExportEcdsaPublicKeyAsPemStr to get ECDSA public key as pem string
|
||||||
|
func ExportEcdsaPublicKeyAsPemStr(pubkey *ecdsa.PublicKey) (string, error) {
|
||||||
|
pubkeyBytes, err := x509.MarshalPKIXPublicKey(pubkey)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
pubkeyPem := pem.EncodeToMemory(
|
||||||
|
&pem.Block{
|
||||||
|
Type: "ECDSA PUBLIC KEY",
|
||||||
|
Bytes: pubkeyBytes,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
return string(pubkeyPem), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ParseEcdsaPrivateKeyFromPemStr to parse ECDSA private key from pem string
|
||||||
|
func ParseEcdsaPrivateKeyFromPemStr(privPEM string) (*ecdsa.PrivateKey, error) {
|
||||||
|
block, _ := pem.Decode([]byte(privPEM))
|
||||||
|
if block == nil {
|
||||||
|
return nil, errors.New("failed to parse PEM block containing the key")
|
||||||
|
}
|
||||||
|
|
||||||
|
priv, err := x509.ParseECPrivateKey(block.Bytes)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return priv, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ParseEcdsaPublicKeyFromPemStr to parse ECDSA public key from pem string
|
||||||
|
func ParseEcdsaPublicKeyFromPemStr(pubPEM string) (*ecdsa.PublicKey, error) {
|
||||||
|
block, _ := pem.Decode([]byte(pubPEM))
|
||||||
|
if block == nil {
|
||||||
|
return nil, errors.New("failed to parse PEM block containing the key")
|
||||||
|
}
|
||||||
|
|
||||||
|
pub, err := x509.ParsePKIXPublicKey(block.Bytes)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
switch pub := pub.(type) {
|
||||||
|
case *ecdsa.PublicKey:
|
||||||
|
return pub, nil
|
||||||
|
default:
|
||||||
|
break // fall through
|
||||||
|
}
|
||||||
|
return nil, errors.New("Key type is not ECDSA")
|
||||||
|
}
|
||||||
|
|
||||||
|
// AsECDSAStr returns private, public key string or error
|
||||||
|
func AsECDSAStr(privateKey *ecdsa.PrivateKey, publicKey *ecdsa.PublicKey) (string, string, error) {
|
||||||
|
// Export the keys to pem string
|
||||||
|
privPem, err := ExportEcdsaPrivateKeyAsPemStr(privateKey)
|
||||||
|
if err != nil {
|
||||||
|
return "", "", err
|
||||||
|
}
|
||||||
|
pubPem, err := ExportEcdsaPublicKeyAsPemStr(publicKey)
|
||||||
|
if err != nil {
|
||||||
|
return "", "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Import the keys from pem string
|
||||||
|
privParsed, err := ParseEcdsaPrivateKeyFromPemStr(privPem)
|
||||||
|
if err != nil {
|
||||||
|
return "", "", err
|
||||||
|
}
|
||||||
|
pubParsed, err := ParseEcdsaPublicKeyFromPemStr(pubPem)
|
||||||
|
if err != nil {
|
||||||
|
return "", "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Export the newly imported keys
|
||||||
|
privParsedPem, err := ExportEcdsaPrivateKeyAsPemStr(privParsed)
|
||||||
|
if err != nil {
|
||||||
|
return "", "", err
|
||||||
|
}
|
||||||
|
pubParsedPem, err := ExportEcdsaPublicKeyAsPemStr(pubParsed)
|
||||||
|
if err != nil {
|
||||||
|
return "", "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
return privParsedPem, pubParsedPem, nil
|
||||||
|
}
|
26
server/crypto/hmac.go
Normal file
26
server/crypto/hmac.go
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
package crypto
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/google/uuid"
|
||||||
|
)
|
||||||
|
|
||||||
|
// NewHMAC key returns new key that can be used to ecnrypt data using HMAC algo
|
||||||
|
// returns key, string, error
|
||||||
|
func NewHMACKey(algo, keyID string) (string, string, error) {
|
||||||
|
key := uuid.New().String()
|
||||||
|
jwkPublicKey, err := GetPubJWK(algo, keyID, []byte(key))
|
||||||
|
if err != nil {
|
||||||
|
return "", "", err
|
||||||
|
}
|
||||||
|
return key, string(jwkPublicKey), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsHMACValid checks if given string is valid HMCA algo
|
||||||
|
func IsHMACA(algo string) bool {
|
||||||
|
switch algo {
|
||||||
|
case "HS256", "HS384", "HS512":
|
||||||
|
return true
|
||||||
|
default:
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
118
server/crypto/rsa.go
Normal file
118
server/crypto/rsa.go
Normal file
@@ -0,0 +1,118 @@
|
|||||||
|
package crypto
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/rand"
|
||||||
|
"crypto/rsa"
|
||||||
|
"crypto/x509"
|
||||||
|
"encoding/pem"
|
||||||
|
"errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
// NewRSAKey to generate new RSA Key if env is not set
|
||||||
|
// returns key instance, private key string, public key string, jwk string, error
|
||||||
|
func NewRSAKey(algo, keyID string) (*rsa.PrivateKey, string, string, string, error) {
|
||||||
|
key, err := rsa.GenerateKey(rand.Reader, 2048)
|
||||||
|
if err != nil {
|
||||||
|
return nil, "", "", "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
privateKey, publicKey, err := AsRSAStr(key, &key.PublicKey)
|
||||||
|
if err != nil {
|
||||||
|
return nil, "", "", "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
jwkPublicKey, err := GetPubJWK(algo, keyID, &key.PublicKey)
|
||||||
|
if err != nil {
|
||||||
|
return nil, "", "", "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
return key, privateKey, publicKey, string(jwkPublicKey), err
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsRSA checks if given string is valid RSA algo
|
||||||
|
func IsRSA(algo string) bool {
|
||||||
|
switch algo {
|
||||||
|
case "RS256", "RS384", "RS512":
|
||||||
|
return true
|
||||||
|
default:
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ExportRsaPrivateKeyAsPemStr to get RSA private key as pem string
|
||||||
|
func ExportRsaPrivateKeyAsPemStr(privkey *rsa.PrivateKey) string {
|
||||||
|
privkeyBytes := x509.MarshalPKCS1PrivateKey(privkey)
|
||||||
|
privkeyPem := pem.EncodeToMemory(
|
||||||
|
&pem.Block{
|
||||||
|
Type: "RSA PRIVATE KEY",
|
||||||
|
Bytes: privkeyBytes,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
return string(privkeyPem)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ExportRsaPublicKeyAsPemStr to get RSA public key as pem string
|
||||||
|
func ExportRsaPublicKeyAsPemStr(pubkey *rsa.PublicKey) string {
|
||||||
|
pubkeyBytes := x509.MarshalPKCS1PublicKey(pubkey)
|
||||||
|
pubkeyPem := pem.EncodeToMemory(
|
||||||
|
&pem.Block{
|
||||||
|
Type: "RSA PUBLIC KEY",
|
||||||
|
Bytes: pubkeyBytes,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
return string(pubkeyPem)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ParseRsaPrivateKeyFromPemStr to parse RSA private key from pem string
|
||||||
|
func ParseRsaPrivateKeyFromPemStr(privPEM string) (*rsa.PrivateKey, error) {
|
||||||
|
block, _ := pem.Decode([]byte(privPEM))
|
||||||
|
if block == nil {
|
||||||
|
return nil, errors.New("failed to parse PEM block containing the key")
|
||||||
|
}
|
||||||
|
|
||||||
|
priv, err := x509.ParsePKCS1PrivateKey(block.Bytes)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return priv, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ParseRsaPublicKeyFromPemStr to parse RSA public key from pem string
|
||||||
|
func ParseRsaPublicKeyFromPemStr(pubPEM string) (*rsa.PublicKey, error) {
|
||||||
|
block, _ := pem.Decode([]byte(pubPEM))
|
||||||
|
if block == nil {
|
||||||
|
return nil, errors.New("failed to parse PEM block containing the key")
|
||||||
|
}
|
||||||
|
|
||||||
|
pub, err := x509.ParsePKCS1PublicKey(block.Bytes)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return pub, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// AsRSAStr returns private, public key string or error
|
||||||
|
func AsRSAStr(privateKey *rsa.PrivateKey, publickKey *rsa.PublicKey) (string, string, error) {
|
||||||
|
// Export the keys to pem string
|
||||||
|
privPem := ExportRsaPrivateKeyAsPemStr(privateKey)
|
||||||
|
pubPem := ExportRsaPublicKeyAsPemStr(publickKey)
|
||||||
|
|
||||||
|
// Import the keys from pem string
|
||||||
|
privParsed, err := ParseRsaPrivateKeyFromPemStr(privPem)
|
||||||
|
if err != nil {
|
||||||
|
return "", "", err
|
||||||
|
}
|
||||||
|
pubParsed, err := ParseRsaPublicKeyFromPemStr(pubPem)
|
||||||
|
if err != nil {
|
||||||
|
return "", "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Export the newly imported keys
|
||||||
|
privParsedPem := ExportRsaPrivateKeyAsPemStr(privParsed)
|
||||||
|
pubParsedPem := ExportRsaPublicKeyAsPemStr(pubParsed)
|
||||||
|
|
||||||
|
return privParsedPem, pubParsedPem, nil
|
||||||
|
}
|
@@ -1,8 +1,6 @@
|
|||||||
package db
|
package db
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"log"
|
|
||||||
|
|
||||||
"github.com/authorizerdev/authorizer/server/constants"
|
"github.com/authorizerdev/authorizer/server/constants"
|
||||||
"github.com/authorizerdev/authorizer/server/db/providers"
|
"github.com/authorizerdev/authorizer/server/db/providers"
|
||||||
"github.com/authorizerdev/authorizer/server/db/providers/arangodb"
|
"github.com/authorizerdev/authorizer/server/db/providers/arangodb"
|
||||||
@@ -14,31 +12,33 @@ import (
|
|||||||
// Provider returns the current database provider
|
// Provider returns the current database provider
|
||||||
var Provider providers.Provider
|
var Provider providers.Provider
|
||||||
|
|
||||||
func InitDB() {
|
func InitDB() error {
|
||||||
var err error
|
var err error
|
||||||
|
|
||||||
isSQL := envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyDatabaseType) != constants.DbTypeArangodb && envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyDatabaseType) != constants.DbTypeMongodb
|
isSQL := envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyDatabaseType) != constants.DbTypeArangodb && envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyDatabaseType) != constants.DbTypeMongodb
|
||||||
isArangoDB := envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyDatabaseType) == constants.DbTypeArangodb
|
isArangoDB := envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyDatabaseType) == constants.DbTypeArangodb
|
||||||
isMongoDB := envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyDatabaseType) == constants.DbTypeMongodb
|
isMongoDB := envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyDatabaseType) == constants.DbTypeMongodb
|
||||||
|
|
||||||
if isSQL {
|
if isSQL {
|
||||||
Provider, err = sql.NewProvider()
|
Provider, err = sql.NewProvider()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal("=> error setting sql provider:", err)
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if isArangoDB {
|
if isArangoDB {
|
||||||
Provider, err = arangodb.NewProvider()
|
Provider, err = arangodb.NewProvider()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal("=> error setting arangodb provider:", err)
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if isMongoDB {
|
if isMongoDB {
|
||||||
Provider, err = mongodb.NewProvider()
|
Provider, err = mongodb.NewProvider()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal("=> error setting arangodb provider:", err)
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
@@ -27,11 +27,16 @@ type User struct {
|
|||||||
Roles string `json:"roles" bson:"roles"`
|
Roles string `json:"roles" bson:"roles"`
|
||||||
UpdatedAt int64 `json:"updated_at" bson:"updated_at"`
|
UpdatedAt int64 `json:"updated_at" bson:"updated_at"`
|
||||||
CreatedAt int64 `json:"created_at" bson:"created_at"`
|
CreatedAt int64 `json:"created_at" bson:"created_at"`
|
||||||
|
RevokedTimestamp *int64 `json:"revoked_timestamp" bson:"revoked_timestamp"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (user *User) AsAPIUser() *model.User {
|
func (user *User) AsAPIUser() *model.User {
|
||||||
isEmailVerified := user.EmailVerifiedAt != nil
|
isEmailVerified := user.EmailVerifiedAt != nil
|
||||||
isPhoneVerified := user.PhoneNumberVerifiedAt != nil
|
isPhoneVerified := user.PhoneNumberVerifiedAt != nil
|
||||||
|
email := user.Email
|
||||||
|
createdAt := user.CreatedAt
|
||||||
|
updatedAt := user.UpdatedAt
|
||||||
|
revokedTimestamp := user.RevokedTimestamp
|
||||||
return &model.User{
|
return &model.User{
|
||||||
ID: user.ID,
|
ID: user.ID,
|
||||||
Email: user.Email,
|
Email: user.Email,
|
||||||
@@ -41,14 +46,15 @@ func (user *User) AsAPIUser() *model.User {
|
|||||||
FamilyName: user.FamilyName,
|
FamilyName: user.FamilyName,
|
||||||
MiddleName: user.MiddleName,
|
MiddleName: user.MiddleName,
|
||||||
Nickname: user.Nickname,
|
Nickname: user.Nickname,
|
||||||
PreferredUsername: &user.Email,
|
PreferredUsername: &email,
|
||||||
Gender: user.Gender,
|
Gender: user.Gender,
|
||||||
Birthdate: user.Birthdate,
|
Birthdate: user.Birthdate,
|
||||||
PhoneNumber: user.PhoneNumber,
|
PhoneNumber: user.PhoneNumber,
|
||||||
PhoneNumberVerified: &isPhoneVerified,
|
PhoneNumberVerified: &isPhoneVerified,
|
||||||
Picture: user.Picture,
|
Picture: user.Picture,
|
||||||
Roles: strings.Split(user.Roles, ","),
|
Roles: strings.Split(user.Roles, ","),
|
||||||
CreatedAt: &user.CreatedAt,
|
CreatedAt: &createdAt,
|
||||||
UpdatedAt: &user.UpdatedAt,
|
UpdatedAt: &updatedAt,
|
||||||
|
RevokedTimestamp: revokedTimestamp,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -7,21 +7,33 @@ type VerificationRequest struct {
|
|||||||
Key string `json:"_key,omitempty" bson:"_key"` // for arangodb
|
Key string `json:"_key,omitempty" bson:"_key"` // for arangodb
|
||||||
ID string `gorm:"primaryKey;type:char(36)" json:"_id" bson:"_id"`
|
ID string `gorm:"primaryKey;type:char(36)" json:"_id" bson:"_id"`
|
||||||
Token string `gorm:"type:text" json:"token" bson:"token"`
|
Token string `gorm:"type:text" json:"token" bson:"token"`
|
||||||
Identifier string `gorm:"uniqueIndex:idx_email_identifier" json:"identifier" bson:"identifier"`
|
Identifier string `gorm:"uniqueIndex:idx_email_identifier;type:varchar(64)" json:"identifier" bson:"identifier"`
|
||||||
ExpiresAt int64 `json:"expires_at" bson:"expires_at"`
|
ExpiresAt int64 `json:"expires_at" bson:"expires_at"`
|
||||||
CreatedAt int64 `json:"created_at" bson:"created_at"`
|
CreatedAt int64 `json:"created_at" bson:"created_at"`
|
||||||
UpdatedAt int64 `json:"updated_at" bson:"updated_at"`
|
UpdatedAt int64 `json:"updated_at" bson:"updated_at"`
|
||||||
Email string `gorm:"uniqueIndex:idx_email_identifier" json:"email" bson:"email"`
|
Email string `gorm:"uniqueIndex:idx_email_identifier;type:varchar(256)" json:"email" bson:"email"`
|
||||||
|
Nonce string `gorm:"type:text" json:"nonce" bson:"nonce"`
|
||||||
|
RedirectURI string `gorm:"type:text" json:"redirect_uri" bson:"redirect_uri"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (v *VerificationRequest) AsAPIVerificationRequest() *model.VerificationRequest {
|
func (v *VerificationRequest) AsAPIVerificationRequest() *model.VerificationRequest {
|
||||||
|
token := v.Token
|
||||||
|
createdAt := v.CreatedAt
|
||||||
|
updatedAt := v.UpdatedAt
|
||||||
|
email := v.Email
|
||||||
|
nonce := v.Nonce
|
||||||
|
redirectURI := v.RedirectURI
|
||||||
|
expires := v.ExpiresAt
|
||||||
|
identifier := v.Identifier
|
||||||
return &model.VerificationRequest{
|
return &model.VerificationRequest{
|
||||||
ID: v.ID,
|
ID: v.ID,
|
||||||
Token: &v.Token,
|
Token: &token,
|
||||||
Identifier: &v.Identifier,
|
Identifier: &identifier,
|
||||||
Expires: &v.ExpiresAt,
|
Expires: &expires,
|
||||||
CreatedAt: &v.CreatedAt,
|
CreatedAt: &createdAt,
|
||||||
UpdatedAt: &v.UpdatedAt,
|
UpdatedAt: &updatedAt,
|
||||||
Email: &v.Email,
|
Email: &email,
|
||||||
|
Nonce: &nonce,
|
||||||
|
RedirectURI: &redirectURI,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -24,7 +24,7 @@ type provider struct {
|
|||||||
func NewProvider() (*provider, error) {
|
func NewProvider() (*provider, error) {
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
conn, err := http.NewConnection(http.ConnectionConfig{
|
conn, err := http.NewConnection(http.ConnectionConfig{
|
||||||
Endpoints: []string{envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyDatabaseURL)},
|
Endpoints: []string{envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyDatabaseURL)},
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -39,16 +39,16 @@ func NewProvider() (*provider, error) {
|
|||||||
|
|
||||||
var arangodb driver.Database
|
var arangodb driver.Database
|
||||||
|
|
||||||
arangodb_exists, err := arangoClient.DatabaseExists(nil, envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyDatabaseName))
|
arangodb_exists, err := arangoClient.DatabaseExists(nil, envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyDatabaseName))
|
||||||
|
|
||||||
if arangodb_exists {
|
if arangodb_exists {
|
||||||
log.Println(envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyDatabaseName) + " db exists already")
|
log.Println(envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyDatabaseName) + " db exists already")
|
||||||
arangodb, err = arangoClient.Database(nil, envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyDatabaseName))
|
arangodb, err = arangoClient.Database(nil, envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyDatabaseName))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
arangodb, err = arangoClient.CreateDatabase(nil, envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyDatabaseName), nil)
|
arangodb, err = arangoClient.CreateDatabase(nil, envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyDatabaseName), nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@@ -34,7 +34,6 @@ func (p *provider) DeleteSession(userId string) error {
|
|||||||
}
|
}
|
||||||
cursor, err := p.db.Query(nil, query, bindVars)
|
cursor, err := p.db.Query(nil, query, bindVars)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Println("=> error deleting arangodb session:", err)
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
defer cursor.Close()
|
defer cursor.Close()
|
||||||
|
@@ -23,7 +23,7 @@ func (p *provider) AddUser(user models.User) (models.User, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if user.Roles == "" {
|
if user.Roles == "" {
|
||||||
user.Roles = strings.Join(envstore.EnvInMemoryStoreObj.GetSliceStoreEnvVariable(constants.EnvKeyDefaultRoles), ",")
|
user.Roles = strings.Join(envstore.EnvStoreObj.GetSliceStoreEnvVariable(constants.EnvKeyDefaultRoles), ",")
|
||||||
}
|
}
|
||||||
|
|
||||||
user.CreatedAt = time.Now().Unix()
|
user.CreatedAt = time.Now().Unix()
|
||||||
|
@@ -19,7 +19,7 @@ type provider struct {
|
|||||||
|
|
||||||
// NewProvider to initialize mongodb connection
|
// NewProvider to initialize mongodb connection
|
||||||
func NewProvider() (*provider, error) {
|
func NewProvider() (*provider, error) {
|
||||||
mongodbOptions := options.Client().ApplyURI(envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyDatabaseURL))
|
mongodbOptions := options.Client().ApplyURI(envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyDatabaseURL))
|
||||||
maxWait := time.Duration(5 * time.Second)
|
maxWait := time.Duration(5 * time.Second)
|
||||||
mongodbOptions.ConnectTimeout = &maxWait
|
mongodbOptions.ConnectTimeout = &maxWait
|
||||||
mongoClient, err := mongo.NewClient(mongodbOptions)
|
mongoClient, err := mongo.NewClient(mongodbOptions)
|
||||||
@@ -37,7 +37,7 @@ func NewProvider() (*provider, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
mongodb := mongoClient.Database(envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyDatabaseName), options.Database())
|
mongodb := mongoClient.Database(envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyDatabaseName), options.Database())
|
||||||
|
|
||||||
mongodb.CreateCollection(ctx, models.Collections.User, options.CreateCollection())
|
mongodb.CreateCollection(ctx, models.Collections.User, options.CreateCollection())
|
||||||
userCollection := mongodb.Collection(models.Collections.User, options.Collection())
|
userCollection := mongodb.Collection(models.Collections.User, options.Collection())
|
||||||
|
@@ -21,7 +21,7 @@ func (p *provider) AddUser(user models.User) (models.User, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if user.Roles == "" {
|
if user.Roles == "" {
|
||||||
user.Roles = strings.Join(envstore.EnvInMemoryStoreObj.GetSliceStoreEnvVariable(constants.EnvKeyDefaultRoles), ",")
|
user.Roles = strings.Join(envstore.EnvStoreObj.GetSliceStoreEnvVariable(constants.EnvKeyDefaultRoles), ",")
|
||||||
}
|
}
|
||||||
user.CreatedAt = time.Now().Unix()
|
user.CreatedAt = time.Now().Unix()
|
||||||
user.UpdatedAt = time.Now().Unix()
|
user.UpdatedAt = time.Now().Unix()
|
||||||
|
@@ -41,26 +41,25 @@ func NewProvider() (*provider, error) {
|
|||||||
TablePrefix: models.Prefix,
|
TablePrefix: models.Prefix,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
switch envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyDatabaseType) {
|
switch envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyDatabaseType) {
|
||||||
case constants.DbTypePostgres, constants.DbTypeYugabyte:
|
case constants.DbTypePostgres, constants.DbTypeYugabyte:
|
||||||
sqlDB, err = gorm.Open(postgres.Open(envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyDatabaseURL)), ormConfig)
|
sqlDB, err = gorm.Open(postgres.Open(envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyDatabaseURL)), ormConfig)
|
||||||
break
|
|
||||||
case constants.DbTypeSqlite:
|
case constants.DbTypeSqlite:
|
||||||
sqlDB, err = gorm.Open(sqlite.Open(envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyDatabaseURL)), ormConfig)
|
sqlDB, err = gorm.Open(sqlite.Open(envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyDatabaseURL)), ormConfig)
|
||||||
break
|
case constants.DbTypeMysql, constants.DbTypeMariaDB:
|
||||||
case constants.DbTypeMysql:
|
sqlDB, err = gorm.Open(mysql.Open(envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyDatabaseURL)), ormConfig)
|
||||||
sqlDB, err = gorm.Open(mysql.Open(envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyDatabaseURL)), ormConfig)
|
|
||||||
break
|
|
||||||
case constants.DbTypeSqlserver:
|
case constants.DbTypeSqlserver:
|
||||||
sqlDB, err = gorm.Open(sqlserver.Open(envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyDatabaseURL)), ormConfig)
|
sqlDB, err = gorm.Open(sqlserver.Open(envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyDatabaseURL)), ormConfig)
|
||||||
break
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
sqlDB.AutoMigrate(&models.User{}, &models.VerificationRequest{}, &models.Session{}, &models.Env{})
|
err = sqlDB.AutoMigrate(&models.User{}, &models.VerificationRequest{}, &models.Session{}, &models.Env{})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
return &provider{
|
return &provider{
|
||||||
db: sqlDB,
|
db: sqlDB,
|
||||||
}, nil
|
}, nil
|
||||||
|
@@ -20,7 +20,7 @@ func (p *provider) AddUser(user models.User) (models.User, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if user.Roles == "" {
|
if user.Roles == "" {
|
||||||
user.Roles = strings.Join(envstore.EnvInMemoryStoreObj.GetSliceStoreEnvVariable(constants.EnvKeyDefaultRoles), ",")
|
user.Roles = strings.Join(envstore.EnvStoreObj.GetSliceStoreEnvVariable(constants.EnvKeyDefaultRoles), ",")
|
||||||
}
|
}
|
||||||
|
|
||||||
user.CreatedAt = time.Now().Unix()
|
user.CreatedAt = time.Now().Unix()
|
||||||
|
@@ -21,7 +21,7 @@ func (p *provider) AddVerificationRequest(verificationRequest models.Verificatio
|
|||||||
verificationRequest.UpdatedAt = time.Now().Unix()
|
verificationRequest.UpdatedAt = time.Now().Unix()
|
||||||
result := p.db.Clauses(clause.OnConflict{
|
result := p.db.Clauses(clause.OnConflict{
|
||||||
Columns: []clause.Column{{Name: "email"}, {Name: "identifier"}},
|
Columns: []clause.Column{{Name: "email"}, {Name: "identifier"}},
|
||||||
DoUpdates: clause.AssignmentColumns([]string{"token", "expires_at"}),
|
DoUpdates: clause.AssignmentColumns([]string{"token", "expires_at", "nonce", "redirect_uri"}),
|
||||||
}).Create(&verificationRequest)
|
}).Create(&verificationRequest)
|
||||||
|
|
||||||
if result.Error != nil {
|
if result.Error != nil {
|
||||||
|
@@ -31,14 +31,18 @@ func addEmailTemplate(a string, b map[string]interface{}, templateName string) s
|
|||||||
|
|
||||||
// SendMail function to send mail
|
// SendMail function to send mail
|
||||||
func SendMail(to []string, Subject, bodyMessage string) error {
|
func SendMail(to []string, Subject, bodyMessage string) error {
|
||||||
|
// dont trigger email sending in case of test
|
||||||
|
if envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyEnv) == "test" {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
m := gomail.NewMessage()
|
m := gomail.NewMessage()
|
||||||
m.SetHeader("From", envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeySenderEmail))
|
m.SetHeader("From", envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeySenderEmail))
|
||||||
m.SetHeader("To", to...)
|
m.SetHeader("To", to...)
|
||||||
m.SetHeader("Subject", Subject)
|
m.SetHeader("Subject", Subject)
|
||||||
m.SetBody("text/html", bodyMessage)
|
m.SetBody("text/html", bodyMessage)
|
||||||
port, _ := strconv.Atoi(envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeySmtpPort))
|
port, _ := strconv.Atoi(envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeySmtpPort))
|
||||||
d := gomail.NewDialer(envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeySmtpHost), port, envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeySmtpUsername), envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeySmtpPassword))
|
d := gomail.NewDialer(envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeySmtpHost), port, envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeySmtpUsername), envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeySmtpPassword))
|
||||||
if envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyEnv) == "development" {
|
if envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyEnv) == "development" {
|
||||||
d.TLSConfig = &tls.Config{InsecureSkipVerify: true}
|
d.TLSConfig = &tls.Config{InsecureSkipVerify: true}
|
||||||
}
|
}
|
||||||
if err := d.DialAndSend(m); err != nil {
|
if err := d.DialAndSend(m); err != nil {
|
||||||
|
@@ -7,9 +7,9 @@ import (
|
|||||||
|
|
||||||
// SendForgotPasswordMail to send forgot password email
|
// SendForgotPasswordMail to send forgot password email
|
||||||
func SendForgotPasswordMail(toEmail, token, hostname string) error {
|
func SendForgotPasswordMail(toEmail, token, hostname string) error {
|
||||||
resetPasswordUrl := envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyResetPasswordURL)
|
resetPasswordUrl := envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyResetPasswordURL)
|
||||||
if resetPasswordUrl == "" {
|
if resetPasswordUrl == "" {
|
||||||
envstore.EnvInMemoryStoreObj.UpdateEnvVariable(constants.StringStoreIdentifier, constants.EnvKeyResetPasswordURL, hostname+"/app/reset-password")
|
envstore.EnvStoreObj.UpdateEnvVariable(constants.StringStoreIdentifier, constants.EnvKeyResetPasswordURL, hostname+"/app/reset-password")
|
||||||
}
|
}
|
||||||
|
|
||||||
// The receiver needs to be in slice as the receive supports multiple receiver
|
// The receiver needs to be in slice as the receive supports multiple receiver
|
||||||
@@ -103,8 +103,8 @@ func SendForgotPasswordMail(toEmail, token, hostname string) error {
|
|||||||
`
|
`
|
||||||
|
|
||||||
data := make(map[string]interface{}, 3)
|
data := make(map[string]interface{}, 3)
|
||||||
data["org_logo"] = envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyOrganizationLogo)
|
data["org_logo"] = envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyOrganizationLogo)
|
||||||
data["org_name"] = envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyOrganizationName)
|
data["org_name"] = envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyOrganizationName)
|
||||||
data["verification_url"] = resetPasswordUrl + "?token=" + token
|
data["verification_url"] = resetPasswordUrl + "?token=" + token
|
||||||
message = addEmailTemplate(message, data, "reset_password_email.tmpl")
|
message = addEmailTemplate(message, data, "reset_password_email.tmpl")
|
||||||
|
|
||||||
|
113
server/email/invite_email.go
Normal file
113
server/email/invite_email.go
Normal file
@@ -0,0 +1,113 @@
|
|||||||
|
package email
|
||||||
|
|
||||||
|
import (
|
||||||
|
"log"
|
||||||
|
|
||||||
|
"github.com/authorizerdev/authorizer/server/constants"
|
||||||
|
"github.com/authorizerdev/authorizer/server/envstore"
|
||||||
|
)
|
||||||
|
|
||||||
|
// InviteEmail to send invite email
|
||||||
|
func InviteEmail(toEmail, token, verificationURL, redirectURI string) error {
|
||||||
|
// The receiver needs to be in slice as the receive supports multiple receiver
|
||||||
|
Receiver := []string{toEmail}
|
||||||
|
|
||||||
|
Subject := "Please accept the invitation"
|
||||||
|
message := `
|
||||||
|
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
|
||||||
|
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:o="urn:schemas-microsoft-com:office:office">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta content="width=device-width, initial-scale=1" name="viewport">
|
||||||
|
<meta name="x-apple-disable-message-reformatting">
|
||||||
|
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||||
|
<meta content="telephone=no" name="format-detection">
|
||||||
|
<title></title>
|
||||||
|
<!--[if (mso 16)]>
|
||||||
|
<style type="text/css">
|
||||||
|
a {}
|
||||||
|
</style>
|
||||||
|
<![endif]-->
|
||||||
|
<!--[if gte mso 9]><style>sup { font-size: 100%% !important; }</style><![endif]-->
|
||||||
|
<!--[if gte mso 9]>
|
||||||
|
<xml>
|
||||||
|
<o:OfficeDocumentSettings>
|
||||||
|
<o:AllowPNG></o:AllowPNG>
|
||||||
|
<o:PixelsPerInch>96</o:PixelsPerInch>
|
||||||
|
</o:OfficeDocumentSettings>
|
||||||
|
</xml>
|
||||||
|
<![endif]-->
|
||||||
|
</head>
|
||||||
|
<body style="font-family: sans-serif;">
|
||||||
|
<div class="es-wrapper-color">
|
||||||
|
<!--[if gte mso 9]>
|
||||||
|
<v:background xmlns:v="urn:schemas-microsoft-com:vml" fill="t">
|
||||||
|
<v:fill type="tile" color="#ffffff"></v:fill>
|
||||||
|
</v:background>
|
||||||
|
<![endif]-->
|
||||||
|
<table class="es-wrapper" width="100%%" cellspacing="0" cellpadding="0">
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td class="esd-email-paddings" valign="top">
|
||||||
|
<table class="es-content esd-footer-popover" cellspacing="0" cellpadding="0" align="center">
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td class="esd-stripe" align="center">
|
||||||
|
<table class="es-content-body" style="border-left:1px solid transparent;border-right:1px solid transparent;border-top:1px solid transparent;border-bottom:1px solid transparent;padding:20px 0px;" width="600" cellspacing="0" cellpadding="0" bgcolor="#ffffff" align="center">
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td class="esd-structure es-p20t es-p40b es-p40r es-p40l" esd-custom-block-id="8537" align="left">
|
||||||
|
<table width="100%%" cellspacing="0" cellpadding="0">
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td class="esd-container-frame" width="518" align="left">
|
||||||
|
<table width="100%%" cellspacing="0" cellpadding="0">
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td class="esd-block-image es-m-txt-c es-p5b" style="font-size:0;padding:10px" align="center"><a target="_blank" clicktracking="off"><img src="{{.org_logo}}" alt="icon" style="display: block;" title="icon" width="30"></a></td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<tr style="background: rgb(249,250,251);padding: 10px;margin-bottom:10px;border-radius:5px;">
|
||||||
|
<td class="esd-block-text es-m-txt-c es-p15t" align="center" style="padding:10px;padding-bottom:30px;">
|
||||||
|
<p>Hi there 👋</p>
|
||||||
|
<p>Join us! You are invited to sign-up for <b>{{.org_name}}</b>. Please accept the invitation by clicking the clicking the button below.</p> <br/>
|
||||||
|
<a
|
||||||
|
clicktracking="off" href="{{.verification_url}}" class="es-button" target="_blank" style="text-decoration: none;padding:10px 15px;background-color: rgba(59,130,246,1);color: #fff;font-size: 1em;border-radius:5px;">Get Started</a>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
<div style="position: absolute; left: -9999px; top: -9999px; margin: 0px;"></div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
`
|
||||||
|
data := make(map[string]interface{}, 3)
|
||||||
|
data["org_logo"] = envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyOrganizationLogo)
|
||||||
|
data["org_name"] = envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyOrganizationName)
|
||||||
|
data["verification_url"] = verificationURL + "?token=" + token + "&redirect_uri=" + redirectURI
|
||||||
|
message = addEmailTemplate(message, data, "invite_email.tmpl")
|
||||||
|
// bodyMessage := sender.WriteHTMLEmail(Receiver, Subject, message)
|
||||||
|
|
||||||
|
err := SendMail(Receiver, Subject, message)
|
||||||
|
if err != nil {
|
||||||
|
log.Println("=> error sending email:", err)
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
@@ -1,6 +1,8 @@
|
|||||||
package email
|
package email
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"log"
|
||||||
|
|
||||||
"github.com/authorizerdev/authorizer/server/constants"
|
"github.com/authorizerdev/authorizer/server/constants"
|
||||||
"github.com/authorizerdev/authorizer/server/envstore"
|
"github.com/authorizerdev/authorizer/server/envstore"
|
||||||
)
|
)
|
||||||
@@ -97,11 +99,15 @@ func SendVerificationMail(toEmail, token, hostname string) error {
|
|||||||
</html>
|
</html>
|
||||||
`
|
`
|
||||||
data := make(map[string]interface{}, 3)
|
data := make(map[string]interface{}, 3)
|
||||||
data["org_logo"] = envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyOrganizationLogo)
|
data["org_logo"] = envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyOrganizationLogo)
|
||||||
data["org_name"] = envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyOrganizationName)
|
data["org_name"] = envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyOrganizationName)
|
||||||
data["verification_url"] = hostname + "/verify_email?token=" + token
|
data["verification_url"] = hostname + "/verify_email?token=" + token
|
||||||
message = addEmailTemplate(message, data, "verify_email.tmpl")
|
message = addEmailTemplate(message, data, "verify_email.tmpl")
|
||||||
// bodyMessage := sender.WriteHTMLEmail(Receiver, Subject, message)
|
// bodyMessage := sender.WriteHTMLEmail(Receiver, Subject, message)
|
||||||
|
|
||||||
return SendMail(Receiver, Subject, message)
|
err := SendMail(Receiver, Subject, message)
|
||||||
|
if err != nil {
|
||||||
|
log.Println("=> error sending email:", err)
|
||||||
|
}
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
216
server/env/env.go
vendored
216
server/env/env.go
vendored
@@ -1,11 +1,13 @@
|
|||||||
package env
|
package env
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
"log"
|
"log"
|
||||||
"os"
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/authorizerdev/authorizer/server/constants"
|
"github.com/authorizerdev/authorizer/server/constants"
|
||||||
|
"github.com/authorizerdev/authorizer/server/crypto"
|
||||||
"github.com/authorizerdev/authorizer/server/envstore"
|
"github.com/authorizerdev/authorizer/server/envstore"
|
||||||
"github.com/authorizerdev/authorizer/server/utils"
|
"github.com/authorizerdev/authorizer/server/utils"
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
@@ -13,10 +15,85 @@ import (
|
|||||||
"github.com/joho/godotenv"
|
"github.com/joho/godotenv"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// InitRequiredEnv to initialize EnvData and through error if required env are not present
|
||||||
|
func InitRequiredEnv() error {
|
||||||
|
envPath := os.Getenv(constants.EnvKeyEnvPath)
|
||||||
|
|
||||||
|
if envPath == "" {
|
||||||
|
envPath = envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyEnvPath)
|
||||||
|
if envPath == "" {
|
||||||
|
envPath = `.env`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if envstore.ARG_ENV_FILE != nil && *envstore.ARG_ENV_FILE != "" {
|
||||||
|
envPath = *envstore.ARG_ENV_FILE
|
||||||
|
}
|
||||||
|
|
||||||
|
err := godotenv.Load(envPath)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("using OS env instead of %s file", envPath)
|
||||||
|
}
|
||||||
|
|
||||||
|
dbURL := os.Getenv(constants.EnvKeyDatabaseURL)
|
||||||
|
dbType := os.Getenv(constants.EnvKeyDatabaseType)
|
||||||
|
dbName := os.Getenv(constants.EnvKeyDatabaseName)
|
||||||
|
|
||||||
|
if strings.TrimSpace(dbType) == "" {
|
||||||
|
if envstore.ARG_DB_TYPE != nil && *envstore.ARG_DB_TYPE != "" {
|
||||||
|
dbType = strings.TrimSpace(*envstore.ARG_DB_TYPE)
|
||||||
|
}
|
||||||
|
|
||||||
|
if dbType == "" {
|
||||||
|
return errors.New("invalid database type. DATABASE_TYPE is empty")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if strings.TrimSpace(dbURL) == "" && envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyDatabaseURL) == "" {
|
||||||
|
if envstore.ARG_DB_URL != nil && *envstore.ARG_DB_URL != "" {
|
||||||
|
dbURL = strings.TrimSpace(*envstore.ARG_DB_URL)
|
||||||
|
}
|
||||||
|
|
||||||
|
if dbURL == "" {
|
||||||
|
return errors.New("invalid database url. DATABASE_URL is required")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if dbName == "" {
|
||||||
|
if dbName == "" {
|
||||||
|
dbName = "authorizer"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
envstore.EnvStoreObj.UpdateEnvVariable(constants.StringStoreIdentifier, constants.EnvKeyEnvPath, envPath)
|
||||||
|
envstore.EnvStoreObj.UpdateEnvVariable(constants.StringStoreIdentifier, constants.EnvKeyDatabaseURL, dbURL)
|
||||||
|
envstore.EnvStoreObj.UpdateEnvVariable(constants.StringStoreIdentifier, constants.EnvKeyDatabaseType, dbType)
|
||||||
|
envstore.EnvStoreObj.UpdateEnvVariable(constants.StringStoreIdentifier, constants.EnvKeyDatabaseName, dbName)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// InitEnv to initialize EnvData and through error if required env are not present
|
// InitEnv to initialize EnvData and through error if required env are not present
|
||||||
func InitEnv() {
|
func InitAllEnv() error {
|
||||||
|
envData, err := GetEnvData()
|
||||||
|
if err != nil {
|
||||||
|
log.Println("No env data found in db, using local clone of env data")
|
||||||
// get clone of current store
|
// get clone of current store
|
||||||
envData := envstore.EnvInMemoryStoreObj.GetEnvStoreClone()
|
envData = envstore.EnvStoreObj.GetEnvStoreClone()
|
||||||
|
}
|
||||||
|
|
||||||
|
clientID := envData.StringEnv[constants.EnvKeyClientID]
|
||||||
|
// unique client id for each instance
|
||||||
|
if clientID == "" {
|
||||||
|
clientID = uuid.New().String()
|
||||||
|
envData.StringEnv[constants.EnvKeyClientID] = clientID
|
||||||
|
}
|
||||||
|
|
||||||
|
clientSecret := envData.StringEnv[constants.EnvKeyClientSecret]
|
||||||
|
// unique client id for each instance
|
||||||
|
if clientSecret == "" {
|
||||||
|
clientSecret = uuid.New().String()
|
||||||
|
envData.StringEnv[constants.EnvKeyClientSecret] = clientSecret
|
||||||
|
}
|
||||||
|
|
||||||
if envData.StringEnv[constants.EnvKeyEnv] == "" {
|
if envData.StringEnv[constants.EnvKeyEnv] == "" {
|
||||||
envData.StringEnv[constants.EnvKeyEnv] = os.Getenv(constants.EnvKeyEnv)
|
envData.StringEnv[constants.EnvKeyEnv] = os.Getenv(constants.EnvKeyEnv)
|
||||||
@@ -36,17 +113,8 @@ func InitEnv() {
|
|||||||
envData.StringEnv[constants.EnvKeyAppURL] = os.Getenv(constants.EnvKeyAppURL)
|
envData.StringEnv[constants.EnvKeyAppURL] = os.Getenv(constants.EnvKeyAppURL)
|
||||||
}
|
}
|
||||||
|
|
||||||
if envData.StringEnv[constants.EnvKeyEnvPath] == "" {
|
if envData.StringEnv[constants.EnvKeyAuthorizerURL] == "" {
|
||||||
envData.StringEnv[constants.EnvKeyEnvPath] = `.env`
|
envData.StringEnv[constants.EnvKeyAuthorizerURL] = os.Getenv(constants.EnvKeyAuthorizerURL)
|
||||||
}
|
|
||||||
|
|
||||||
if envstore.ARG_ENV_FILE != nil && *envstore.ARG_ENV_FILE != "" {
|
|
||||||
envData.StringEnv[constants.EnvKeyEnvPath] = *envstore.ARG_ENV_FILE
|
|
||||||
}
|
|
||||||
|
|
||||||
err := godotenv.Load(envData.StringEnv[constants.EnvKeyEnvPath])
|
|
||||||
if err != nil {
|
|
||||||
log.Printf("using OS env instead of %s file", envData.StringEnv[constants.EnvKeyEnvPath])
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if envData.StringEnv[constants.EnvKeyPort] == "" {
|
if envData.StringEnv[constants.EnvKeyPort] == "" {
|
||||||
@@ -56,41 +124,17 @@ func InitEnv() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if envData.StringEnv[constants.EnvKeyAccessTokenExpiryTime] == "" {
|
||||||
|
envData.StringEnv[constants.EnvKeyAccessTokenExpiryTime] = os.Getenv(constants.EnvKeyAccessTokenExpiryTime)
|
||||||
|
if envData.StringEnv[constants.EnvKeyAccessTokenExpiryTime] == "" {
|
||||||
|
envData.StringEnv[constants.EnvKeyAccessTokenExpiryTime] = "30m"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if envData.StringEnv[constants.EnvKeyAdminSecret] == "" {
|
if envData.StringEnv[constants.EnvKeyAdminSecret] == "" {
|
||||||
envData.StringEnv[constants.EnvKeyAdminSecret] = os.Getenv(constants.EnvKeyAdminSecret)
|
envData.StringEnv[constants.EnvKeyAdminSecret] = os.Getenv(constants.EnvKeyAdminSecret)
|
||||||
}
|
}
|
||||||
|
|
||||||
if envData.StringEnv[constants.EnvKeyDatabaseType] == "" {
|
|
||||||
envData.StringEnv[constants.EnvKeyDatabaseType] = os.Getenv(constants.EnvKeyDatabaseType)
|
|
||||||
|
|
||||||
if envstore.ARG_DB_TYPE != nil && *envstore.ARG_DB_TYPE != "" {
|
|
||||||
envData.StringEnv[constants.EnvKeyDatabaseType] = *envstore.ARG_DB_TYPE
|
|
||||||
}
|
|
||||||
|
|
||||||
if envData.StringEnv[constants.EnvKeyDatabaseType] == "" {
|
|
||||||
panic("DATABASE_TYPE is required")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if envData.StringEnv[constants.EnvKeyDatabaseURL] == "" {
|
|
||||||
envData.StringEnv[constants.EnvKeyDatabaseURL] = os.Getenv(constants.EnvKeyDatabaseURL)
|
|
||||||
|
|
||||||
if envstore.ARG_DB_URL != nil && *envstore.ARG_DB_URL != "" {
|
|
||||||
envData.StringEnv[constants.EnvKeyDatabaseURL] = *envstore.ARG_DB_URL
|
|
||||||
}
|
|
||||||
|
|
||||||
if envData.StringEnv[constants.EnvKeyDatabaseURL] == "" {
|
|
||||||
panic("DATABASE_URL is required")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if envData.StringEnv[constants.EnvKeyDatabaseName] == "" {
|
|
||||||
envData.StringEnv[constants.EnvKeyDatabaseName] = os.Getenv(constants.EnvKeyDatabaseName)
|
|
||||||
if envData.StringEnv[constants.EnvKeyDatabaseName] == "" {
|
|
||||||
envData.StringEnv[constants.EnvKeyDatabaseName] = "authorizer"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if envData.StringEnv[constants.EnvKeySmtpHost] == "" {
|
if envData.StringEnv[constants.EnvKeySmtpHost] == "" {
|
||||||
envData.StringEnv[constants.EnvKeySmtpHost] = os.Getenv(constants.EnvKeySmtpHost)
|
envData.StringEnv[constants.EnvKeySmtpHost] = os.Getenv(constants.EnvKeySmtpHost)
|
||||||
}
|
}
|
||||||
@@ -111,26 +155,86 @@ func InitEnv() {
|
|||||||
envData.StringEnv[constants.EnvKeySenderEmail] = os.Getenv(constants.EnvKeySenderEmail)
|
envData.StringEnv[constants.EnvKeySenderEmail] = os.Getenv(constants.EnvKeySenderEmail)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
algo := envData.StringEnv[constants.EnvKeyJwtType]
|
||||||
|
if algo == "" {
|
||||||
|
envData.StringEnv[constants.EnvKeyJwtType] = os.Getenv(constants.EnvKeyJwtType)
|
||||||
|
if envData.StringEnv[constants.EnvKeyJwtType] == "" {
|
||||||
|
envData.StringEnv[constants.EnvKeyJwtType] = "RS256"
|
||||||
|
algo = envData.StringEnv[constants.EnvKeyJwtType]
|
||||||
|
} else {
|
||||||
|
algo = envData.StringEnv[constants.EnvKeyJwtType]
|
||||||
|
if !crypto.IsHMACA(algo) && !crypto.IsRSA(algo) && !crypto.IsECDSA(algo) {
|
||||||
|
return errors.New("invalid JWT_TYPE")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if crypto.IsHMACA(algo) {
|
||||||
if envData.StringEnv[constants.EnvKeyJwtSecret] == "" {
|
if envData.StringEnv[constants.EnvKeyJwtSecret] == "" {
|
||||||
envData.StringEnv[constants.EnvKeyJwtSecret] = os.Getenv(constants.EnvKeyJwtSecret)
|
envData.StringEnv[constants.EnvKeyJwtSecret] = os.Getenv(constants.EnvKeyJwtSecret)
|
||||||
if envData.StringEnv[constants.EnvKeyJwtSecret] == "" {
|
if envData.StringEnv[constants.EnvKeyJwtSecret] == "" {
|
||||||
envData.StringEnv[constants.EnvKeyJwtSecret] = uuid.New().String()
|
envData.StringEnv[constants.EnvKeyJwtSecret], _, err = crypto.NewHMACKey(algo, clientID)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if crypto.IsRSA(algo) || crypto.IsECDSA(algo) {
|
||||||
|
privateKey, publicKey := "", ""
|
||||||
|
|
||||||
if envData.StringEnv[constants.EnvKeyJwtPrivateKey] == "" {
|
if envData.StringEnv[constants.EnvKeyJwtPrivateKey] == "" {
|
||||||
envData.StringEnv[constants.EnvKeyJwtPrivateKey] = os.Getenv(constants.EnvKeyJwtPrivateKey)
|
privateKey = os.Getenv(constants.EnvKeyJwtPrivateKey)
|
||||||
}
|
}
|
||||||
|
|
||||||
if envData.StringEnv[constants.EnvKeyJwtPublicKey] == "" {
|
if envData.StringEnv[constants.EnvKeyJwtPublicKey] == "" {
|
||||||
envData.StringEnv[constants.EnvKeyJwtPublicKey] = os.Getenv(constants.EnvKeyJwtPublicKey)
|
publicKey = os.Getenv(constants.EnvKeyJwtPublicKey)
|
||||||
}
|
}
|
||||||
|
|
||||||
if envData.StringEnv[constants.EnvKeyJwtType] == "" {
|
// if algo is RSA / ECDSA, then we need to have both private and public key
|
||||||
envData.StringEnv[constants.EnvKeyJwtType] = os.Getenv(constants.EnvKeyJwtType)
|
// if either of them is not present generate new keys
|
||||||
if envData.StringEnv[constants.EnvKeyJwtType] == "" {
|
if privateKey == "" || publicKey == "" {
|
||||||
envData.StringEnv[constants.EnvKeyJwtType] = "HS256"
|
if crypto.IsRSA(algo) {
|
||||||
|
_, privateKey, publicKey, _, err = crypto.NewRSAKey(algo, clientID)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
} else if crypto.IsECDSA(algo) {
|
||||||
|
_, privateKey, publicKey, _, err = crypto.NewECDSAKey(algo, clientID)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// parse keys to make sure they are valid
|
||||||
|
if crypto.IsRSA(algo) {
|
||||||
|
_, err = crypto.ParseRsaPrivateKeyFromPemStr(privateKey)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err := crypto.ParseRsaPublicKeyFromPemStr(publicKey)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
} else if crypto.IsECDSA(algo) {
|
||||||
|
_, err = crypto.ParseEcdsaPrivateKeyFromPemStr(privateKey)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err := crypto.ParseEcdsaPublicKeyFromPemStr(publicKey)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
envData.StringEnv[constants.EnvKeyJwtPrivateKey] = privateKey
|
||||||
|
envData.StringEnv[constants.EnvKeyJwtPublicKey] = publicKey
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if envData.StringEnv[constants.EnvKeyJwtRoleClaim] == "" {
|
if envData.StringEnv[constants.EnvKeyJwtRoleClaim] == "" {
|
||||||
@@ -141,6 +245,10 @@ func InitEnv() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if envData.StringEnv[constants.EnvKeyCustomAccessTokenScript] == "" {
|
||||||
|
envData.StringEnv[constants.EnvKeyCustomAccessTokenScript] = os.Getenv(constants.EnvKeyCustomAccessTokenScript)
|
||||||
|
}
|
||||||
|
|
||||||
if envData.StringEnv[constants.EnvKeyRedisURL] == "" {
|
if envData.StringEnv[constants.EnvKeyRedisURL] == "" {
|
||||||
envData.StringEnv[constants.EnvKeyRedisURL] = os.Getenv(constants.EnvKeyRedisURL)
|
envData.StringEnv[constants.EnvKeyRedisURL] = os.Getenv(constants.EnvKeyRedisURL)
|
||||||
}
|
}
|
||||||
@@ -184,6 +292,7 @@ func InitEnv() {
|
|||||||
envData.BoolEnv[constants.EnvKeyDisableEmailVerification] = os.Getenv(constants.EnvKeyDisableEmailVerification) == "true"
|
envData.BoolEnv[constants.EnvKeyDisableEmailVerification] = os.Getenv(constants.EnvKeyDisableEmailVerification) == "true"
|
||||||
envData.BoolEnv[constants.EnvKeyDisableMagicLinkLogin] = os.Getenv(constants.EnvKeyDisableMagicLinkLogin) == "true"
|
envData.BoolEnv[constants.EnvKeyDisableMagicLinkLogin] = os.Getenv(constants.EnvKeyDisableMagicLinkLogin) == "true"
|
||||||
envData.BoolEnv[constants.EnvKeyDisableLoginPage] = os.Getenv(constants.EnvKeyDisableLoginPage) == "true"
|
envData.BoolEnv[constants.EnvKeyDisableLoginPage] = os.Getenv(constants.EnvKeyDisableLoginPage) == "true"
|
||||||
|
envData.BoolEnv[constants.EnvKeyDisableSignUp] = os.Getenv(constants.EnvKeyDisableSignUp) == "true"
|
||||||
|
|
||||||
// no need to add nil check as its already done above
|
// no need to add nil check as its already done above
|
||||||
if envData.StringEnv[constants.EnvKeySmtpHost] == "" || envData.StringEnv[constants.EnvKeySmtpUsername] == "" || envData.StringEnv[constants.EnvKeySmtpPassword] == "" || envData.StringEnv[constants.EnvKeySenderEmail] == "" && envData.StringEnv[constants.EnvKeySmtpPort] == "" {
|
if envData.StringEnv[constants.EnvKeySmtpHost] == "" || envData.StringEnv[constants.EnvKeySmtpUsername] == "" || envData.StringEnv[constants.EnvKeySmtpPassword] == "" || envData.StringEnv[constants.EnvKeySenderEmail] == "" && envData.StringEnv[constants.EnvKeySmtpPort] == "" {
|
||||||
@@ -260,7 +369,7 @@ func InitEnv() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if len(roles) > 0 && len(defaultRoles) == 0 && len(defaultRolesEnv) > 0 {
|
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`)
|
return errors.New(`invalid DEFAULT_ROLE environment variable. It can be one from give ROLES environment variable value`)
|
||||||
}
|
}
|
||||||
|
|
||||||
envData.SliceEnv[constants.EnvKeyRoles] = roles
|
envData.SliceEnv[constants.EnvKeyRoles] = roles
|
||||||
@@ -275,5 +384,6 @@ func InitEnv() {
|
|||||||
envData.StringEnv[constants.EnvKeyOrganizationLogo] = os.Getenv(constants.EnvKeyOrganizationLogo)
|
envData.StringEnv[constants.EnvKeyOrganizationLogo] = os.Getenv(constants.EnvKeyOrganizationLogo)
|
||||||
}
|
}
|
||||||
|
|
||||||
envstore.EnvInMemoryStoreObj.UpdateEnvStore(envData)
|
envstore.EnvStoreObj.UpdateEnvStore(envData)
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
81
server/env/persist_env.go
vendored
81
server/env/persist_env.go
vendored
@@ -7,14 +7,51 @@ import (
|
|||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/google/uuid"
|
||||||
|
|
||||||
"github.com/authorizerdev/authorizer/server/constants"
|
"github.com/authorizerdev/authorizer/server/constants"
|
||||||
|
"github.com/authorizerdev/authorizer/server/crypto"
|
||||||
"github.com/authorizerdev/authorizer/server/db"
|
"github.com/authorizerdev/authorizer/server/db"
|
||||||
"github.com/authorizerdev/authorizer/server/db/models"
|
"github.com/authorizerdev/authorizer/server/db/models"
|
||||||
"github.com/authorizerdev/authorizer/server/envstore"
|
"github.com/authorizerdev/authorizer/server/envstore"
|
||||||
"github.com/authorizerdev/authorizer/server/utils"
|
"github.com/authorizerdev/authorizer/server/utils"
|
||||||
"github.com/google/uuid"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// GetEnvData returns the env data from database
|
||||||
|
func GetEnvData() (envstore.Store, error) {
|
||||||
|
var result envstore.Store
|
||||||
|
env, err := db.Provider.GetEnv()
|
||||||
|
// config not found in db
|
||||||
|
if err != nil {
|
||||||
|
return result, err
|
||||||
|
}
|
||||||
|
|
||||||
|
encryptionKey := env.Hash
|
||||||
|
decryptedEncryptionKey, err := crypto.DecryptB64(encryptionKey)
|
||||||
|
if err != nil {
|
||||||
|
return result, err
|
||||||
|
}
|
||||||
|
|
||||||
|
envstore.EnvStoreObj.UpdateEnvVariable(constants.StringStoreIdentifier, constants.EnvKeyEncryptionKey, decryptedEncryptionKey)
|
||||||
|
|
||||||
|
b64DecryptedConfig, err := crypto.DecryptB64(env.EnvData)
|
||||||
|
if err != nil {
|
||||||
|
return result, err
|
||||||
|
}
|
||||||
|
|
||||||
|
decryptedConfigs, err := crypto.DecryptAESEnv([]byte(b64DecryptedConfig))
|
||||||
|
if err != nil {
|
||||||
|
return result, err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = json.Unmarshal(decryptedConfigs, &result)
|
||||||
|
if err != nil {
|
||||||
|
return result, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return result, err
|
||||||
|
}
|
||||||
|
|
||||||
// PersistEnv persists the environment variables to the database
|
// PersistEnv persists the environment variables to the database
|
||||||
func PersistEnv() error {
|
func PersistEnv() error {
|
||||||
env, err := db.Provider.GetEnv()
|
env, err := db.Provider.GetEnv()
|
||||||
@@ -22,45 +59,40 @@ func PersistEnv() error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
// AES encryption needs 32 bit key only, so we chop off last 4 characters from 36 bit uuid
|
// AES encryption needs 32 bit key only, so we chop off last 4 characters from 36 bit uuid
|
||||||
hash := uuid.New().String()[:36-4]
|
hash := uuid.New().String()[:36-4]
|
||||||
envstore.EnvInMemoryStoreObj.UpdateEnvVariable(constants.StringStoreIdentifier, constants.EnvKeyEncryptionKey, hash)
|
envstore.EnvStoreObj.UpdateEnvVariable(constants.StringStoreIdentifier, constants.EnvKeyEncryptionKey, hash)
|
||||||
encodedHash := utils.EncryptB64(hash)
|
encodedHash := crypto.EncryptB64(hash)
|
||||||
|
|
||||||
encryptedConfig, err := utils.EncryptEnvData(envstore.EnvInMemoryStoreObj.GetEnvStoreClone())
|
encryptedConfig, err := crypto.EncryptEnvData(envstore.EnvStoreObj.GetEnvStoreClone())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
// configData, err := json.Marshal()
|
|
||||||
// if err != nil {
|
|
||||||
// return err
|
|
||||||
// }
|
|
||||||
|
|
||||||
// encryptedConfig, err := utils.EncryptAES(configData)
|
|
||||||
// if err != nil {
|
|
||||||
// return err
|
|
||||||
// }
|
|
||||||
|
|
||||||
env = models.Env{
|
env = models.Env{
|
||||||
Hash: encodedHash,
|
Hash: encodedHash,
|
||||||
EnvData: encryptedConfig,
|
EnvData: encryptedConfig,
|
||||||
}
|
}
|
||||||
|
|
||||||
db.Provider.AddEnv(env)
|
env, err = db.Provider.AddEnv(env)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
// decrypt the config data from db
|
// decrypt the config data from db
|
||||||
// decryption can be done using the hash stored in db
|
// decryption can be done using the hash stored in db
|
||||||
encryptionKey := env.Hash
|
encryptionKey := env.Hash
|
||||||
decryptedEncryptionKey, err := utils.DecryptB64(encryptionKey)
|
decryptedEncryptionKey, err := crypto.DecryptB64(encryptionKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
envstore.EnvInMemoryStoreObj.UpdateEnvVariable(constants.StringStoreIdentifier, constants.EnvKeyEncryptionKey, decryptedEncryptionKey)
|
envstore.EnvStoreObj.UpdateEnvVariable(constants.StringStoreIdentifier, constants.EnvKeyEncryptionKey, decryptedEncryptionKey)
|
||||||
b64DecryptedConfig, err := utils.DecryptB64(env.EnvData)
|
|
||||||
|
b64DecryptedConfig, err := crypto.DecryptB64(env.EnvData)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
decryptedConfigs, err := utils.DecryptAES([]byte(b64DecryptedConfig))
|
decryptedConfigs, err := crypto.DecryptAESEnv([]byte(b64DecryptedConfig))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -79,6 +111,7 @@ func PersistEnv() error {
|
|||||||
hasChanged := false
|
hasChanged := false
|
||||||
|
|
||||||
for key, value := range storeData.StringEnv {
|
for key, value := range storeData.StringEnv {
|
||||||
|
// don't override unexposed envs
|
||||||
if key != constants.EnvKeyEncryptionKey {
|
if key != constants.EnvKeyEncryptionKey {
|
||||||
// check only for derivative keys
|
// check only for derivative keys
|
||||||
// No need to check for ENCRYPTION_KEY which special key we use for encrypting config data
|
// No need to check for ENCRYPTION_KEY which special key we use for encrypting config data
|
||||||
@@ -133,9 +166,16 @@ func PersistEnv() error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
envstore.EnvInMemoryStoreObj.UpdateEnvStore(storeData)
|
envstore.EnvStoreObj.UpdateEnvStore(storeData)
|
||||||
|
jwk, err := crypto.GenerateJWKBasedOnEnv()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
// updating jwk
|
||||||
|
envstore.EnvStoreObj.UpdateEnvVariable(constants.StringStoreIdentifier, constants.EnvKeyJWK, jwk)
|
||||||
|
|
||||||
if hasChanged {
|
if hasChanged {
|
||||||
encryptedConfig, err := utils.EncryptEnvData(storeData)
|
encryptedConfig, err := crypto.EncryptEnvData(storeData)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -147,7 +187,6 @@ func PersistEnv() error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
@@ -22,14 +22,13 @@ type Store struct {
|
|||||||
SliceEnv map[string][]string `json:"slice_env"`
|
SliceEnv map[string][]string `json:"slice_env"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// EnvInMemoryStore struct
|
// EnvStore struct
|
||||||
type EnvInMemoryStore struct {
|
type EnvStore struct {
|
||||||
mutex sync.Mutex
|
mutex sync.Mutex
|
||||||
store *Store
|
store *Store
|
||||||
}
|
}
|
||||||
|
|
||||||
// EnvInMemoryStoreObj global variable for EnvInMemoryStore
|
var defaultStore = &EnvStore{
|
||||||
var EnvInMemoryStoreObj = &EnvInMemoryStore{
|
|
||||||
store: &Store{
|
store: &Store{
|
||||||
StringEnv: map[string]string{
|
StringEnv: map[string]string{
|
||||||
constants.EnvKeyAdminCookieName: "authorizer-admin",
|
constants.EnvKeyAdminCookieName: "authorizer-admin",
|
||||||
@@ -42,13 +41,17 @@ var EnvInMemoryStoreObj = &EnvInMemoryStore{
|
|||||||
constants.EnvKeyDisableMagicLinkLogin: false,
|
constants.EnvKeyDisableMagicLinkLogin: false,
|
||||||
constants.EnvKeyDisableEmailVerification: false,
|
constants.EnvKeyDisableEmailVerification: false,
|
||||||
constants.EnvKeyDisableLoginPage: false,
|
constants.EnvKeyDisableLoginPage: false,
|
||||||
|
constants.EnvKeyDisableSignUp: false,
|
||||||
},
|
},
|
||||||
SliceEnv: map[string][]string{},
|
SliceEnv: map[string][]string{},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// EnvStoreObj.GetBoolStoreEnvVariable global variable for EnvStore
|
||||||
|
var EnvStoreObj = defaultStore
|
||||||
|
|
||||||
// UpdateEnvStore to update the whole env store object
|
// UpdateEnvStore to update the whole env store object
|
||||||
func (e *EnvInMemoryStore) UpdateEnvStore(store Store) {
|
func (e *EnvStore) UpdateEnvStore(store Store) {
|
||||||
e.mutex.Lock()
|
e.mutex.Lock()
|
||||||
defer e.mutex.Unlock()
|
defer e.mutex.Unlock()
|
||||||
// just override the keys + new keys
|
// just override the keys + new keys
|
||||||
@@ -67,7 +70,7 @@ func (e *EnvInMemoryStore) UpdateEnvStore(store Store) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// UpdateEnvVariable to update the particular env variable
|
// UpdateEnvVariable to update the particular env variable
|
||||||
func (e *EnvInMemoryStore) UpdateEnvVariable(storeIdentifier, key string, value interface{}) {
|
func (e *EnvStore) UpdateEnvVariable(storeIdentifier, key string, value interface{}) {
|
||||||
e.mutex.Lock()
|
e.mutex.Lock()
|
||||||
defer e.mutex.Unlock()
|
defer e.mutex.Unlock()
|
||||||
switch storeIdentifier {
|
switch storeIdentifier {
|
||||||
@@ -81,31 +84,37 @@ func (e *EnvInMemoryStore) UpdateEnvVariable(storeIdentifier, key string, value
|
|||||||
}
|
}
|
||||||
|
|
||||||
// GetStringStoreEnvVariable to get the env variable from string store object
|
// GetStringStoreEnvVariable to get the env variable from string store object
|
||||||
func (e *EnvInMemoryStore) GetStringStoreEnvVariable(key string) string {
|
func (e *EnvStore) GetStringStoreEnvVariable(key string) string {
|
||||||
// e.mutex.Lock()
|
// e.mutex.Lock()
|
||||||
// defer e.mutex.Unlock()
|
// defer e.mutex.Unlock()
|
||||||
return e.store.StringEnv[key]
|
return e.store.StringEnv[key]
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetBoolStoreEnvVariable to get the env variable from bool store object
|
// GetBoolStoreEnvVariable to get the env variable from bool store object
|
||||||
func (e *EnvInMemoryStore) GetBoolStoreEnvVariable(key string) bool {
|
func (e *EnvStore) GetBoolStoreEnvVariable(key string) bool {
|
||||||
// e.mutex.Lock()
|
// e.mutex.Lock()
|
||||||
// defer e.mutex.Unlock()
|
// defer e.mutex.Unlock()
|
||||||
return e.store.BoolEnv[key]
|
return e.store.BoolEnv[key]
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetSliceStoreEnvVariable to get the env variable from slice store object
|
// GetSliceStoreEnvVariable to get the env variable from slice store object
|
||||||
func (e *EnvInMemoryStore) GetSliceStoreEnvVariable(key string) []string {
|
func (e *EnvStore) GetSliceStoreEnvVariable(key string) []string {
|
||||||
// e.mutex.Lock()
|
// e.mutex.Lock()
|
||||||
// defer e.mutex.Unlock()
|
// defer e.mutex.Unlock()
|
||||||
return e.store.SliceEnv[key]
|
return e.store.SliceEnv[key]
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetEnvStoreClone to get clone of current env store object
|
// GetEnvStoreClone to get clone of current env store object
|
||||||
func (e *EnvInMemoryStore) GetEnvStoreClone() Store {
|
func (e *EnvStore) GetEnvStoreClone() Store {
|
||||||
e.mutex.Lock()
|
e.mutex.Lock()
|
||||||
defer e.mutex.Unlock()
|
defer e.mutex.Unlock()
|
||||||
|
|
||||||
result := *e.store
|
result := *e.store
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (e *EnvStore) ResetStore() {
|
||||||
|
e.mutex.Lock()
|
||||||
|
defer e.mutex.Unlock()
|
||||||
|
e.store = defaultStore.store
|
||||||
|
}
|
||||||
|
@@ -30,6 +30,7 @@ require (
|
|||||||
google.golang.org/protobuf v1.27.1 // indirect
|
google.golang.org/protobuf v1.27.1 // indirect
|
||||||
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect
|
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect
|
||||||
gopkg.in/mail.v2 v2.3.1
|
gopkg.in/mail.v2 v2.3.1
|
||||||
|
gopkg.in/square/go-jose.v2 v2.6.0
|
||||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||||
gorm.io/driver/mysql v1.2.1
|
gorm.io/driver/mysql v1.2.1
|
||||||
gorm.io/driver/postgres v1.2.3
|
gorm.io/driver/postgres v1.2.3
|
||||||
|
@@ -682,8 +682,9 @@ gopkg.in/mail.v2 v2.3.1/go.mod h1:htwXN1Qh09vZJ1NVKxQqHPBaCBbzKhp5GzuJEA4VJWw=
|
|||||||
gopkg.in/readline.v1 v1.0.0-20160726135117-62c6fe619375/go.mod h1:lNEQeAhU009zbRxng+XOj5ITVgY24WcbNnQopyfKoYQ=
|
gopkg.in/readline.v1 v1.0.0-20160726135117-62c6fe619375/go.mod h1:lNEQeAhU009zbRxng+XOj5ITVgY24WcbNnQopyfKoYQ=
|
||||||
gopkg.in/sourcemap.v1 v1.0.5 h1:inv58fC9f9J3TK2Y2R1NPntXEn3/wjWHkonhIUODNTI=
|
gopkg.in/sourcemap.v1 v1.0.5 h1:inv58fC9f9J3TK2Y2R1NPntXEn3/wjWHkonhIUODNTI=
|
||||||
gopkg.in/sourcemap.v1 v1.0.5/go.mod h1:2RlvNNSMglmRrcvhfuzp4hQHwOtjxlbjX7UPY/GXb78=
|
gopkg.in/sourcemap.v1 v1.0.5/go.mod h1:2RlvNNSMglmRrcvhfuzp4hQHwOtjxlbjX7UPY/GXb78=
|
||||||
gopkg.in/square/go-jose.v2 v2.5.1 h1:7odma5RETjNHWJnR32wx8t+Io4djHE1PqxCFx3iiZ2w=
|
|
||||||
gopkg.in/square/go-jose.v2 v2.5.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI=
|
gopkg.in/square/go-jose.v2 v2.5.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI=
|
||||||
|
gopkg.in/square/go-jose.v2 v2.6.0 h1:NGk74WTnPKBNUhNzQX7PYcTLUjoq7mzKk2OKbvwk2iI=
|
||||||
|
gopkg.in/square/go-jose.v2 v2.6.0/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI=
|
||||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
|
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
|
||||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
|
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
|
||||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
|
File diff suppressed because it is too large
Load Diff
@@ -13,7 +13,9 @@ type AdminSignupInput struct {
|
|||||||
type AuthResponse struct {
|
type AuthResponse struct {
|
||||||
Message string `json:"message"`
|
Message string `json:"message"`
|
||||||
AccessToken *string `json:"access_token"`
|
AccessToken *string `json:"access_token"`
|
||||||
ExpiresAt *int64 `json:"expires_at"`
|
IDToken *string `json:"id_token"`
|
||||||
|
RefreshToken *string `json:"refresh_token"`
|
||||||
|
ExpiresIn *int64 `json:"expires_in"`
|
||||||
User *User `json:"user"`
|
User *User `json:"user"`
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -22,10 +24,13 @@ type DeleteUserInput struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type Env struct {
|
type Env struct {
|
||||||
|
AccessTokenExpiryTime *string `json:"ACCESS_TOKEN_EXPIRY_TIME"`
|
||||||
AdminSecret *string `json:"ADMIN_SECRET"`
|
AdminSecret *string `json:"ADMIN_SECRET"`
|
||||||
DatabaseName *string `json:"DATABASE_NAME"`
|
DatabaseName string `json:"DATABASE_NAME"`
|
||||||
DatabaseURL *string `json:"DATABASE_URL"`
|
DatabaseURL string `json:"DATABASE_URL"`
|
||||||
DatabaseType *string `json:"DATABASE_TYPE"`
|
DatabaseType string `json:"DATABASE_TYPE"`
|
||||||
|
ClientID string `json:"CLIENT_ID"`
|
||||||
|
ClientSecret string `json:"CLIENT_SECRET"`
|
||||||
CustomAccessTokenScript *string `json:"CUSTOM_ACCESS_TOKEN_SCRIPT"`
|
CustomAccessTokenScript *string `json:"CUSTOM_ACCESS_TOKEN_SCRIPT"`
|
||||||
SMTPHost *string `json:"SMTP_HOST"`
|
SMTPHost *string `json:"SMTP_HOST"`
|
||||||
SMTPPort *string `json:"SMTP_PORT"`
|
SMTPPort *string `json:"SMTP_PORT"`
|
||||||
@@ -45,6 +50,7 @@ type Env struct {
|
|||||||
DisableBasicAuthentication *bool `json:"DISABLE_BASIC_AUTHENTICATION"`
|
DisableBasicAuthentication *bool `json:"DISABLE_BASIC_AUTHENTICATION"`
|
||||||
DisableMagicLinkLogin *bool `json:"DISABLE_MAGIC_LINK_LOGIN"`
|
DisableMagicLinkLogin *bool `json:"DISABLE_MAGIC_LINK_LOGIN"`
|
||||||
DisableLoginPage *bool `json:"DISABLE_LOGIN_PAGE"`
|
DisableLoginPage *bool `json:"DISABLE_LOGIN_PAGE"`
|
||||||
|
DisableSignUp *bool `json:"DISABLE_SIGN_UP"`
|
||||||
Roles []string `json:"ROLES"`
|
Roles []string `json:"ROLES"`
|
||||||
ProtectedRoles []string `json:"PROTECTED_ROLES"`
|
ProtectedRoles []string `json:"PROTECTED_ROLES"`
|
||||||
DefaultRoles []string `json:"DEFAULT_ROLES"`
|
DefaultRoles []string `json:"DEFAULT_ROLES"`
|
||||||
@@ -66,32 +72,54 @@ type Error struct {
|
|||||||
|
|
||||||
type ForgotPasswordInput struct {
|
type ForgotPasswordInput struct {
|
||||||
Email string `json:"email"`
|
Email string `json:"email"`
|
||||||
|
State *string `json:"state"`
|
||||||
|
RedirectURI *string `json:"redirect_uri"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type IsValidJWTQueryInput struct {
|
type GenerateJWTKeysInput struct {
|
||||||
Jwt *string `json:"jwt"`
|
Type string `json:"type"`
|
||||||
Roles []string `json:"roles"`
|
}
|
||||||
|
|
||||||
|
type GenerateJWTKeysResponse struct {
|
||||||
|
Secret *string `json:"secret"`
|
||||||
|
PublicKey *string `json:"public_key"`
|
||||||
|
PrivateKey *string `json:"private_key"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type InviteMemberInput struct {
|
||||||
|
Emails []string `json:"emails"`
|
||||||
|
RedirectURI *string `json:"redirect_uri"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type LoginInput struct {
|
type LoginInput struct {
|
||||||
Email string `json:"email"`
|
Email string `json:"email"`
|
||||||
Password string `json:"password"`
|
Password string `json:"password"`
|
||||||
Roles []string `json:"roles"`
|
Roles []string `json:"roles"`
|
||||||
|
Scope []string `json:"scope"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type MagicLinkLoginInput struct {
|
type MagicLinkLoginInput struct {
|
||||||
Email string `json:"email"`
|
Email string `json:"email"`
|
||||||
Roles []string `json:"roles"`
|
Roles []string `json:"roles"`
|
||||||
|
Scope []string `json:"scope"`
|
||||||
|
State *string `json:"state"`
|
||||||
|
RedirectURI *string `json:"redirect_uri"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type Meta struct {
|
type Meta struct {
|
||||||
Version string `json:"version"`
|
Version string `json:"version"`
|
||||||
|
ClientID string `json:"client_id"`
|
||||||
IsGoogleLoginEnabled bool `json:"is_google_login_enabled"`
|
IsGoogleLoginEnabled bool `json:"is_google_login_enabled"`
|
||||||
IsFacebookLoginEnabled bool `json:"is_facebook_login_enabled"`
|
IsFacebookLoginEnabled bool `json:"is_facebook_login_enabled"`
|
||||||
IsGithubLoginEnabled bool `json:"is_github_login_enabled"`
|
IsGithubLoginEnabled bool `json:"is_github_login_enabled"`
|
||||||
IsEmailVerificationEnabled bool `json:"is_email_verification_enabled"`
|
IsEmailVerificationEnabled bool `json:"is_email_verification_enabled"`
|
||||||
IsBasicAuthenticationEnabled bool `json:"is_basic_authentication_enabled"`
|
IsBasicAuthenticationEnabled bool `json:"is_basic_authentication_enabled"`
|
||||||
IsMagicLinkLoginEnabled bool `json:"is_magic_link_login_enabled"`
|
IsMagicLinkLoginEnabled bool `json:"is_magic_link_login_enabled"`
|
||||||
|
IsSignUpEnabled bool `json:"is_sign_up_enabled"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type OAuthRevokeInput struct {
|
||||||
|
RefreshToken string `json:"refresh_token"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type PaginatedInput struct {
|
type PaginatedInput struct {
|
||||||
@@ -127,6 +155,7 @@ type Response struct {
|
|||||||
|
|
||||||
type SessionQueryInput struct {
|
type SessionQueryInput struct {
|
||||||
Roles []string `json:"roles"`
|
Roles []string `json:"roles"`
|
||||||
|
Scope []string `json:"scope"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type SignUpInput struct {
|
type SignUpInput struct {
|
||||||
@@ -142,9 +171,16 @@ type SignUpInput struct {
|
|||||||
Password string `json:"password"`
|
Password string `json:"password"`
|
||||||
ConfirmPassword string `json:"confirm_password"`
|
ConfirmPassword string `json:"confirm_password"`
|
||||||
Roles []string `json:"roles"`
|
Roles []string `json:"roles"`
|
||||||
|
Scope []string `json:"scope"`
|
||||||
|
RedirectURI *string `json:"redirect_uri"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type UpdateAccessInput struct {
|
||||||
|
UserID string `json:"user_id"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type UpdateEnvInput struct {
|
type UpdateEnvInput struct {
|
||||||
|
AccessTokenExpiryTime *string `json:"ACCESS_TOKEN_EXPIRY_TIME"`
|
||||||
AdminSecret *string `json:"ADMIN_SECRET"`
|
AdminSecret *string `json:"ADMIN_SECRET"`
|
||||||
CustomAccessTokenScript *string `json:"CUSTOM_ACCESS_TOKEN_SCRIPT"`
|
CustomAccessTokenScript *string `json:"CUSTOM_ACCESS_TOKEN_SCRIPT"`
|
||||||
OldAdminSecret *string `json:"OLD_ADMIN_SECRET"`
|
OldAdminSecret *string `json:"OLD_ADMIN_SECRET"`
|
||||||
@@ -166,6 +202,7 @@ type UpdateEnvInput struct {
|
|||||||
DisableBasicAuthentication *bool `json:"DISABLE_BASIC_AUTHENTICATION"`
|
DisableBasicAuthentication *bool `json:"DISABLE_BASIC_AUTHENTICATION"`
|
||||||
DisableMagicLinkLogin *bool `json:"DISABLE_MAGIC_LINK_LOGIN"`
|
DisableMagicLinkLogin *bool `json:"DISABLE_MAGIC_LINK_LOGIN"`
|
||||||
DisableLoginPage *bool `json:"DISABLE_LOGIN_PAGE"`
|
DisableLoginPage *bool `json:"DISABLE_LOGIN_PAGE"`
|
||||||
|
DisableSignUp *bool `json:"DISABLE_SIGN_UP"`
|
||||||
Roles []string `json:"ROLES"`
|
Roles []string `json:"ROLES"`
|
||||||
ProtectedRoles []string `json:"PROTECTED_ROLES"`
|
ProtectedRoles []string `json:"PROTECTED_ROLES"`
|
||||||
DefaultRoles []string `json:"DEFAULT_ROLES"`
|
DefaultRoles []string `json:"DEFAULT_ROLES"`
|
||||||
@@ -228,6 +265,7 @@ type User struct {
|
|||||||
Roles []string `json:"roles"`
|
Roles []string `json:"roles"`
|
||||||
CreatedAt *int64 `json:"created_at"`
|
CreatedAt *int64 `json:"created_at"`
|
||||||
UpdatedAt *int64 `json:"updated_at"`
|
UpdatedAt *int64 `json:"updated_at"`
|
||||||
|
RevokedTimestamp *int64 `json:"revoked_timestamp"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type Users struct {
|
type Users struct {
|
||||||
@@ -235,9 +273,14 @@ type Users struct {
|
|||||||
Users []*User `json:"users"`
|
Users []*User `json:"users"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type ValidJWTResponse struct {
|
type ValidateJWTTokenInput struct {
|
||||||
Valid bool `json:"valid"`
|
TokenType string `json:"token_type"`
|
||||||
Message string `json:"message"`
|
Token string `json:"token"`
|
||||||
|
Roles []string `json:"roles"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ValidateJWTTokenResponse struct {
|
||||||
|
IsValid bool `json:"is_valid"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type VerificationRequest struct {
|
type VerificationRequest struct {
|
||||||
@@ -248,6 +291,8 @@ type VerificationRequest struct {
|
|||||||
Expires *int64 `json:"expires"`
|
Expires *int64 `json:"expires"`
|
||||||
CreatedAt *int64 `json:"created_at"`
|
CreatedAt *int64 `json:"created_at"`
|
||||||
UpdatedAt *int64 `json:"updated_at"`
|
UpdatedAt *int64 `json:"updated_at"`
|
||||||
|
Nonce *string `json:"nonce"`
|
||||||
|
RedirectURI *string `json:"redirect_uri"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type VerificationRequests struct {
|
type VerificationRequests struct {
|
||||||
|
@@ -14,12 +14,14 @@ type Pagination {
|
|||||||
|
|
||||||
type Meta {
|
type Meta {
|
||||||
version: String!
|
version: String!
|
||||||
|
client_id: String!
|
||||||
is_google_login_enabled: Boolean!
|
is_google_login_enabled: Boolean!
|
||||||
is_facebook_login_enabled: Boolean!
|
is_facebook_login_enabled: Boolean!
|
||||||
is_github_login_enabled: Boolean!
|
is_github_login_enabled: Boolean!
|
||||||
is_email_verification_enabled: Boolean!
|
is_email_verification_enabled: Boolean!
|
||||||
is_basic_authentication_enabled: Boolean!
|
is_basic_authentication_enabled: Boolean!
|
||||||
is_magic_link_login_enabled: Boolean!
|
is_magic_link_login_enabled: Boolean!
|
||||||
|
is_sign_up_enabled: Boolean!
|
||||||
}
|
}
|
||||||
|
|
||||||
type User {
|
type User {
|
||||||
@@ -41,6 +43,7 @@ type User {
|
|||||||
roles: [String!]!
|
roles: [String!]!
|
||||||
created_at: Int64
|
created_at: Int64
|
||||||
updated_at: Int64
|
updated_at: Int64
|
||||||
|
revoked_timestamp: Int64
|
||||||
}
|
}
|
||||||
|
|
||||||
type Users {
|
type Users {
|
||||||
@@ -56,6 +59,8 @@ type VerificationRequest {
|
|||||||
expires: Int64
|
expires: Int64
|
||||||
created_at: Int64
|
created_at: Int64
|
||||||
updated_at: Int64
|
updated_at: Int64
|
||||||
|
nonce: String
|
||||||
|
redirect_uri: String
|
||||||
}
|
}
|
||||||
|
|
||||||
type VerificationRequests {
|
type VerificationRequests {
|
||||||
@@ -71,7 +76,9 @@ type Error {
|
|||||||
type AuthResponse {
|
type AuthResponse {
|
||||||
message: String!
|
message: String!
|
||||||
access_token: String
|
access_token: String
|
||||||
expires_at: Int64
|
id_token: String
|
||||||
|
refresh_token: String
|
||||||
|
expires_in: Int64
|
||||||
user: User
|
user: User
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -79,16 +86,14 @@ type Response {
|
|||||||
message: String!
|
message: String!
|
||||||
}
|
}
|
||||||
|
|
||||||
type ValidJWTResponse {
|
|
||||||
valid: Boolean!
|
|
||||||
message: String!
|
|
||||||
}
|
|
||||||
|
|
||||||
type Env {
|
type Env {
|
||||||
|
ACCESS_TOKEN_EXPIRY_TIME: String
|
||||||
ADMIN_SECRET: String
|
ADMIN_SECRET: String
|
||||||
DATABASE_NAME: String
|
DATABASE_NAME: String!
|
||||||
DATABASE_URL: String
|
DATABASE_URL: String!
|
||||||
DATABASE_TYPE: String
|
DATABASE_TYPE: String!
|
||||||
|
CLIENT_ID: String!
|
||||||
|
CLIENT_SECRET: String!
|
||||||
CUSTOM_ACCESS_TOKEN_SCRIPT: String
|
CUSTOM_ACCESS_TOKEN_SCRIPT: String
|
||||||
SMTP_HOST: String
|
SMTP_HOST: String
|
||||||
SMTP_PORT: String
|
SMTP_PORT: String
|
||||||
@@ -108,6 +113,7 @@ type Env {
|
|||||||
DISABLE_BASIC_AUTHENTICATION: Boolean
|
DISABLE_BASIC_AUTHENTICATION: Boolean
|
||||||
DISABLE_MAGIC_LINK_LOGIN: Boolean
|
DISABLE_MAGIC_LINK_LOGIN: Boolean
|
||||||
DISABLE_LOGIN_PAGE: Boolean
|
DISABLE_LOGIN_PAGE: Boolean
|
||||||
|
DISABLE_SIGN_UP: Boolean
|
||||||
ROLES: [String!]
|
ROLES: [String!]
|
||||||
PROTECTED_ROLES: [String!]
|
PROTECTED_ROLES: [String!]
|
||||||
DEFAULT_ROLES: [String!]
|
DEFAULT_ROLES: [String!]
|
||||||
@@ -122,7 +128,18 @@ type Env {
|
|||||||
ORGANIZATION_LOGO: String
|
ORGANIZATION_LOGO: String
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type ValidateJWTTokenResponse {
|
||||||
|
is_valid: Boolean!
|
||||||
|
}
|
||||||
|
|
||||||
|
type GenerateJWTKeysResponse {
|
||||||
|
secret: String
|
||||||
|
public_key: String
|
||||||
|
private_key: String
|
||||||
|
}
|
||||||
|
|
||||||
input UpdateEnvInput {
|
input UpdateEnvInput {
|
||||||
|
ACCESS_TOKEN_EXPIRY_TIME: String
|
||||||
ADMIN_SECRET: String
|
ADMIN_SECRET: String
|
||||||
CUSTOM_ACCESS_TOKEN_SCRIPT: String
|
CUSTOM_ACCESS_TOKEN_SCRIPT: String
|
||||||
OLD_ADMIN_SECRET: String
|
OLD_ADMIN_SECRET: String
|
||||||
@@ -144,6 +161,7 @@ input UpdateEnvInput {
|
|||||||
DISABLE_BASIC_AUTHENTICATION: Boolean
|
DISABLE_BASIC_AUTHENTICATION: Boolean
|
||||||
DISABLE_MAGIC_LINK_LOGIN: Boolean
|
DISABLE_MAGIC_LINK_LOGIN: Boolean
|
||||||
DISABLE_LOGIN_PAGE: Boolean
|
DISABLE_LOGIN_PAGE: Boolean
|
||||||
|
DISABLE_SIGN_UP: Boolean
|
||||||
ROLES: [String!]
|
ROLES: [String!]
|
||||||
PROTECTED_ROLES: [String!]
|
PROTECTED_ROLES: [String!]
|
||||||
DEFAULT_ROLES: [String!]
|
DEFAULT_ROLES: [String!]
|
||||||
@@ -179,12 +197,15 @@ input SignUpInput {
|
|||||||
password: String!
|
password: String!
|
||||||
confirm_password: String!
|
confirm_password: String!
|
||||||
roles: [String!]
|
roles: [String!]
|
||||||
|
scope: [String!]
|
||||||
|
redirect_uri: String
|
||||||
}
|
}
|
||||||
|
|
||||||
input LoginInput {
|
input LoginInput {
|
||||||
email: String!
|
email: String!
|
||||||
password: String!
|
password: String!
|
||||||
roles: [String!]
|
roles: [String!]
|
||||||
|
scope: [String!]
|
||||||
}
|
}
|
||||||
|
|
||||||
input VerifyEmailInput {
|
input VerifyEmailInput {
|
||||||
@@ -228,6 +249,8 @@ input UpdateUserInput {
|
|||||||
|
|
||||||
input ForgotPasswordInput {
|
input ForgotPasswordInput {
|
||||||
email: String!
|
email: String!
|
||||||
|
state: String
|
||||||
|
redirect_uri: String
|
||||||
}
|
}
|
||||||
|
|
||||||
input ResetPasswordInput {
|
input ResetPasswordInput {
|
||||||
@@ -243,15 +266,14 @@ input DeleteUserInput {
|
|||||||
input MagicLinkLoginInput {
|
input MagicLinkLoginInput {
|
||||||
email: String!
|
email: String!
|
||||||
roles: [String!]
|
roles: [String!]
|
||||||
|
scope: [String!]
|
||||||
|
state: String
|
||||||
|
redirect_uri: String
|
||||||
}
|
}
|
||||||
|
|
||||||
input SessionQueryInput {
|
input SessionQueryInput {
|
||||||
roles: [String!]
|
roles: [String!]
|
||||||
}
|
scope: [String!]
|
||||||
|
|
||||||
input IsValidJWTQueryInput {
|
|
||||||
jwt: String
|
|
||||||
roles: [String!]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
input PaginationInput {
|
input PaginationInput {
|
||||||
@@ -263,6 +285,29 @@ input PaginatedInput {
|
|||||||
pagination: PaginationInput
|
pagination: PaginationInput
|
||||||
}
|
}
|
||||||
|
|
||||||
|
input OAuthRevokeInput {
|
||||||
|
refresh_token: String!
|
||||||
|
}
|
||||||
|
|
||||||
|
input InviteMemberInput {
|
||||||
|
emails: [String!]!
|
||||||
|
redirect_uri: String
|
||||||
|
}
|
||||||
|
|
||||||
|
input UpdateAccessInput {
|
||||||
|
user_id: String!
|
||||||
|
}
|
||||||
|
|
||||||
|
input ValidateJWTTokenInput {
|
||||||
|
token_type: String!
|
||||||
|
token: String!
|
||||||
|
roles: [String!]
|
||||||
|
}
|
||||||
|
|
||||||
|
input GenerateJWTKeysInput {
|
||||||
|
type: String!
|
||||||
|
}
|
||||||
|
|
||||||
type Mutation {
|
type Mutation {
|
||||||
signup(params: SignUpInput!): AuthResponse!
|
signup(params: SignUpInput!): AuthResponse!
|
||||||
login(params: LoginInput!): AuthResponse!
|
login(params: LoginInput!): AuthResponse!
|
||||||
@@ -273,6 +318,7 @@ type Mutation {
|
|||||||
resend_verify_email(params: ResendVerifyEmailInput!): Response!
|
resend_verify_email(params: ResendVerifyEmailInput!): Response!
|
||||||
forgot_password(params: ForgotPasswordInput!): Response!
|
forgot_password(params: ForgotPasswordInput!): Response!
|
||||||
reset_password(params: ResetPasswordInput!): Response!
|
reset_password(params: ResetPasswordInput!): Response!
|
||||||
|
revoke(params: OAuthRevokeInput!): Response!
|
||||||
# admin only apis
|
# admin only apis
|
||||||
_delete_user(params: DeleteUserInput!): Response!
|
_delete_user(params: DeleteUserInput!): Response!
|
||||||
_update_user(params: UpdateUserInput!): User!
|
_update_user(params: UpdateUserInput!): User!
|
||||||
@@ -280,13 +326,17 @@ type Mutation {
|
|||||||
_admin_login(params: AdminLoginInput!): Response!
|
_admin_login(params: AdminLoginInput!): Response!
|
||||||
_admin_logout: Response!
|
_admin_logout: Response!
|
||||||
_update_env(params: UpdateEnvInput!): Response!
|
_update_env(params: UpdateEnvInput!): Response!
|
||||||
|
_invite_members(params: InviteMemberInput!): Response!
|
||||||
|
_revoke_access(param: UpdateAccessInput!): Response!
|
||||||
|
_enable_access(param: UpdateAccessInput!): Response!
|
||||||
|
_generate_jwt_keys(params: GenerateJWTKeysInput!): GenerateJWTKeysResponse!
|
||||||
}
|
}
|
||||||
|
|
||||||
type Query {
|
type Query {
|
||||||
meta: Meta!
|
meta: Meta!
|
||||||
session(params: SessionQueryInput): AuthResponse!
|
session(params: SessionQueryInput): AuthResponse!
|
||||||
is_valid_jwt(params: IsValidJWTQueryInput): ValidJWTResponse!
|
|
||||||
profile: User!
|
profile: User!
|
||||||
|
validate_jwt_token(params: ValidateJWTTokenInput!): ValidateJWTTokenResponse!
|
||||||
# admin only apis
|
# admin only apis
|
||||||
_users(params: PaginatedInput): Users!
|
_users(params: PaginatedInput): Users!
|
||||||
_verification_requests(params: PaginatedInput): VerificationRequests!
|
_verification_requests(params: PaginatedInput): VerificationRequests!
|
||||||
|
@@ -47,6 +47,10 @@ func (r *mutationResolver) ResetPassword(ctx context.Context, params model.Reset
|
|||||||
return resolvers.ResetPasswordResolver(ctx, params)
|
return resolvers.ResetPasswordResolver(ctx, params)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r *mutationResolver) Revoke(ctx context.Context, params model.OAuthRevokeInput) (*model.Response, error) {
|
||||||
|
return resolvers.RevokeResolver(ctx, params)
|
||||||
|
}
|
||||||
|
|
||||||
func (r *mutationResolver) DeleteUser(ctx context.Context, params model.DeleteUserInput) (*model.Response, error) {
|
func (r *mutationResolver) DeleteUser(ctx context.Context, params model.DeleteUserInput) (*model.Response, error) {
|
||||||
return resolvers.DeleteUserResolver(ctx, params)
|
return resolvers.DeleteUserResolver(ctx, params)
|
||||||
}
|
}
|
||||||
@@ -71,6 +75,22 @@ func (r *mutationResolver) UpdateEnv(ctx context.Context, params model.UpdateEnv
|
|||||||
return resolvers.UpdateEnvResolver(ctx, params)
|
return resolvers.UpdateEnvResolver(ctx, params)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r *mutationResolver) InviteMembers(ctx context.Context, params model.InviteMemberInput) (*model.Response, error) {
|
||||||
|
return resolvers.InviteMembersResolver(ctx, params)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *mutationResolver) RevokeAccess(ctx context.Context, param model.UpdateAccessInput) (*model.Response, error) {
|
||||||
|
return resolvers.RevokeAccessResolver(ctx, param)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *mutationResolver) EnableAccess(ctx context.Context, param model.UpdateAccessInput) (*model.Response, error) {
|
||||||
|
return resolvers.EnableAccessResolver(ctx, param)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *mutationResolver) GenerateJwtKeys(ctx context.Context, params model.GenerateJWTKeysInput) (*model.GenerateJWTKeysResponse, error) {
|
||||||
|
return resolvers.GenerateJWTKeysResolver(ctx, params)
|
||||||
|
}
|
||||||
|
|
||||||
func (r *queryResolver) Meta(ctx context.Context) (*model.Meta, error) {
|
func (r *queryResolver) Meta(ctx context.Context) (*model.Meta, error) {
|
||||||
return resolvers.MetaResolver(ctx)
|
return resolvers.MetaResolver(ctx)
|
||||||
}
|
}
|
||||||
@@ -79,14 +99,14 @@ func (r *queryResolver) Session(ctx context.Context, params *model.SessionQueryI
|
|||||||
return resolvers.SessionResolver(ctx, params)
|
return resolvers.SessionResolver(ctx, params)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *queryResolver) IsValidJwt(ctx context.Context, params *model.IsValidJWTQueryInput) (*model.ValidJWTResponse, error) {
|
|
||||||
return resolvers.IsValidJwtResolver(ctx, params)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *queryResolver) Profile(ctx context.Context) (*model.User, error) {
|
func (r *queryResolver) Profile(ctx context.Context) (*model.User, error) {
|
||||||
return resolvers.ProfileResolver(ctx)
|
return resolvers.ProfileResolver(ctx)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r *queryResolver) ValidateJwtToken(ctx context.Context, params model.ValidateJWTTokenInput) (*model.ValidateJWTTokenResponse, error) {
|
||||||
|
return resolvers.ValidateJwtTokenResolver(ctx, params)
|
||||||
|
}
|
||||||
|
|
||||||
func (r *queryResolver) Users(ctx context.Context, params *model.PaginatedInput) (*model.Users, error) {
|
func (r *queryResolver) Users(ctx context.Context, params *model.PaginatedInput) (*model.Users, error) {
|
||||||
return resolvers.UsersResolver(ctx, params)
|
return resolvers.UsersResolver(ctx, params)
|
||||||
}
|
}
|
||||||
|
@@ -1,7 +1,6 @@
|
|||||||
package handlers
|
package handlers
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
|
||||||
"log"
|
"log"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
"strings"
|
||||||
@@ -23,49 +22,30 @@ type State struct {
|
|||||||
func AppHandler() gin.HandlerFunc {
|
func AppHandler() gin.HandlerFunc {
|
||||||
return func(c *gin.Context) {
|
return func(c *gin.Context) {
|
||||||
hostname := utils.GetHost(c)
|
hostname := utils.GetHost(c)
|
||||||
if envstore.EnvInMemoryStoreObj.GetBoolStoreEnvVariable(constants.EnvKeyDisableLoginPage) {
|
if envstore.EnvStoreObj.GetBoolStoreEnvVariable(constants.EnvKeyDisableLoginPage) {
|
||||||
c.JSON(400, gin.H{"error": "login page is not enabled"})
|
c.JSON(400, gin.H{"error": "login page is not enabled"})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
state := c.Query("state")
|
redirect_uri := strings.TrimSpace(c.Query("redirect_uri"))
|
||||||
|
state := strings.TrimSpace(c.Query("state"))
|
||||||
|
scopeString := strings.TrimSpace(c.Query("scope"))
|
||||||
|
|
||||||
var stateObj State
|
var scope []string
|
||||||
|
if scopeString == "" {
|
||||||
if state == "" {
|
scope = []string{"openid", "profile", "email"}
|
||||||
stateObj.AuthorizerURL = hostname
|
|
||||||
stateObj.RedirectURL = hostname + "/app"
|
|
||||||
} else {
|
} else {
|
||||||
decodedState, err := utils.DecryptB64(state)
|
scope = strings.Split(scopeString, " ")
|
||||||
if err != nil {
|
|
||||||
c.JSON(400, gin.H{"error": "[unable to decode state] invalid state"})
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
err = json.Unmarshal([]byte(decodedState), &stateObj)
|
if redirect_uri == "" {
|
||||||
if err != nil {
|
redirect_uri = hostname + "/app"
|
||||||
c.JSON(400, gin.H{"error": "[unable to parse state] invalid state"})
|
} else {
|
||||||
return
|
|
||||||
}
|
|
||||||
stateObj.AuthorizerURL = strings.TrimSuffix(stateObj.AuthorizerURL, "/")
|
|
||||||
stateObj.RedirectURL = strings.TrimSuffix(stateObj.RedirectURL, "/")
|
|
||||||
|
|
||||||
// validate redirect url with allowed origins
|
// validate redirect url with allowed origins
|
||||||
if !utils.IsValidOrigin(stateObj.RedirectURL) {
|
if !utils.IsValidOrigin(redirect_uri) {
|
||||||
c.JSON(400, gin.H{"error": "invalid redirect url"})
|
c.JSON(400, gin.H{"error": "invalid redirect url"})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if stateObj.AuthorizerURL == "" {
|
|
||||||
c.JSON(400, gin.H{"error": "invalid authorizer url"})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// validate host and domain of authorizer url
|
|
||||||
if strings.TrimSuffix(stateObj.AuthorizerURL, "/") != hostname {
|
|
||||||
c.JSON(400, gin.H{"error": "invalid host url"})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// debug the request state
|
// debug the request state
|
||||||
@@ -76,11 +56,13 @@ func AppHandler() gin.HandlerFunc {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
c.HTML(http.StatusOK, "app.tmpl", gin.H{
|
c.HTML(http.StatusOK, "app.tmpl", gin.H{
|
||||||
"data": map[string]string{
|
"data": map[string]interface{}{
|
||||||
"authorizerURL": stateObj.AuthorizerURL,
|
"authorizerURL": hostname,
|
||||||
"redirectURL": stateObj.RedirectURL,
|
"redirectURL": redirect_uri,
|
||||||
"organizationName": envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyOrganizationName),
|
"scope": scope,
|
||||||
"organizationLogo": envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyOrganizationLogo),
|
"state": state,
|
||||||
|
"organizationName": envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyOrganizationName),
|
||||||
|
"organizationLogo": envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyOrganizationLogo),
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
338
server/handlers/authorize.go
Normal file
338
server/handlers/authorize.go
Normal file
@@ -0,0 +1,338 @@
|
|||||||
|
package handlers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/authorizerdev/authorizer/server/constants"
|
||||||
|
"github.com/authorizerdev/authorizer/server/cookie"
|
||||||
|
"github.com/authorizerdev/authorizer/server/db"
|
||||||
|
"github.com/authorizerdev/authorizer/server/envstore"
|
||||||
|
"github.com/authorizerdev/authorizer/server/sessionstore"
|
||||||
|
"github.com/authorizerdev/authorizer/server/token"
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"github.com/google/uuid"
|
||||||
|
)
|
||||||
|
|
||||||
|
// AuthorizeHandler is the handler for the /authorize route
|
||||||
|
// required params
|
||||||
|
// ?redirect_uri = redirect url
|
||||||
|
// ?response_mode = to decide if result should be html or re-direct
|
||||||
|
// state[recommended] = to prevent CSRF attack (for authorizer its compulsory)
|
||||||
|
// code_challenge = to prevent CSRF attack
|
||||||
|
// code_challenge_method = to prevent CSRF attack [only sh256 is supported]
|
||||||
|
|
||||||
|
// check the flow for generating and verifying codes: https://developer.okta.com/blog/2019/08/22/okta-authjs-pkce#:~:text=PKCE%20works%20by%20having%20the,is%20called%20the%20Code%20Challenge.
|
||||||
|
func AuthorizeHandler() gin.HandlerFunc {
|
||||||
|
return func(gc *gin.Context) {
|
||||||
|
redirectURI := strings.TrimSpace(gc.Query("redirect_uri"))
|
||||||
|
responseType := strings.TrimSpace(gc.Query("response_type"))
|
||||||
|
state := strings.TrimSpace(gc.Query("state"))
|
||||||
|
codeChallenge := strings.TrimSpace(gc.Query("code_challenge"))
|
||||||
|
scopeString := strings.TrimSpace(gc.Query("scope"))
|
||||||
|
clientID := strings.TrimSpace(gc.Query("client_id"))
|
||||||
|
template := "authorize.tmpl"
|
||||||
|
responseMode := strings.TrimSpace(gc.Query("response_mode"))
|
||||||
|
|
||||||
|
var scope []string
|
||||||
|
if scopeString == "" {
|
||||||
|
scope = []string{"openid", "profile", "email"}
|
||||||
|
} else {
|
||||||
|
scope = strings.Split(scopeString, " ")
|
||||||
|
}
|
||||||
|
|
||||||
|
if responseMode == "" {
|
||||||
|
responseMode = "query"
|
||||||
|
}
|
||||||
|
|
||||||
|
if responseMode != "query" && responseMode != "web_message" {
|
||||||
|
gc.JSON(400, gin.H{"error": "invalid response mode"})
|
||||||
|
}
|
||||||
|
|
||||||
|
if redirectURI == "" {
|
||||||
|
redirectURI = "/app"
|
||||||
|
}
|
||||||
|
|
||||||
|
isQuery := responseMode == "query"
|
||||||
|
|
||||||
|
loginURL := "/app?state=" + state + "&scope=" + strings.Join(scope, " ") + "&redirect_uri=" + redirectURI
|
||||||
|
|
||||||
|
if clientID == "" {
|
||||||
|
if isQuery {
|
||||||
|
gc.Redirect(http.StatusFound, loginURL)
|
||||||
|
} else {
|
||||||
|
gc.HTML(http.StatusOK, template, gin.H{
|
||||||
|
"target_origin": redirectURI,
|
||||||
|
"authorization_response": map[string]interface{}{
|
||||||
|
"type": "authorization_response",
|
||||||
|
"response": map[string]string{
|
||||||
|
"error": "client_id is required",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if clientID != envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyClientID) {
|
||||||
|
if isQuery {
|
||||||
|
gc.Redirect(http.StatusFound, loginURL)
|
||||||
|
} else {
|
||||||
|
gc.HTML(http.StatusOK, template, gin.H{
|
||||||
|
"target_origin": redirectURI,
|
||||||
|
"authorization_response": map[string]interface{}{
|
||||||
|
"type": "authorization_response",
|
||||||
|
"response": map[string]string{
|
||||||
|
"error": "invalid_client_id",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if state == "" {
|
||||||
|
if isQuery {
|
||||||
|
gc.Redirect(http.StatusFound, loginURL)
|
||||||
|
} else {
|
||||||
|
gc.HTML(http.StatusOK, template, gin.H{
|
||||||
|
"target_origin": redirectURI,
|
||||||
|
"authorization_response": map[string]interface{}{
|
||||||
|
"type": "authorization_response",
|
||||||
|
"response": map[string]string{
|
||||||
|
"error": "state is required",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if responseType == "" {
|
||||||
|
responseType = "token"
|
||||||
|
}
|
||||||
|
|
||||||
|
isResponseTypeCode := responseType == "code"
|
||||||
|
isResponseTypeToken := responseType == "token"
|
||||||
|
|
||||||
|
if !isResponseTypeCode && !isResponseTypeToken {
|
||||||
|
if isQuery {
|
||||||
|
gc.Redirect(http.StatusFound, loginURL)
|
||||||
|
} else {
|
||||||
|
gc.HTML(http.StatusOK, template, gin.H{
|
||||||
|
"target_origin": redirectURI,
|
||||||
|
"authorization_response": map[string]interface{}{
|
||||||
|
"type": "authorization_response",
|
||||||
|
"response": map[string]string{
|
||||||
|
"error": "response_type is invalid",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if isResponseTypeCode {
|
||||||
|
if codeChallenge == "" {
|
||||||
|
if isQuery {
|
||||||
|
gc.Redirect(http.StatusFound, loginURL)
|
||||||
|
} else {
|
||||||
|
gc.HTML(http.StatusBadRequest, template, gin.H{
|
||||||
|
"target_origin": redirectURI,
|
||||||
|
"authorization_response": map[string]interface{}{
|
||||||
|
"type": "authorization_response",
|
||||||
|
"response": map[string]string{
|
||||||
|
"error": "code_challenge is required",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sessionToken, err := cookie.GetSession(gc)
|
||||||
|
if err != nil {
|
||||||
|
if isQuery {
|
||||||
|
gc.Redirect(http.StatusFound, loginURL)
|
||||||
|
} else {
|
||||||
|
gc.HTML(http.StatusOK, template, gin.H{
|
||||||
|
"target_origin": redirectURI,
|
||||||
|
"authorization_response": map[string]interface{}{
|
||||||
|
"type": "authorization_response",
|
||||||
|
"response": map[string]string{
|
||||||
|
"error": "login_required",
|
||||||
|
"error_description": "Login is required",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// get session from cookie
|
||||||
|
claims, err := token.ValidateBrowserSession(gc, sessionToken)
|
||||||
|
if err != nil {
|
||||||
|
if isQuery {
|
||||||
|
gc.Redirect(http.StatusFound, loginURL)
|
||||||
|
} else {
|
||||||
|
gc.HTML(http.StatusOK, template, gin.H{
|
||||||
|
"target_origin": redirectURI,
|
||||||
|
"authorization_response": map[string]interface{}{
|
||||||
|
"type": "authorization_response",
|
||||||
|
"response": map[string]string{
|
||||||
|
"error": "login_required",
|
||||||
|
"error_description": "Login is required",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
userID := claims.Subject
|
||||||
|
user, err := db.Provider.GetUserByID(userID)
|
||||||
|
if err != nil {
|
||||||
|
if isQuery {
|
||||||
|
gc.Redirect(http.StatusFound, loginURL)
|
||||||
|
} else {
|
||||||
|
gc.HTML(http.StatusOK, template, gin.H{
|
||||||
|
"target_origin": redirectURI,
|
||||||
|
"authorization_response": map[string]interface{}{
|
||||||
|
"type": "authorization_response",
|
||||||
|
"response": map[string]string{
|
||||||
|
"error": "signup_required",
|
||||||
|
"error_description": "Sign up required",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// if user is logged in
|
||||||
|
// based on the response type, generate the response
|
||||||
|
if isResponseTypeCode {
|
||||||
|
// rollover the session for security
|
||||||
|
sessionstore.RemoveState(sessionToken)
|
||||||
|
nonce := uuid.New().String()
|
||||||
|
newSessionTokenData, newSessionToken, err := token.CreateSessionToken(user, nonce, claims.Roles, scope)
|
||||||
|
if err != nil {
|
||||||
|
if isQuery {
|
||||||
|
gc.Redirect(http.StatusFound, loginURL)
|
||||||
|
} else {
|
||||||
|
gc.HTML(http.StatusOK, template, gin.H{
|
||||||
|
"target_origin": redirectURI,
|
||||||
|
"authorization_response": map[string]interface{}{
|
||||||
|
"type": "authorization_response",
|
||||||
|
"response": map[string]string{
|
||||||
|
"error": "login_required",
|
||||||
|
"error_description": "Login is required",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
sessionstore.SetState(newSessionToken, newSessionTokenData.Nonce+"@"+user.ID)
|
||||||
|
cookie.SetSession(gc, newSessionToken)
|
||||||
|
code := uuid.New().String()
|
||||||
|
sessionstore.SetState(codeChallenge, code+"@"+newSessionToken)
|
||||||
|
gc.HTML(http.StatusOK, template, gin.H{
|
||||||
|
"target_origin": redirectURI,
|
||||||
|
"authorization_response": map[string]interface{}{
|
||||||
|
"type": "authorization_response",
|
||||||
|
"response": map[string]string{
|
||||||
|
"code": code,
|
||||||
|
"state": state,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if isResponseTypeToken {
|
||||||
|
// rollover the session for security
|
||||||
|
authToken, err := token.CreateAuthToken(gc, user, claims.Roles, scope)
|
||||||
|
if err != nil {
|
||||||
|
if isQuery {
|
||||||
|
gc.Redirect(http.StatusFound, loginURL)
|
||||||
|
} else {
|
||||||
|
gc.HTML(http.StatusOK, template, gin.H{
|
||||||
|
"target_origin": redirectURI,
|
||||||
|
"authorization_response": map[string]interface{}{
|
||||||
|
"type": "authorization_response",
|
||||||
|
"response": map[string]string{
|
||||||
|
"error": "login_required",
|
||||||
|
"error_description": "Login is required",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
sessionstore.RemoveState(sessionToken)
|
||||||
|
sessionstore.SetState(authToken.FingerPrintHash, authToken.FingerPrint+"@"+user.ID)
|
||||||
|
sessionstore.SetState(authToken.AccessToken.Token, authToken.FingerPrint+"@"+user.ID)
|
||||||
|
cookie.SetSession(gc, authToken.FingerPrintHash)
|
||||||
|
|
||||||
|
expiresIn := authToken.AccessToken.ExpiresAt - time.Now().Unix()
|
||||||
|
if expiresIn <= 0 {
|
||||||
|
expiresIn = 1
|
||||||
|
}
|
||||||
|
|
||||||
|
// used of query mode
|
||||||
|
params := "access_token=" + authToken.AccessToken.Token + "&token_type=bearer&expires_in=" + strconv.FormatInt(expiresIn, 10) + "&state=" + state + "&id_token=" + authToken.IDToken.Token
|
||||||
|
|
||||||
|
res := map[string]interface{}{
|
||||||
|
"access_token": authToken.AccessToken.Token,
|
||||||
|
"id_token": authToken.IDToken.Token,
|
||||||
|
"state": state,
|
||||||
|
"scope": scope,
|
||||||
|
"token_type": "Bearer",
|
||||||
|
"expires_in": expiresIn,
|
||||||
|
}
|
||||||
|
|
||||||
|
if authToken.RefreshToken != nil {
|
||||||
|
res["refresh_token"] = authToken.RefreshToken.Token
|
||||||
|
params += "&refresh_token=" + authToken.RefreshToken.Token
|
||||||
|
sessionstore.SetState(authToken.RefreshToken.Token, authToken.FingerPrint+"@"+user.ID)
|
||||||
|
}
|
||||||
|
|
||||||
|
if isQuery {
|
||||||
|
if strings.Contains(redirectURI, "?") {
|
||||||
|
gc.Redirect(http.StatusFound, redirectURI+"&"+params)
|
||||||
|
} else {
|
||||||
|
gc.Redirect(http.StatusFound, redirectURI+"?"+params)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
gc.HTML(http.StatusOK, template, gin.H{
|
||||||
|
"target_origin": redirectURI,
|
||||||
|
"authorization_response": map[string]interface{}{
|
||||||
|
"type": "authorization_response",
|
||||||
|
"response": res,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if isQuery {
|
||||||
|
gc.Redirect(http.StatusFound, loginURL)
|
||||||
|
} else {
|
||||||
|
// by default return with error
|
||||||
|
gc.HTML(http.StatusOK, template, gin.H{
|
||||||
|
"target_origin": redirectURI,
|
||||||
|
"authorization_response": map[string]interface{}{
|
||||||
|
"type": "authorization_response",
|
||||||
|
"response": map[string]string{
|
||||||
|
"error": "login_required",
|
||||||
|
"error_description": "Login is required",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -13,7 +13,7 @@ func DashboardHandler() gin.HandlerFunc {
|
|||||||
return func(c *gin.Context) {
|
return func(c *gin.Context) {
|
||||||
isOnboardingCompleted := false
|
isOnboardingCompleted := false
|
||||||
|
|
||||||
if envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyAdminSecret) != "" {
|
if envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyAdminSecret) != "" {
|
||||||
isOnboardingCompleted = true
|
isOnboardingCompleted = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
30
server/handlers/jwks.go
Normal file
30
server/handlers/jwks.go
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
package handlers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
|
||||||
|
"github.com/authorizerdev/authorizer/server/constants"
|
||||||
|
"github.com/authorizerdev/authorizer/server/envstore"
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
)
|
||||||
|
|
||||||
|
func JWKsHandler() gin.HandlerFunc {
|
||||||
|
return func(c *gin.Context) {
|
||||||
|
var data map[string]string
|
||||||
|
jwk := envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyJWK)
|
||||||
|
err := json.Unmarshal([]byte(jwk), &data)
|
||||||
|
if err != nil {
|
||||||
|
c.JSON(500, gin.H{
|
||||||
|
"error": err.Error(),
|
||||||
|
})
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"keys": []map[string]string{
|
||||||
|
data,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
47
server/handlers/logout.go
Normal file
47
server/handlers/logout.go
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
package handlers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/authorizerdev/authorizer/server/cookie"
|
||||||
|
"github.com/authorizerdev/authorizer/server/crypto"
|
||||||
|
"github.com/authorizerdev/authorizer/server/sessionstore"
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Handler to logout user
|
||||||
|
func LogoutHandler() gin.HandlerFunc {
|
||||||
|
return func(gc *gin.Context) {
|
||||||
|
redirectURL := strings.TrimSpace(gc.Query("redirect_uri"))
|
||||||
|
// get fingerprint hash
|
||||||
|
fingerprintHash, err := cookie.GetSession(gc)
|
||||||
|
if err != nil {
|
||||||
|
gc.JSON(http.StatusUnauthorized, gin.H{
|
||||||
|
"error": err.Error(),
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
decryptedFingerPrint, err := crypto.DecryptAES(fingerprintHash)
|
||||||
|
if err != nil {
|
||||||
|
gc.JSON(http.StatusUnauthorized, gin.H{
|
||||||
|
"error": err.Error(),
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
fingerPrint := string(decryptedFingerPrint)
|
||||||
|
|
||||||
|
sessionstore.RemoveState(fingerPrint)
|
||||||
|
cookie.DeleteSession(gc)
|
||||||
|
|
||||||
|
if redirectURL != "" {
|
||||||
|
gc.Redirect(http.StatusFound, redirectURL)
|
||||||
|
} else {
|
||||||
|
gc.JSON(http.StatusOK, gin.H{
|
||||||
|
"message": "Logged out successfully",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -7,6 +7,7 @@ import (
|
|||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"log"
|
"log"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@@ -30,22 +31,23 @@ func OAuthCallbackHandler() gin.HandlerFunc {
|
|||||||
provider := c.Param("oauth_provider")
|
provider := c.Param("oauth_provider")
|
||||||
state := c.Request.FormValue("state")
|
state := c.Request.FormValue("state")
|
||||||
|
|
||||||
sessionState := sessionstore.GetSocailLoginState(state)
|
sessionState := sessionstore.GetState(state)
|
||||||
if sessionState == "" {
|
if sessionState == "" {
|
||||||
c.JSON(400, gin.H{"error": "invalid oauth state"})
|
c.JSON(400, gin.H{"error": "invalid oauth state"})
|
||||||
}
|
}
|
||||||
sessionstore.RemoveSocialLoginState(state)
|
sessionstore.GetState(state)
|
||||||
// contains random token, redirect url, role
|
// contains random token, redirect url, role
|
||||||
sessionSplit := strings.Split(state, "___")
|
sessionSplit := strings.Split(state, "___")
|
||||||
|
|
||||||
// TODO validate redirect url
|
if len(sessionSplit) < 3 {
|
||||||
if len(sessionSplit) < 2 {
|
|
||||||
c.JSON(400, gin.H{"error": "invalid redirect url"})
|
c.JSON(400, gin.H{"error": "invalid redirect url"})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
inputRoles := strings.Split(sessionSplit[2], ",")
|
stateValue := sessionSplit[0]
|
||||||
redirectURL := sessionSplit[1]
|
redirectURL := sessionSplit[1]
|
||||||
|
inputRoles := strings.Split(sessionSplit[2], ",")
|
||||||
|
scopes := strings.Split(sessionSplit[3], ",")
|
||||||
|
|
||||||
var err error
|
var err error
|
||||||
user := models.User{}
|
user := models.User{}
|
||||||
@@ -69,12 +71,16 @@ func OAuthCallbackHandler() gin.HandlerFunc {
|
|||||||
existingUser, err := db.Provider.GetUserByEmail(user.Email)
|
existingUser, err := db.Provider.GetUserByEmail(user.Email)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
if envstore.EnvStoreObj.GetBoolStoreEnvVariable(constants.EnvKeyDisableSignUp) {
|
||||||
|
c.JSON(400, gin.H{"error": "signup is disabled for this instance"})
|
||||||
|
return
|
||||||
|
}
|
||||||
// user not registered, register user and generate session token
|
// user not registered, register user and generate session token
|
||||||
user.SignupMethods = provider
|
user.SignupMethods = provider
|
||||||
// make sure inputRoles don't include protected roles
|
// make sure inputRoles don't include protected roles
|
||||||
hasProtectedRole := false
|
hasProtectedRole := false
|
||||||
for _, ir := range inputRoles {
|
for _, ir := range inputRoles {
|
||||||
if utils.StringSliceContains(envstore.EnvInMemoryStoreObj.GetSliceStoreEnvVariable(constants.EnvKeyProtectedRoles), ir) {
|
if utils.StringSliceContains(envstore.EnvStoreObj.GetSliceStoreEnvVariable(constants.EnvKeyProtectedRoles), ir) {
|
||||||
hasProtectedRole = true
|
hasProtectedRole = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -89,9 +95,12 @@ func OAuthCallbackHandler() gin.HandlerFunc {
|
|||||||
user.EmailVerifiedAt = &now
|
user.EmailVerifiedAt = &now
|
||||||
user, _ = db.Provider.AddUser(user)
|
user, _ = db.Provider.AddUser(user)
|
||||||
} else {
|
} else {
|
||||||
|
if user.RevokedTimestamp != nil {
|
||||||
|
c.JSON(400, gin.H{"error": "user access has been revoked"})
|
||||||
|
}
|
||||||
|
|
||||||
// user exists in db, check if method was google
|
// user exists in db, check if method was google
|
||||||
// if not append google to existing signup method and save it
|
// if not append google to existing signup method and save it
|
||||||
|
|
||||||
signupMethod := existingUser.SignupMethods
|
signupMethod := existingUser.SignupMethods
|
||||||
if !strings.Contains(signupMethod, provider) {
|
if !strings.Contains(signupMethod, provider) {
|
||||||
signupMethod = signupMethod + "," + provider
|
signupMethod = signupMethod + "," + provider
|
||||||
@@ -122,7 +131,7 @@ func OAuthCallbackHandler() gin.HandlerFunc {
|
|||||||
// check if it contains protected unassigned role
|
// check if it contains protected unassigned role
|
||||||
hasProtectedRole := false
|
hasProtectedRole := false
|
||||||
for _, ur := range unasignedRoles {
|
for _, ur := range unasignedRoles {
|
||||||
if utils.StringSliceContains(envstore.EnvInMemoryStoreObj.GetSliceStoreEnvVariable(constants.EnvKeyProtectedRoles), ur) {
|
if utils.StringSliceContains(envstore.EnvStoreObj.GetSliceStoreEnvVariable(constants.EnvKeyProtectedRoles), ur) {
|
||||||
hasProtectedRole = true
|
hasProtectedRole = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -144,10 +153,33 @@ func OAuthCallbackHandler() gin.HandlerFunc {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
authToken, _ := token.CreateAuthToken(user, inputRoles)
|
authToken, err := token.CreateAuthToken(c, user, inputRoles, scopes)
|
||||||
sessionstore.SetUserSession(user.ID, authToken.FingerPrint, authToken.RefreshToken.Token)
|
if err != nil {
|
||||||
cookie.SetCookie(c, authToken.AccessToken.Token, authToken.RefreshToken.Token, authToken.FingerPrintHash)
|
c.JSON(500, gin.H{"error": err.Error()})
|
||||||
utils.SaveSessionInDB(user.ID, c)
|
}
|
||||||
|
|
||||||
|
expiresIn := authToken.AccessToken.ExpiresAt - time.Now().Unix()
|
||||||
|
if expiresIn <= 0 {
|
||||||
|
expiresIn = 1
|
||||||
|
}
|
||||||
|
|
||||||
|
params := "access_token=" + authToken.AccessToken.Token + "&token_type=bearer&expires_in=" + strconv.FormatInt(expiresIn, 10) + "&state=" + stateValue + "&id_token=" + authToken.IDToken.Token
|
||||||
|
|
||||||
|
cookie.SetSession(c, authToken.FingerPrintHash)
|
||||||
|
sessionstore.SetState(authToken.FingerPrintHash, authToken.FingerPrint+"@"+user.ID)
|
||||||
|
sessionstore.SetState(authToken.AccessToken.Token, authToken.FingerPrint+"@"+user.ID)
|
||||||
|
|
||||||
|
if authToken.RefreshToken != nil {
|
||||||
|
params = params + `&refresh_token=` + authToken.RefreshToken.Token
|
||||||
|
sessionstore.SetState(authToken.RefreshToken.Token, authToken.FingerPrint+"@"+user.ID)
|
||||||
|
}
|
||||||
|
|
||||||
|
go utils.SaveSessionInDB(c, user.ID)
|
||||||
|
if strings.Contains(redirectURL, "?") {
|
||||||
|
redirectURL = redirectURL + "&" + params
|
||||||
|
} else {
|
||||||
|
redirectURL = redirectURL + "?" + params
|
||||||
|
}
|
||||||
|
|
||||||
c.Redirect(http.StatusTemporaryRedirect, redirectURL)
|
c.Redirect(http.StatusTemporaryRedirect, redirectURL)
|
||||||
}
|
}
|
||||||
@@ -227,7 +259,7 @@ func processGithubUserInfo(code string) (models.User, error) {
|
|||||||
GivenName: &firstName,
|
GivenName: &firstName,
|
||||||
FamilyName: &lastName,
|
FamilyName: &lastName,
|
||||||
Picture: &picture,
|
Picture: &picture,
|
||||||
Email: userRawData["email"],
|
Email: userRawData["sub"],
|
||||||
}
|
}
|
||||||
|
|
||||||
return user, nil
|
return user, nil
|
||||||
@@ -260,7 +292,7 @@ func processFacebookUserInfo(code string) (models.User, error) {
|
|||||||
userRawData := make(map[string]interface{})
|
userRawData := make(map[string]interface{})
|
||||||
json.Unmarshal(body, &userRawData)
|
json.Unmarshal(body, &userRawData)
|
||||||
|
|
||||||
email := fmt.Sprintf("%v", userRawData["email"])
|
email := fmt.Sprintf("%v", userRawData["sub"])
|
||||||
|
|
||||||
picObject := userRawData["picture"].(map[string]interface{})["data"]
|
picObject := userRawData["picture"].(map[string]interface{})["data"]
|
||||||
picDataObject := picObject.(map[string]interface{})
|
picDataObject := picObject.(map[string]interface{})
|
||||||
|
@@ -10,41 +10,59 @@ import (
|
|||||||
"github.com/authorizerdev/authorizer/server/sessionstore"
|
"github.com/authorizerdev/authorizer/server/sessionstore"
|
||||||
"github.com/authorizerdev/authorizer/server/utils"
|
"github.com/authorizerdev/authorizer/server/utils"
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"github.com/google/uuid"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// OAuthLoginHandler set host in the oauth state that is useful for redirecting to oauth_callback
|
// OAuthLoginHandler set host in the oauth state that is useful for redirecting to oauth_callback
|
||||||
func OAuthLoginHandler() gin.HandlerFunc {
|
func OAuthLoginHandler() gin.HandlerFunc {
|
||||||
return func(c *gin.Context) {
|
return func(c *gin.Context) {
|
||||||
hostname := utils.GetHost(c)
|
hostname := utils.GetHost(c)
|
||||||
redirectURL := c.Query("redirectURL")
|
// deprecating redirectURL instead use redirect_uri
|
||||||
roles := c.Query("roles")
|
redirectURI := strings.TrimSpace(c.Query("redirectURL"))
|
||||||
|
if redirectURI == "" {
|
||||||
|
redirectURI = strings.TrimSpace(c.Query("redirect_uri"))
|
||||||
|
}
|
||||||
|
roles := strings.TrimSpace(c.Query("roles"))
|
||||||
|
state := strings.TrimSpace(c.Query("state"))
|
||||||
|
scopeString := strings.TrimSpace(c.Query("scope"))
|
||||||
|
|
||||||
if redirectURL == "" {
|
if redirectURI == "" {
|
||||||
c.JSON(400, gin.H{
|
c.JSON(400, gin.H{
|
||||||
"error": "invalid redirect url",
|
"error": "invalid redirect uri",
|
||||||
})
|
})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if state == "" {
|
||||||
|
c.JSON(400, gin.H{
|
||||||
|
"error": "invalid state",
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var scope []string
|
||||||
|
if scopeString == "" {
|
||||||
|
scope = []string{"openid", "profile", "email"}
|
||||||
|
} else {
|
||||||
|
scope = strings.Split(scopeString, " ")
|
||||||
|
}
|
||||||
|
|
||||||
if roles != "" {
|
if roles != "" {
|
||||||
// validate role
|
// validate role
|
||||||
rolesSplit := strings.Split(roles, ",")
|
rolesSplit := strings.Split(roles, ",")
|
||||||
|
|
||||||
// use protected roles verification for admin login only.
|
// use protected roles verification for admin login only.
|
||||||
// though if not associated with user, it will be rejected from oauth_callback
|
// though if not associated with user, it will be rejected from oauth_callback
|
||||||
if !utils.IsValidRoles(append([]string{}, append(envstore.EnvInMemoryStoreObj.GetSliceStoreEnvVariable(constants.EnvKeyRoles), envstore.EnvInMemoryStoreObj.GetSliceStoreEnvVariable(constants.EnvKeyProtectedRoles)...)...), rolesSplit) {
|
if !utils.IsValidRoles(append([]string{}, append(envstore.EnvStoreObj.GetSliceStoreEnvVariable(constants.EnvKeyRoles), envstore.EnvStoreObj.GetSliceStoreEnvVariable(constants.EnvKeyProtectedRoles)...)...), rolesSplit) {
|
||||||
c.JSON(400, gin.H{
|
c.JSON(400, gin.H{
|
||||||
"error": "invalid role",
|
"error": "invalid role",
|
||||||
})
|
})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
roles = strings.Join(envstore.EnvInMemoryStoreObj.GetSliceStoreEnvVariable(constants.EnvKeyDefaultRoles), ",")
|
roles = strings.Join(envstore.EnvStoreObj.GetSliceStoreEnvVariable(constants.EnvKeyDefaultRoles), ",")
|
||||||
}
|
}
|
||||||
|
|
||||||
uuid := uuid.New()
|
oauthStateString := state + "___" + redirectURI + "___" + roles + "___" + strings.Join(scope, ",")
|
||||||
oauthStateString := uuid.String() + "___" + redirectURL + "___" + roles
|
|
||||||
|
|
||||||
provider := c.Param("oauth_provider")
|
provider := c.Param("oauth_provider")
|
||||||
isProviderConfigured := true
|
isProviderConfigured := true
|
||||||
@@ -54,7 +72,7 @@ func OAuthLoginHandler() gin.HandlerFunc {
|
|||||||
isProviderConfigured = false
|
isProviderConfigured = false
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
sessionstore.SetSocailLoginState(oauthStateString, constants.SignupMethodGoogle)
|
sessionstore.SetState(oauthStateString, constants.SignupMethodGoogle)
|
||||||
// during the init of OAuthProvider authorizer url might be empty
|
// during the init of OAuthProvider authorizer url might be empty
|
||||||
oauth.OAuthProviders.GoogleConfig.RedirectURL = hostname + "/oauth_callback/google"
|
oauth.OAuthProviders.GoogleConfig.RedirectURL = hostname + "/oauth_callback/google"
|
||||||
url := oauth.OAuthProviders.GoogleConfig.AuthCodeURL(oauthStateString)
|
url := oauth.OAuthProviders.GoogleConfig.AuthCodeURL(oauthStateString)
|
||||||
@@ -64,7 +82,7 @@ func OAuthLoginHandler() gin.HandlerFunc {
|
|||||||
isProviderConfigured = false
|
isProviderConfigured = false
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
sessionstore.SetSocailLoginState(oauthStateString, constants.SignupMethodGithub)
|
sessionstore.SetState(oauthStateString, constants.SignupMethodGithub)
|
||||||
oauth.OAuthProviders.GithubConfig.RedirectURL = hostname + "/oauth_callback/github"
|
oauth.OAuthProviders.GithubConfig.RedirectURL = hostname + "/oauth_callback/github"
|
||||||
url := oauth.OAuthProviders.GithubConfig.AuthCodeURL(oauthStateString)
|
url := oauth.OAuthProviders.GithubConfig.AuthCodeURL(oauthStateString)
|
||||||
c.Redirect(http.StatusTemporaryRedirect, url)
|
c.Redirect(http.StatusTemporaryRedirect, url)
|
||||||
@@ -73,7 +91,7 @@ func OAuthLoginHandler() gin.HandlerFunc {
|
|||||||
isProviderConfigured = false
|
isProviderConfigured = false
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
sessionstore.SetSocailLoginState(oauthStateString, constants.SignupMethodFacebook)
|
sessionstore.SetState(oauthStateString, constants.SignupMethodFacebook)
|
||||||
oauth.OAuthProviders.FacebookConfig.RedirectURL = hostname + "/oauth_callback/facebook"
|
oauth.OAuthProviders.FacebookConfig.RedirectURL = hostname + "/oauth_callback/facebook"
|
||||||
url := oauth.OAuthProviders.FacebookConfig.AuthCodeURL(oauthStateString)
|
url := oauth.OAuthProviders.FacebookConfig.AuthCodeURL(oauthStateString)
|
||||||
c.Redirect(http.StatusTemporaryRedirect, url)
|
c.Redirect(http.StatusTemporaryRedirect, url)
|
||||||
|
30
server/handlers/openid_config.go
Normal file
30
server/handlers/openid_config.go
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
package handlers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
|
||||||
|
"github.com/authorizerdev/authorizer/server/constants"
|
||||||
|
"github.com/authorizerdev/authorizer/server/envstore"
|
||||||
|
"github.com/authorizerdev/authorizer/server/utils"
|
||||||
|
)
|
||||||
|
|
||||||
|
// OpenIDConfigurationHandler handler for open-id configurations
|
||||||
|
func OpenIDConfigurationHandler() gin.HandlerFunc {
|
||||||
|
return func(c *gin.Context) {
|
||||||
|
issuer := utils.GetHost(c)
|
||||||
|
jwtType := envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyJwtType)
|
||||||
|
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"issuer": issuer,
|
||||||
|
"authorization_endpoint": issuer + "/authorize",
|
||||||
|
"token_endpoint": issuer + "/token",
|
||||||
|
"userinfo_endpoint": issuer + "/userinfo",
|
||||||
|
"jwks_uri": issuer + "/.well-known/jwks.json",
|
||||||
|
"response_types_supported": []string{"code", "token", "id_token", "code token", "code id_token", "token id_token", "code token id_token"},
|
||||||
|
"scopes_supported": []string{"openid", "email", "profile", "email_verified", "given_name", "family_name", "nick_name", "picture"},
|
||||||
|
"response_modes_supported": []string{"query", "fragment", "form_post"},
|
||||||
|
"id_token_signing_alg_values_supported": []string{jwtType},
|
||||||
|
"claims_supported": []string{"aud", "exp", "iss", "iat", "sub", "given_name", "family_name", "middle_name", "nickname", "preferred_username", "picture", "email", "email_verified", "roles", "gender", "birthdate", "phone_number", "phone_number_verified"},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
50
server/handlers/revoke.go
Normal file
50
server/handlers/revoke.go
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
package handlers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/authorizerdev/authorizer/server/constants"
|
||||||
|
"github.com/authorizerdev/authorizer/server/envstore"
|
||||||
|
"github.com/authorizerdev/authorizer/server/sessionstore"
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Revoke handler to revoke refresh token
|
||||||
|
func RevokeHandler() gin.HandlerFunc {
|
||||||
|
return func(gc *gin.Context) {
|
||||||
|
var reqBody map[string]string
|
||||||
|
if err := gc.BindJSON(&reqBody); err != nil {
|
||||||
|
gc.JSON(http.StatusBadRequest, gin.H{
|
||||||
|
"error": "error_binding_json",
|
||||||
|
"error_description": err.Error(),
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// get fingerprint hash
|
||||||
|
refreshToken := strings.TrimSpace(reqBody["refresh_token"])
|
||||||
|
clientID := strings.TrimSpace(reqBody["client_id"])
|
||||||
|
|
||||||
|
if clientID == "" {
|
||||||
|
gc.JSON(http.StatusBadRequest, gin.H{
|
||||||
|
"error": "client_id_required",
|
||||||
|
"error_description": "The client id is required",
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if clientID != envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyClientID) {
|
||||||
|
gc.JSON(http.StatusBadRequest, gin.H{
|
||||||
|
"error": "invalid_client_id",
|
||||||
|
"error_description": "The client id is invalid",
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
sessionstore.RemoveState(refreshToken)
|
||||||
|
|
||||||
|
gc.JSON(http.StatusOK, gin.H{
|
||||||
|
"message": "Token revoked successfully",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
198
server/handlers/token.go
Normal file
198
server/handlers/token.go
Normal file
@@ -0,0 +1,198 @@
|
|||||||
|
package handlers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/sha256"
|
||||||
|
"encoding/base64"
|
||||||
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/authorizerdev/authorizer/server/constants"
|
||||||
|
"github.com/authorizerdev/authorizer/server/cookie"
|
||||||
|
"github.com/authorizerdev/authorizer/server/db"
|
||||||
|
"github.com/authorizerdev/authorizer/server/envstore"
|
||||||
|
"github.com/authorizerdev/authorizer/server/sessionstore"
|
||||||
|
"github.com/authorizerdev/authorizer/server/token"
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TokenHandler to handle /oauth/token requests
|
||||||
|
// grant type required
|
||||||
|
func TokenHandler() gin.HandlerFunc {
|
||||||
|
return func(gc *gin.Context) {
|
||||||
|
var reqBody map[string]string
|
||||||
|
if err := gc.BindJSON(&reqBody); err != nil {
|
||||||
|
gc.JSON(http.StatusBadRequest, gin.H{
|
||||||
|
"error": "error_binding_json",
|
||||||
|
"error_description": err.Error(),
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
codeVerifier := strings.TrimSpace(reqBody["code_verifier"])
|
||||||
|
code := strings.TrimSpace(reqBody["code"])
|
||||||
|
clientID := strings.TrimSpace(reqBody["client_id"])
|
||||||
|
grantType := strings.TrimSpace(reqBody["grant_type"])
|
||||||
|
refreshToken := strings.TrimSpace(reqBody["refresh_token"])
|
||||||
|
|
||||||
|
if grantType == "" {
|
||||||
|
grantType = "authorization_code"
|
||||||
|
}
|
||||||
|
|
||||||
|
isRefreshTokenGrant := grantType == "refresh_token"
|
||||||
|
isAuthorizationCodeGrant := grantType == "authorization_code"
|
||||||
|
|
||||||
|
if !isRefreshTokenGrant && !isAuthorizationCodeGrant {
|
||||||
|
gc.JSON(http.StatusBadRequest, gin.H{
|
||||||
|
"error": "invalid_grant_type",
|
||||||
|
"error_description": "grant_type is invalid",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
if clientID == "" {
|
||||||
|
gc.JSON(http.StatusBadRequest, gin.H{
|
||||||
|
"error": "client_id_required",
|
||||||
|
"error_description": "The client id is required",
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if clientID != envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyClientID) {
|
||||||
|
gc.JSON(http.StatusBadRequest, gin.H{
|
||||||
|
"error": "invalid_client_id",
|
||||||
|
"error_description": "The client id is invalid",
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var userID string
|
||||||
|
var roles, scope []string
|
||||||
|
if isAuthorizationCodeGrant {
|
||||||
|
|
||||||
|
if codeVerifier == "" {
|
||||||
|
gc.JSON(http.StatusBadRequest, gin.H{
|
||||||
|
"error": "invalid_code_verifier",
|
||||||
|
"error_description": "The code verifier is required",
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if code == "" {
|
||||||
|
gc.JSON(http.StatusBadRequest, gin.H{
|
||||||
|
"error": "invalid_code",
|
||||||
|
"error_description": "The code is required",
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
hash := sha256.New()
|
||||||
|
hash.Write([]byte(codeVerifier))
|
||||||
|
encryptedCode := strings.ReplaceAll(base64.URLEncoding.EncodeToString(hash.Sum(nil)), "+", "-")
|
||||||
|
encryptedCode = strings.ReplaceAll(encryptedCode, "/", "_")
|
||||||
|
encryptedCode = strings.ReplaceAll(encryptedCode, "=", "")
|
||||||
|
sessionData := sessionstore.GetState(encryptedCode)
|
||||||
|
if sessionData == "" {
|
||||||
|
gc.JSON(http.StatusBadRequest, gin.H{
|
||||||
|
"error": "invalid_code_verifier",
|
||||||
|
"error_description": "The code verifier is invalid",
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// split session data
|
||||||
|
// it contains code@sessiontoken
|
||||||
|
sessionDataSplit := strings.Split(sessionData, "@")
|
||||||
|
|
||||||
|
if sessionDataSplit[0] != code {
|
||||||
|
gc.JSON(http.StatusBadRequest, gin.H{
|
||||||
|
"error": "invalid_code_verifier",
|
||||||
|
"error_description": "The code verifier is invalid",
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// validate session
|
||||||
|
claims, err := token.ValidateBrowserSession(gc, sessionDataSplit[1])
|
||||||
|
if err != nil {
|
||||||
|
gc.JSON(http.StatusUnauthorized, gin.H{
|
||||||
|
"error": "unauthorized",
|
||||||
|
"error_description": "Invalid session data",
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// rollover the session for security
|
||||||
|
sessionstore.RemoveState(sessionDataSplit[1])
|
||||||
|
userID = claims.Subject
|
||||||
|
roles = claims.Roles
|
||||||
|
scope = claims.Scope
|
||||||
|
} else {
|
||||||
|
// validate refresh token
|
||||||
|
if refreshToken == "" {
|
||||||
|
gc.JSON(http.StatusBadRequest, gin.H{
|
||||||
|
"error": "invalid_refresh_token",
|
||||||
|
"error_description": "The refresh token is invalid",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
claims, err := token.ValidateRefreshToken(gc, refreshToken)
|
||||||
|
if err != nil {
|
||||||
|
gc.JSON(http.StatusUnauthorized, gin.H{
|
||||||
|
"error": "unauthorized",
|
||||||
|
"error_description": err.Error(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
userID = claims["sub"].(string)
|
||||||
|
rolesInterface := claims["roles"].([]interface{})
|
||||||
|
scopeInterface := claims["scope"].([]interface{})
|
||||||
|
for _, v := range rolesInterface {
|
||||||
|
roles = append(roles, v.(string))
|
||||||
|
}
|
||||||
|
for _, v := range scopeInterface {
|
||||||
|
scope = append(scope, v.(string))
|
||||||
|
}
|
||||||
|
// remove older refresh token and rotate it for security
|
||||||
|
sessionstore.RemoveState(refreshToken)
|
||||||
|
}
|
||||||
|
|
||||||
|
user, err := db.Provider.GetUserByID(userID)
|
||||||
|
if err != nil {
|
||||||
|
gc.JSON(http.StatusUnauthorized, gin.H{
|
||||||
|
"error": "unauthorized",
|
||||||
|
"error_description": "User not found",
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
authToken, err := token.CreateAuthToken(gc, user, roles, scope)
|
||||||
|
if err != nil {
|
||||||
|
gc.JSON(http.StatusUnauthorized, gin.H{
|
||||||
|
"error": "unauthorized",
|
||||||
|
"error_description": "User not found",
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
sessionstore.SetState(authToken.FingerPrintHash, authToken.FingerPrint+"@"+user.ID)
|
||||||
|
sessionstore.SetState(authToken.AccessToken.Token, authToken.FingerPrint+"@"+user.ID)
|
||||||
|
cookie.SetSession(gc, authToken.FingerPrintHash)
|
||||||
|
|
||||||
|
expiresIn := authToken.AccessToken.ExpiresAt - time.Now().Unix()
|
||||||
|
if expiresIn <= 0 {
|
||||||
|
expiresIn = 1
|
||||||
|
}
|
||||||
|
|
||||||
|
res := map[string]interface{}{
|
||||||
|
"access_token": authToken.AccessToken.Token,
|
||||||
|
"id_token": authToken.IDToken.Token,
|
||||||
|
"scope": scope,
|
||||||
|
"roles": roles,
|
||||||
|
"expires_in": expiresIn,
|
||||||
|
}
|
||||||
|
|
||||||
|
if authToken.RefreshToken != nil {
|
||||||
|
res["refresh_token"] = authToken.RefreshToken.Token
|
||||||
|
sessionstore.SetState(authToken.RefreshToken.Token, authToken.FingerPrint+"@"+user.ID)
|
||||||
|
}
|
||||||
|
|
||||||
|
gc.JSON(http.StatusOK, res)
|
||||||
|
}
|
||||||
|
}
|
40
server/handlers/userinfo.go
Normal file
40
server/handlers/userinfo.go
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
package handlers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/authorizerdev/authorizer/server/db"
|
||||||
|
"github.com/authorizerdev/authorizer/server/token"
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
)
|
||||||
|
|
||||||
|
func UserInfoHandler() gin.HandlerFunc {
|
||||||
|
return func(gc *gin.Context) {
|
||||||
|
accessToken, err := token.GetAccessToken(gc)
|
||||||
|
if err != nil {
|
||||||
|
gc.JSON(http.StatusUnauthorized, gin.H{
|
||||||
|
"error": err.Error(),
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
claims, err := token.ValidateAccessToken(gc, accessToken)
|
||||||
|
if err != nil {
|
||||||
|
gc.JSON(http.StatusUnauthorized, gin.H{
|
||||||
|
"error": err.Error(),
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
userID := claims["sub"].(string)
|
||||||
|
user, err := db.Provider.GetUserByID(userID)
|
||||||
|
if err != nil {
|
||||||
|
gc.JSON(http.StatusUnauthorized, gin.H{
|
||||||
|
"error": err.Error(),
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
gc.JSON(http.StatusOK, user.AsAPIUser())
|
||||||
|
}
|
||||||
|
}
|
@@ -2,6 +2,7 @@ package handlers
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@@ -18,7 +19,7 @@ import (
|
|||||||
func VerifyEmailHandler() gin.HandlerFunc {
|
func VerifyEmailHandler() gin.HandlerFunc {
|
||||||
return func(c *gin.Context) {
|
return func(c *gin.Context) {
|
||||||
errorRes := gin.H{
|
errorRes := gin.H{
|
||||||
"message": "invalid token",
|
"error": "invalid_token",
|
||||||
}
|
}
|
||||||
tokenInQuery := c.Query("token")
|
tokenInQuery := c.Query("token")
|
||||||
if tokenInQuery == "" {
|
if tokenInQuery == "" {
|
||||||
@@ -28,22 +29,24 @@ func VerifyEmailHandler() gin.HandlerFunc {
|
|||||||
|
|
||||||
verificationRequest, err := db.Provider.GetVerificationRequestByToken(tokenInQuery)
|
verificationRequest, err := db.Provider.GetVerificationRequestByToken(tokenInQuery)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
errorRes["error_description"] = err.Error()
|
||||||
c.JSON(400, errorRes)
|
c.JSON(400, errorRes)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// verify if token exists in db
|
// verify if token exists in db
|
||||||
claim, err := token.ParseJWTToken(tokenInQuery)
|
hostname := utils.GetHost(c)
|
||||||
|
claim, err := token.ParseJWTToken(tokenInQuery, hostname, verificationRequest.Nonce, verificationRequest.Email)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
errorRes["error_description"] = err.Error()
|
||||||
c.JSON(400, errorRes)
|
c.JSON(400, errorRes)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
user, err := db.Provider.GetUserByEmail(claim["email"].(string))
|
user, err := db.Provider.GetUserByEmail(claim["sub"].(string))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.JSON(400, gin.H{
|
errorRes["error_description"] = err.Error()
|
||||||
"message": err.Error(),
|
c.JSON(400, errorRes)
|
||||||
})
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -56,18 +59,58 @@ func VerifyEmailHandler() gin.HandlerFunc {
|
|||||||
// delete from verification table
|
// delete from verification table
|
||||||
db.Provider.DeleteVerificationRequest(verificationRequest)
|
db.Provider.DeleteVerificationRequest(verificationRequest)
|
||||||
|
|
||||||
roles := strings.Split(user.Roles, ",")
|
state := strings.TrimSpace(c.Query("state"))
|
||||||
authToken, err := token.CreateAuthToken(user, roles)
|
redirectURL := strings.TrimSpace(c.Query("redirect_uri"))
|
||||||
|
rolesString := strings.TrimSpace(c.Query("roles"))
|
||||||
|
var roles []string
|
||||||
|
if rolesString == "" {
|
||||||
|
roles = strings.Split(user.Roles, ",")
|
||||||
|
} else {
|
||||||
|
roles = strings.Split(rolesString, ",")
|
||||||
|
}
|
||||||
|
|
||||||
|
scopeString := strings.TrimSpace(c.Query("scope"))
|
||||||
|
var scope []string
|
||||||
|
if scopeString == "" {
|
||||||
|
scope = []string{"openid", "email", "profile"}
|
||||||
|
} else {
|
||||||
|
scope = strings.Split(scopeString, " ")
|
||||||
|
}
|
||||||
|
authToken, err := token.CreateAuthToken(c, user, roles, scope)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.JSON(400, gin.H{
|
errorRes["error_description"] = err.Error()
|
||||||
"message": err.Error(),
|
c.JSON(500, errorRes)
|
||||||
})
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
sessionstore.SetUserSession(user.ID, authToken.FingerPrint, authToken.RefreshToken.Token)
|
|
||||||
cookie.SetCookie(c, authToken.AccessToken.Token, authToken.RefreshToken.Token, authToken.FingerPrintHash)
|
|
||||||
utils.SaveSessionInDB(user.ID, c)
|
|
||||||
|
|
||||||
c.Redirect(http.StatusTemporaryRedirect, claim["redirect_url"].(string))
|
expiresIn := authToken.AccessToken.ExpiresAt - time.Now().Unix()
|
||||||
|
if expiresIn <= 0 {
|
||||||
|
expiresIn = 1
|
||||||
|
}
|
||||||
|
|
||||||
|
params := "access_token=" + authToken.AccessToken.Token + "&token_type=bearer&expires_in=" + strconv.FormatInt(expiresIn, 10) + "&state=" + state + "&id_token=" + authToken.IDToken.Token
|
||||||
|
|
||||||
|
cookie.SetSession(c, authToken.FingerPrintHash)
|
||||||
|
sessionstore.SetState(authToken.FingerPrintHash, authToken.FingerPrint+"@"+user.ID)
|
||||||
|
sessionstore.SetState(authToken.AccessToken.Token, authToken.FingerPrint+"@"+user.ID)
|
||||||
|
|
||||||
|
if authToken.RefreshToken != nil {
|
||||||
|
params = params + `&refresh_token=${refresh_token}`
|
||||||
|
sessionstore.SetState(authToken.RefreshToken.Token, authToken.FingerPrint+"@"+user.ID)
|
||||||
|
}
|
||||||
|
|
||||||
|
if redirectURL == "" {
|
||||||
|
redirectURL = claim["redirect_uri"].(string)
|
||||||
|
}
|
||||||
|
|
||||||
|
if strings.Contains(redirectURL, "?") {
|
||||||
|
redirectURL = redirectURL + "&" + params
|
||||||
|
} else {
|
||||||
|
redirectURL = redirectURL + "?" + params
|
||||||
|
}
|
||||||
|
|
||||||
|
go utils.SaveSessionInDB(c, user.ID)
|
||||||
|
|
||||||
|
c.Redirect(http.StatusTemporaryRedirect, redirectURL)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -2,6 +2,7 @@ package main
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"flag"
|
"flag"
|
||||||
|
"log"
|
||||||
|
|
||||||
"github.com/authorizerdev/authorizer/server/constants"
|
"github.com/authorizerdev/authorizer/server/constants"
|
||||||
"github.com/authorizerdev/authorizer/server/db"
|
"github.com/authorizerdev/authorizer/server/db"
|
||||||
@@ -20,16 +21,46 @@ func main() {
|
|||||||
envstore.ARG_ENV_FILE = flag.String("env_file", "", "Env file path")
|
envstore.ARG_ENV_FILE = flag.String("env_file", "", "Env file path")
|
||||||
flag.Parse()
|
flag.Parse()
|
||||||
|
|
||||||
envstore.EnvInMemoryStoreObj.UpdateEnvVariable(constants.StringStoreIdentifier, constants.EnvKeyVersion, VERSION)
|
log.Println("=> version:", VERSION)
|
||||||
|
constants.VERSION = VERSION
|
||||||
|
|
||||||
env.InitEnv()
|
// initialize required envs (mainly db & env file path)
|
||||||
db.InitDB()
|
err := env.InitRequiredEnv()
|
||||||
env.PersistEnv()
|
if err != nil {
|
||||||
|
log.Fatal("Error while initializing required envs:", err)
|
||||||
|
}
|
||||||
|
|
||||||
sessionstore.InitSession()
|
// initialize db provider
|
||||||
oauth.InitOAuth()
|
err = db.InitDB()
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalln("Error while initializing db:", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// initialize all envs
|
||||||
|
// (get if present from db else construct from os env + defaults)
|
||||||
|
err = env.InitAllEnv()
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalln("Error while initializing env: ", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// persist all envs
|
||||||
|
err = env.PersistEnv()
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalln("Error while persisting env:", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// initialize session store (redis or in-memory based on env)
|
||||||
|
err = sessionstore.InitSession()
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalln("Error while initializing session store:", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// initialize oauth providers based on env
|
||||||
|
err = oauth.InitOAuth()
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalln("Error while initializing oauth:", err)
|
||||||
|
}
|
||||||
|
|
||||||
router := routes.InitRouter()
|
router := routes.InitRouter()
|
||||||
|
router.Run(":" + envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyPort))
|
||||||
router.Run(":" + envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyPort))
|
|
||||||
}
|
}
|
||||||
|
@@ -2,7 +2,6 @@ package oauth
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"log"
|
|
||||||
|
|
||||||
"github.com/authorizerdev/authorizer/server/constants"
|
"github.com/authorizerdev/authorizer/server/constants"
|
||||||
"github.com/authorizerdev/authorizer/server/envstore"
|
"github.com/authorizerdev/authorizer/server/envstore"
|
||||||
@@ -32,37 +31,39 @@ var (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// InitOAuth initializes the OAuth providers based on EnvData
|
// InitOAuth initializes the OAuth providers based on EnvData
|
||||||
func InitOAuth() {
|
func InitOAuth() error {
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
if envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyGoogleClientID) != "" && envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyGoogleClientSecret) != "" {
|
if envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyGoogleClientID) != "" && envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyGoogleClientSecret) != "" {
|
||||||
p, err := oidc.NewProvider(ctx, "https://accounts.google.com")
|
p, err := oidc.NewProvider(ctx, "https://accounts.google.com")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalln("error creating oidc provider for google:", err)
|
return err
|
||||||
}
|
}
|
||||||
OIDCProviders.GoogleOIDC = p
|
OIDCProviders.GoogleOIDC = p
|
||||||
OAuthProviders.GoogleConfig = &oauth2.Config{
|
OAuthProviders.GoogleConfig = &oauth2.Config{
|
||||||
ClientID: envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyGoogleClientID),
|
ClientID: envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyGoogleClientID),
|
||||||
ClientSecret: envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyGoogleClientSecret),
|
ClientSecret: envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyGoogleClientSecret),
|
||||||
RedirectURL: "/oauth_callback/google",
|
RedirectURL: "/oauth_callback/google",
|
||||||
Endpoint: OIDCProviders.GoogleOIDC.Endpoint(),
|
Endpoint: OIDCProviders.GoogleOIDC.Endpoint(),
|
||||||
Scopes: []string{oidc.ScopeOpenID, "profile", "email"},
|
Scopes: []string{oidc.ScopeOpenID, "profile", "email"},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyGithubClientID) != "" && envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyGithubClientSecret) != "" {
|
if envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyGithubClientID) != "" && envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyGithubClientSecret) != "" {
|
||||||
OAuthProviders.GithubConfig = &oauth2.Config{
|
OAuthProviders.GithubConfig = &oauth2.Config{
|
||||||
ClientID: envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyGithubClientID),
|
ClientID: envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyGithubClientID),
|
||||||
ClientSecret: envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyGithubClientSecret),
|
ClientSecret: envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyGithubClientSecret),
|
||||||
RedirectURL: "/oauth_callback/github",
|
RedirectURL: "/oauth_callback/github",
|
||||||
Endpoint: githubOAuth2.Endpoint,
|
Endpoint: githubOAuth2.Endpoint,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyFacebookClientID) != "" && envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyGoogleClientID) != "" {
|
if envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyFacebookClientID) != "" && envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyGoogleClientID) != "" {
|
||||||
OAuthProviders.FacebookConfig = &oauth2.Config{
|
OAuthProviders.FacebookConfig = &oauth2.Config{
|
||||||
ClientID: envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyFacebookClientID),
|
ClientID: envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyFacebookClientID),
|
||||||
ClientSecret: envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyFacebookClientSecret),
|
ClientSecret: envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyFacebookClientSecret),
|
||||||
RedirectURL: "/oauth_callback/facebook",
|
RedirectURL: "/oauth_callback/facebook",
|
||||||
Endpoint: facebookOAuth2.Endpoint,
|
Endpoint: facebookOAuth2.Endpoint,
|
||||||
Scopes: []string{"public_profile", "email"},
|
Scopes: []string{"public_profile", "email"},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
@@ -6,6 +6,7 @@ import (
|
|||||||
|
|
||||||
"github.com/authorizerdev/authorizer/server/constants"
|
"github.com/authorizerdev/authorizer/server/constants"
|
||||||
"github.com/authorizerdev/authorizer/server/cookie"
|
"github.com/authorizerdev/authorizer/server/cookie"
|
||||||
|
"github.com/authorizerdev/authorizer/server/crypto"
|
||||||
"github.com/authorizerdev/authorizer/server/envstore"
|
"github.com/authorizerdev/authorizer/server/envstore"
|
||||||
"github.com/authorizerdev/authorizer/server/graph/model"
|
"github.com/authorizerdev/authorizer/server/graph/model"
|
||||||
"github.com/authorizerdev/authorizer/server/utils"
|
"github.com/authorizerdev/authorizer/server/utils"
|
||||||
@@ -20,12 +21,12 @@ func AdminLoginResolver(ctx context.Context, params model.AdminLoginInput) (*mod
|
|||||||
return res, err
|
return res, err
|
||||||
}
|
}
|
||||||
|
|
||||||
adminSecret := envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyAdminSecret)
|
adminSecret := envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyAdminSecret)
|
||||||
if params.AdminSecret != adminSecret {
|
if params.AdminSecret != adminSecret {
|
||||||
return res, fmt.Errorf(`invalid admin secret`)
|
return res, fmt.Errorf(`invalid admin secret`)
|
||||||
}
|
}
|
||||||
|
|
||||||
hashedKey, err := utils.EncryptPassword(adminSecret)
|
hashedKey, err := crypto.EncryptPassword(adminSecret)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return res, err
|
return res, err
|
||||||
}
|
}
|
||||||
|
@@ -6,6 +6,7 @@ import (
|
|||||||
|
|
||||||
"github.com/authorizerdev/authorizer/server/constants"
|
"github.com/authorizerdev/authorizer/server/constants"
|
||||||
"github.com/authorizerdev/authorizer/server/cookie"
|
"github.com/authorizerdev/authorizer/server/cookie"
|
||||||
|
"github.com/authorizerdev/authorizer/server/crypto"
|
||||||
"github.com/authorizerdev/authorizer/server/envstore"
|
"github.com/authorizerdev/authorizer/server/envstore"
|
||||||
"github.com/authorizerdev/authorizer/server/graph/model"
|
"github.com/authorizerdev/authorizer/server/graph/model"
|
||||||
"github.com/authorizerdev/authorizer/server/token"
|
"github.com/authorizerdev/authorizer/server/token"
|
||||||
@@ -25,7 +26,7 @@ func AdminSessionResolver(ctx context.Context) (*model.Response, error) {
|
|||||||
return res, fmt.Errorf("unauthorized")
|
return res, fmt.Errorf("unauthorized")
|
||||||
}
|
}
|
||||||
|
|
||||||
hashedKey, err := utils.EncryptPassword(envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyAdminSecret))
|
hashedKey, err := crypto.EncryptPassword(envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyAdminSecret))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return res, err
|
return res, err
|
||||||
}
|
}
|
||||||
|
@@ -8,6 +8,7 @@ import (
|
|||||||
|
|
||||||
"github.com/authorizerdev/authorizer/server/constants"
|
"github.com/authorizerdev/authorizer/server/constants"
|
||||||
"github.com/authorizerdev/authorizer/server/cookie"
|
"github.com/authorizerdev/authorizer/server/cookie"
|
||||||
|
"github.com/authorizerdev/authorizer/server/crypto"
|
||||||
"github.com/authorizerdev/authorizer/server/db"
|
"github.com/authorizerdev/authorizer/server/db"
|
||||||
"github.com/authorizerdev/authorizer/server/envstore"
|
"github.com/authorizerdev/authorizer/server/envstore"
|
||||||
"github.com/authorizerdev/authorizer/server/graph/model"
|
"github.com/authorizerdev/authorizer/server/graph/model"
|
||||||
@@ -33,18 +34,18 @@ func AdminSignupResolver(ctx context.Context, params model.AdminSignupInput) (*m
|
|||||||
return res, err
|
return res, err
|
||||||
}
|
}
|
||||||
|
|
||||||
adminSecret := envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyAdminSecret)
|
adminSecret := envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyAdminSecret)
|
||||||
|
|
||||||
if adminSecret != "" {
|
if adminSecret != "" {
|
||||||
err = fmt.Errorf("admin sign up already completed")
|
err = fmt.Errorf("admin sign up already completed")
|
||||||
return res, err
|
return res, err
|
||||||
}
|
}
|
||||||
|
|
||||||
envstore.EnvInMemoryStoreObj.UpdateEnvVariable(constants.StringStoreIdentifier, constants.EnvKeyAdminSecret, params.AdminSecret)
|
envstore.EnvStoreObj.UpdateEnvVariable(constants.StringStoreIdentifier, constants.EnvKeyAdminSecret, params.AdminSecret)
|
||||||
// consvert EnvData to JSON
|
// consvert EnvData to JSON
|
||||||
var storeData envstore.Store
|
var storeData envstore.Store
|
||||||
|
|
||||||
jsonBytes, err := json.Marshal(envstore.EnvInMemoryStoreObj.GetEnvStoreClone())
|
jsonBytes, err := json.Marshal(envstore.EnvStoreObj.GetEnvStoreClone())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return res, err
|
return res, err
|
||||||
}
|
}
|
||||||
@@ -58,7 +59,7 @@ func AdminSignupResolver(ctx context.Context, params model.AdminSignupInput) (*m
|
|||||||
return res, err
|
return res, err
|
||||||
}
|
}
|
||||||
|
|
||||||
envData, err := utils.EncryptEnvData(storeData)
|
envData, err := crypto.EncryptEnvData(storeData)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return res, err
|
return res, err
|
||||||
}
|
}
|
||||||
@@ -68,7 +69,7 @@ func AdminSignupResolver(ctx context.Context, params model.AdminSignupInput) (*m
|
|||||||
return res, err
|
return res, err
|
||||||
}
|
}
|
||||||
|
|
||||||
hashedKey, err := utils.EncryptPassword(params.AdminSecret)
|
hashedKey, err := crypto.EncryptPassword(params.AdminSecret)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return res, err
|
return res, err
|
||||||
}
|
}
|
||||||
|
@@ -29,7 +29,7 @@ func DeleteUserResolver(ctx context.Context, params model.DeleteUserInput) (*mod
|
|||||||
return res, err
|
return res, err
|
||||||
}
|
}
|
||||||
|
|
||||||
sessionstore.DeleteAllUserSession(fmt.Sprintf("%x", user.ID))
|
go sessionstore.DeleteAllUserSession(fmt.Sprintf("%x", user.ID))
|
||||||
|
|
||||||
err = db.Provider.DeleteUser(user)
|
err = db.Provider.DeleteUser(user)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
44
server/resolvers/enable_access.go
Normal file
44
server/resolvers/enable_access.go
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
package resolvers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
|
||||||
|
"github.com/authorizerdev/authorizer/server/db"
|
||||||
|
"github.com/authorizerdev/authorizer/server/graph/model"
|
||||||
|
"github.com/authorizerdev/authorizer/server/token"
|
||||||
|
"github.com/authorizerdev/authorizer/server/utils"
|
||||||
|
)
|
||||||
|
|
||||||
|
// EnableAccessResolver is a resolver for enabling user access
|
||||||
|
func EnableAccessResolver(ctx context.Context, params model.UpdateAccessInput) (*model.Response, error) {
|
||||||
|
gc, err := utils.GinContextFromContext(ctx)
|
||||||
|
var res *model.Response
|
||||||
|
if err != nil {
|
||||||
|
return res, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if !token.IsSuperAdmin(gc) {
|
||||||
|
return res, fmt.Errorf("unauthorized")
|
||||||
|
}
|
||||||
|
|
||||||
|
user, err := db.Provider.GetUserByID(params.UserID)
|
||||||
|
if err != nil {
|
||||||
|
return res, err
|
||||||
|
}
|
||||||
|
|
||||||
|
user.RevokedTimestamp = nil
|
||||||
|
|
||||||
|
user, err = db.Provider.UpdateUser(user)
|
||||||
|
if err != nil {
|
||||||
|
log.Println("error updating user:", err)
|
||||||
|
return res, err
|
||||||
|
}
|
||||||
|
|
||||||
|
res = &model.Response{
|
||||||
|
Message: `user access enabled successfully`,
|
||||||
|
}
|
||||||
|
|
||||||
|
return res, nil
|
||||||
|
}
|
@@ -26,8 +26,11 @@ func EnvResolver(ctx context.Context) (*model.Env, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// get clone of store
|
// get clone of store
|
||||||
store := envstore.EnvInMemoryStoreObj.GetEnvStoreClone()
|
store := envstore.EnvStoreObj.GetEnvStoreClone()
|
||||||
|
accessTokenExpiryTime := store.StringEnv[constants.EnvKeyAccessTokenExpiryTime]
|
||||||
adminSecret := store.StringEnv[constants.EnvKeyAdminSecret]
|
adminSecret := store.StringEnv[constants.EnvKeyAdminSecret]
|
||||||
|
clientID := store.StringEnv[constants.EnvKeyClientID]
|
||||||
|
clientSecret := store.StringEnv[constants.EnvKeyClientSecret]
|
||||||
databaseURL := store.StringEnv[constants.EnvKeyDatabaseURL]
|
databaseURL := store.StringEnv[constants.EnvKeyDatabaseURL]
|
||||||
databaseName := store.StringEnv[constants.EnvKeyDatabaseName]
|
databaseName := store.StringEnv[constants.EnvKeyDatabaseName]
|
||||||
databaseType := store.StringEnv[constants.EnvKeyDatabaseType]
|
databaseType := store.StringEnv[constants.EnvKeyDatabaseType]
|
||||||
@@ -51,6 +54,7 @@ func EnvResolver(ctx context.Context) (*model.Env, error) {
|
|||||||
disableBasicAuthentication := store.BoolEnv[constants.EnvKeyDisableBasicAuthentication]
|
disableBasicAuthentication := store.BoolEnv[constants.EnvKeyDisableBasicAuthentication]
|
||||||
disableMagicLinkLogin := store.BoolEnv[constants.EnvKeyDisableMagicLinkLogin]
|
disableMagicLinkLogin := store.BoolEnv[constants.EnvKeyDisableMagicLinkLogin]
|
||||||
disableLoginPage := store.BoolEnv[constants.EnvKeyDisableLoginPage]
|
disableLoginPage := store.BoolEnv[constants.EnvKeyDisableLoginPage]
|
||||||
|
disableSignUp := store.BoolEnv[constants.EnvKeyDisableSignUp]
|
||||||
roles := store.SliceEnv[constants.EnvKeyRoles]
|
roles := store.SliceEnv[constants.EnvKeyRoles]
|
||||||
defaultRoles := store.SliceEnv[constants.EnvKeyDefaultRoles]
|
defaultRoles := store.SliceEnv[constants.EnvKeyDefaultRoles]
|
||||||
protectedRoles := store.SliceEnv[constants.EnvKeyProtectedRoles]
|
protectedRoles := store.SliceEnv[constants.EnvKeyProtectedRoles]
|
||||||
@@ -63,11 +67,18 @@ func EnvResolver(ctx context.Context) (*model.Env, error) {
|
|||||||
organizationName := store.StringEnv[constants.EnvKeyOrganizationName]
|
organizationName := store.StringEnv[constants.EnvKeyOrganizationName]
|
||||||
organizationLogo := store.StringEnv[constants.EnvKeyOrganizationLogo]
|
organizationLogo := store.StringEnv[constants.EnvKeyOrganizationLogo]
|
||||||
|
|
||||||
|
if accessTokenExpiryTime == "" {
|
||||||
|
accessTokenExpiryTime = "30m"
|
||||||
|
}
|
||||||
|
|
||||||
res = &model.Env{
|
res = &model.Env{
|
||||||
|
AccessTokenExpiryTime: &accessTokenExpiryTime,
|
||||||
AdminSecret: &adminSecret,
|
AdminSecret: &adminSecret,
|
||||||
DatabaseName: &databaseName,
|
DatabaseName: databaseName,
|
||||||
DatabaseURL: &databaseURL,
|
DatabaseURL: databaseURL,
|
||||||
DatabaseType: &databaseType,
|
DatabaseType: databaseType,
|
||||||
|
ClientID: clientID,
|
||||||
|
ClientSecret: clientSecret,
|
||||||
CustomAccessTokenScript: &customAccessTokenScript,
|
CustomAccessTokenScript: &customAccessTokenScript,
|
||||||
SMTPHost: &smtpHost,
|
SMTPHost: &smtpHost,
|
||||||
SMTPPort: &smtpPort,
|
SMTPPort: &smtpPort,
|
||||||
@@ -88,6 +99,7 @@ func EnvResolver(ctx context.Context) (*model.Env, error) {
|
|||||||
DisableBasicAuthentication: &disableBasicAuthentication,
|
DisableBasicAuthentication: &disableBasicAuthentication,
|
||||||
DisableMagicLinkLogin: &disableMagicLinkLogin,
|
DisableMagicLinkLogin: &disableMagicLinkLogin,
|
||||||
DisableLoginPage: &disableLoginPage,
|
DisableLoginPage: &disableLoginPage,
|
||||||
|
DisableSignUp: &disableSignUp,
|
||||||
Roles: roles,
|
Roles: roles,
|
||||||
ProtectedRoles: protectedRoles,
|
ProtectedRoles: protectedRoles,
|
||||||
DefaultRoles: defaultRoles,
|
DefaultRoles: defaultRoles,
|
||||||
|
@@ -24,7 +24,7 @@ func ForgotPasswordResolver(ctx context.Context, params model.ForgotPasswordInpu
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return res, err
|
return res, err
|
||||||
}
|
}
|
||||||
if envstore.EnvInMemoryStoreObj.GetBoolStoreEnvVariable(constants.EnvKeyDisableBasicAuthentication) {
|
if envstore.EnvStoreObj.GetBoolStoreEnvVariable(constants.EnvKeyDisableBasicAuthentication) {
|
||||||
return res, fmt.Errorf(`basic authentication is disabled for this instance`)
|
return res, fmt.Errorf(`basic authentication is disabled for this instance`)
|
||||||
}
|
}
|
||||||
params.Email = strings.ToLower(params.Email)
|
params.Email = strings.ToLower(params.Email)
|
||||||
@@ -39,7 +39,16 @@ func ForgotPasswordResolver(ctx context.Context, params model.ForgotPasswordInpu
|
|||||||
}
|
}
|
||||||
|
|
||||||
hostname := utils.GetHost(gc)
|
hostname := utils.GetHost(gc)
|
||||||
verificationToken, err := token.CreateVerificationToken(params.Email, constants.VerificationTypeForgotPassword, hostname)
|
_, nonceHash, err := utils.GenerateNonce()
|
||||||
|
if err != nil {
|
||||||
|
return res, err
|
||||||
|
}
|
||||||
|
redirectURL := utils.GetAppURL(gc) + "/reset-password"
|
||||||
|
if params.RedirectURI != nil {
|
||||||
|
redirectURL = *params.RedirectURI
|
||||||
|
}
|
||||||
|
|
||||||
|
verificationToken, err := token.CreateVerificationToken(params.Email, constants.VerificationTypeForgotPassword, hostname, nonceHash, redirectURL)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Println(`error generating token`, err)
|
log.Println(`error generating token`, err)
|
||||||
}
|
}
|
||||||
@@ -48,12 +57,12 @@ func ForgotPasswordResolver(ctx context.Context, params model.ForgotPasswordInpu
|
|||||||
Identifier: constants.VerificationTypeForgotPassword,
|
Identifier: constants.VerificationTypeForgotPassword,
|
||||||
ExpiresAt: time.Now().Add(time.Minute * 30).Unix(),
|
ExpiresAt: time.Now().Add(time.Minute * 30).Unix(),
|
||||||
Email: params.Email,
|
Email: params.Email,
|
||||||
|
Nonce: nonceHash,
|
||||||
|
RedirectURI: redirectURL,
|
||||||
})
|
})
|
||||||
|
|
||||||
// exec it as go routin so that we can reduce the api latency
|
// exec it as go routin so that we can reduce the api latency
|
||||||
go func() {
|
go email.SendForgotPasswordMail(params.Email, verificationToken, hostname)
|
||||||
email.SendForgotPasswordMail(params.Email, verificationToken, hostname)
|
|
||||||
}()
|
|
||||||
|
|
||||||
res = &model.Response{
|
res = &model.Response{
|
||||||
Message: `Please check your inbox! We have sent a password reset link.`,
|
Message: `Please check your inbox! We have sent a password reset link.`,
|
||||||
|
60
server/resolvers/generate_jwt_keys.go
Normal file
60
server/resolvers/generate_jwt_keys.go
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
package resolvers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/authorizerdev/authorizer/server/constants"
|
||||||
|
"github.com/authorizerdev/authorizer/server/crypto"
|
||||||
|
"github.com/authorizerdev/authorizer/server/envstore"
|
||||||
|
"github.com/authorizerdev/authorizer/server/graph/model"
|
||||||
|
"github.com/authorizerdev/authorizer/server/token"
|
||||||
|
"github.com/authorizerdev/authorizer/server/utils"
|
||||||
|
)
|
||||||
|
|
||||||
|
// GenerateJWTKeysResolver mutation to generate new jwt keys
|
||||||
|
func GenerateJWTKeysResolver(ctx context.Context, params model.GenerateJWTKeysInput) (*model.GenerateJWTKeysResponse, error) {
|
||||||
|
gc, err := utils.GinContextFromContext(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if !token.IsSuperAdmin(gc) {
|
||||||
|
return nil, fmt.Errorf("unauthorized")
|
||||||
|
}
|
||||||
|
|
||||||
|
clientID := envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyClientID)
|
||||||
|
if crypto.IsHMACA(params.Type) {
|
||||||
|
secret, _, err := crypto.NewHMACKey(params.Type, clientID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &model.GenerateJWTKeysResponse{
|
||||||
|
Secret: &secret,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if crypto.IsRSA(params.Type) {
|
||||||
|
_, privateKey, publicKey, _, err := crypto.NewRSAKey(params.Type, clientID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &model.GenerateJWTKeysResponse{
|
||||||
|
PrivateKey: &privateKey,
|
||||||
|
PublicKey: &publicKey,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if crypto.IsECDSA(params.Type) {
|
||||||
|
_, privateKey, publicKey, _, err := crypto.NewECDSAKey(params.Type, clientID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &model.GenerateJWTKeysResponse{
|
||||||
|
PrivateKey: &privateKey,
|
||||||
|
PublicKey: &publicKey,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, fmt.Errorf("invalid algorithm")
|
||||||
|
}
|
135
server/resolvers/invite_members.go
Normal file
135
server/resolvers/invite_members.go
Normal file
@@ -0,0 +1,135 @@
|
|||||||
|
package resolvers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/authorizerdev/authorizer/server/constants"
|
||||||
|
"github.com/authorizerdev/authorizer/server/db"
|
||||||
|
"github.com/authorizerdev/authorizer/server/db/models"
|
||||||
|
emailservice "github.com/authorizerdev/authorizer/server/email"
|
||||||
|
"github.com/authorizerdev/authorizer/server/envstore"
|
||||||
|
"github.com/authorizerdev/authorizer/server/graph/model"
|
||||||
|
"github.com/authorizerdev/authorizer/server/token"
|
||||||
|
"github.com/authorizerdev/authorizer/server/utils"
|
||||||
|
)
|
||||||
|
|
||||||
|
// InviteMembersResolver resolver to invite members
|
||||||
|
func InviteMembersResolver(ctx context.Context, params model.InviteMemberInput) (*model.Response, error) {
|
||||||
|
gc, err := utils.GinContextFromContext(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if !token.IsSuperAdmin(gc) {
|
||||||
|
return nil, errors.New("unauthorized")
|
||||||
|
}
|
||||||
|
|
||||||
|
// this feature is only allowed if email server is configured
|
||||||
|
if envstore.EnvStoreObj.GetBoolStoreEnvVariable(constants.EnvKeyDisableEmailVerification) {
|
||||||
|
return nil, errors.New("email sending is disabled")
|
||||||
|
}
|
||||||
|
|
||||||
|
if envstore.EnvStoreObj.GetBoolStoreEnvVariable(constants.EnvKeyDisableBasicAuthentication) && envstore.EnvStoreObj.GetBoolStoreEnvVariable(constants.EnvKeyDisableMagicLinkLogin) {
|
||||||
|
return nil, errors.New("either basic authentication or magic link login is required")
|
||||||
|
}
|
||||||
|
|
||||||
|
// filter valid emails
|
||||||
|
emails := []string{}
|
||||||
|
for _, email := range params.Emails {
|
||||||
|
if utils.IsValidEmail(email) {
|
||||||
|
emails = append(emails, email)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(emails) == 0 {
|
||||||
|
return nil, errors.New("no valid emails found")
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: optimise to use like query instead of looping through emails and getting user individually
|
||||||
|
// for each emails check if emails exists in db
|
||||||
|
newEmails := []string{}
|
||||||
|
for _, email := range emails {
|
||||||
|
_, err := db.Provider.GetUserByEmail(email)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("%s user not found. inviting user.", email)
|
||||||
|
newEmails = append(newEmails, email)
|
||||||
|
} else {
|
||||||
|
log.Println("%s user already exists. skipping.", email)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(newEmails) == 0 {
|
||||||
|
return nil, errors.New("all emails already exist")
|
||||||
|
}
|
||||||
|
|
||||||
|
// invite new emails
|
||||||
|
for _, email := range newEmails {
|
||||||
|
|
||||||
|
user := models.User{
|
||||||
|
Email: email,
|
||||||
|
Roles: strings.Join(envstore.EnvStoreObj.GetSliceStoreEnvVariable(constants.EnvKeyDefaultRoles), ","),
|
||||||
|
}
|
||||||
|
hostname := utils.GetHost(gc)
|
||||||
|
verifyEmailURL := hostname + "/verify_email"
|
||||||
|
appURL := utils.GetAppURL(gc)
|
||||||
|
|
||||||
|
redirectURL := appURL
|
||||||
|
if params.RedirectURI != nil {
|
||||||
|
redirectURL = *params.RedirectURI
|
||||||
|
}
|
||||||
|
|
||||||
|
_, nonceHash, err := utils.GenerateNonce()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
verificationToken, err := token.CreateVerificationToken(email, constants.VerificationTypeForgotPassword, hostname, nonceHash, redirectURL)
|
||||||
|
if err != nil {
|
||||||
|
log.Println(`error generating token`, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
verificationRequest := models.VerificationRequest{
|
||||||
|
Token: verificationToken,
|
||||||
|
ExpiresAt: time.Now().Add(time.Minute * 30).Unix(),
|
||||||
|
Email: email,
|
||||||
|
Nonce: nonceHash,
|
||||||
|
RedirectURI: redirectURL,
|
||||||
|
}
|
||||||
|
|
||||||
|
// use magic link login if that option is on
|
||||||
|
if !envstore.EnvStoreObj.GetBoolStoreEnvVariable(constants.EnvKeyDisableMagicLinkLogin) {
|
||||||
|
user.SignupMethods = constants.SignupMethodMagicLinkLogin
|
||||||
|
verificationRequest.Identifier = constants.VerificationTypeMagicLinkLogin
|
||||||
|
} else {
|
||||||
|
// use basic authentication if that option is on
|
||||||
|
user.SignupMethods = constants.SignupMethodBasicAuth
|
||||||
|
verificationRequest.Identifier = constants.VerificationTypeForgotPassword
|
||||||
|
|
||||||
|
verifyEmailURL = appURL + "/setup-password"
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
user, err = db.Provider.AddUser(user)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("error inviting user: %s, err: %v", email, err)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = db.Provider.AddVerificationRequest(verificationRequest)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("error inviting user: %s, err: %v", email, err)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
go emailservice.InviteEmail(email, verificationToken, verifyEmailURL, redirectURL)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &model.Response{
|
||||||
|
Message: fmt.Sprintf("%d user(s) invited successfully.", len(newEmails)),
|
||||||
|
}, nil
|
||||||
|
}
|
@@ -1,52 +0,0 @@
|
|||||||
package resolvers
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"github.com/authorizerdev/authorizer/server/constants"
|
|
||||||
"github.com/authorizerdev/authorizer/server/envstore"
|
|
||||||
"github.com/authorizerdev/authorizer/server/graph/model"
|
|
||||||
"github.com/authorizerdev/authorizer/server/token"
|
|
||||||
tokenHelper "github.com/authorizerdev/authorizer/server/token"
|
|
||||||
"github.com/authorizerdev/authorizer/server/utils"
|
|
||||||
)
|
|
||||||
|
|
||||||
// IsValidJwtResolver resolver to return if given jwt is valid
|
|
||||||
func IsValidJwtResolver(ctx context.Context, params *model.IsValidJWTQueryInput) (*model.ValidJWTResponse, error) {
|
|
||||||
gc, err := utils.GinContextFromContext(ctx)
|
|
||||||
token, err := token.GetAccessToken(gc)
|
|
||||||
|
|
||||||
if token == "" || err != nil {
|
|
||||||
if params != nil && *params.Jwt != "" {
|
|
||||||
token = *params.Jwt
|
|
||||||
} else {
|
|
||||||
return nil, errors.New("no jwt provided via cookie / header / params")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
claims, err := tokenHelper.ParseJWTToken(token)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
claimRoleInterface := claims[envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyJwtRoleClaim)].([]interface{})
|
|
||||||
claimRoles := []string{}
|
|
||||||
for _, v := range claimRoleInterface {
|
|
||||||
claimRoles = append(claimRoles, v.(string))
|
|
||||||
}
|
|
||||||
|
|
||||||
if params != nil && params.Roles != nil && len(params.Roles) > 0 {
|
|
||||||
for _, v := range params.Roles {
|
|
||||||
if !utils.StringSliceContains(claimRoles, v) {
|
|
||||||
return nil, fmt.Errorf(`unauthorized`)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return &model.ValidJWTResponse{
|
|
||||||
Valid: true,
|
|
||||||
Message: "Valid JWT",
|
|
||||||
}, nil
|
|
||||||
}
|
|
@@ -5,6 +5,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
"strings"
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/authorizerdev/authorizer/server/constants"
|
"github.com/authorizerdev/authorizer/server/constants"
|
||||||
"github.com/authorizerdev/authorizer/server/cookie"
|
"github.com/authorizerdev/authorizer/server/cookie"
|
||||||
@@ -25,7 +26,7 @@ func LoginResolver(ctx context.Context, params model.LoginInput) (*model.AuthRes
|
|||||||
return res, err
|
return res, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if envstore.EnvInMemoryStoreObj.GetBoolStoreEnvVariable(constants.EnvKeyDisableBasicAuthentication) {
|
if envstore.EnvStoreObj.GetBoolStoreEnvVariable(constants.EnvKeyDisableBasicAuthentication) {
|
||||||
return res, fmt.Errorf(`basic authentication is disabled for this instance`)
|
return res, fmt.Errorf(`basic authentication is disabled for this instance`)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -35,6 +36,10 @@ func LoginResolver(ctx context.Context, params model.LoginInput) (*model.AuthRes
|
|||||||
return res, fmt.Errorf(`user with this email not found`)
|
return res, fmt.Errorf(`user with this email not found`)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if user.RevokedTimestamp != nil {
|
||||||
|
return res, fmt.Errorf(`user access has been revoked`)
|
||||||
|
}
|
||||||
|
|
||||||
if !strings.Contains(user.SignupMethods, constants.SignupMethodBasicAuth) {
|
if !strings.Contains(user.SignupMethods, constants.SignupMethodBasicAuth) {
|
||||||
return res, fmt.Errorf(`user has not signed up email & password`)
|
return res, fmt.Errorf(`user has not signed up email & password`)
|
||||||
}
|
}
|
||||||
@@ -49,7 +54,7 @@ func LoginResolver(ctx context.Context, params model.LoginInput) (*model.AuthRes
|
|||||||
log.Println("compare password error:", err)
|
log.Println("compare password error:", err)
|
||||||
return res, fmt.Errorf(`invalid password`)
|
return res, fmt.Errorf(`invalid password`)
|
||||||
}
|
}
|
||||||
roles := envstore.EnvInMemoryStoreObj.GetSliceStoreEnvVariable(constants.EnvKeyDefaultRoles)
|
roles := envstore.EnvStoreObj.GetSliceStoreEnvVariable(constants.EnvKeyDefaultRoles)
|
||||||
currentRoles := strings.Split(user.Roles, ",")
|
currentRoles := strings.Split(user.Roles, ",")
|
||||||
if len(params.Roles) > 0 {
|
if len(params.Roles) > 0 {
|
||||||
if !utils.IsValidRoles(currentRoles, params.Roles) {
|
if !utils.IsValidRoles(currentRoles, params.Roles) {
|
||||||
@@ -59,20 +64,39 @@ func LoginResolver(ctx context.Context, params model.LoginInput) (*model.AuthRes
|
|||||||
roles = params.Roles
|
roles = params.Roles
|
||||||
}
|
}
|
||||||
|
|
||||||
authToken, err := token.CreateAuthToken(user, roles)
|
scope := []string{"openid", "email", "profile"}
|
||||||
|
if params.Scope != nil && len(scope) > 0 {
|
||||||
|
scope = params.Scope
|
||||||
|
}
|
||||||
|
|
||||||
|
authToken, err := token.CreateAuthToken(gc, user, roles, scope)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return res, err
|
return res, err
|
||||||
}
|
}
|
||||||
sessionstore.SetUserSession(user.ID, authToken.FingerPrint, authToken.RefreshToken.Token)
|
|
||||||
cookie.SetCookie(gc, authToken.AccessToken.Token, authToken.RefreshToken.Token, authToken.FingerPrintHash)
|
expiresIn := authToken.AccessToken.ExpiresAt - time.Now().Unix()
|
||||||
utils.SaveSessionInDB(user.ID, gc)
|
if expiresIn <= 0 {
|
||||||
|
expiresIn = 1
|
||||||
|
}
|
||||||
|
|
||||||
res = &model.AuthResponse{
|
res = &model.AuthResponse{
|
||||||
Message: `Logged in successfully`,
|
Message: `Logged in successfully`,
|
||||||
AccessToken: &authToken.AccessToken.Token,
|
AccessToken: &authToken.AccessToken.Token,
|
||||||
ExpiresAt: &authToken.AccessToken.ExpiresAt,
|
IDToken: &authToken.IDToken.Token,
|
||||||
|
ExpiresIn: &expiresIn,
|
||||||
User: user.AsAPIUser(),
|
User: user.AsAPIUser(),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
cookie.SetSession(gc, authToken.FingerPrintHash)
|
||||||
|
sessionstore.SetState(authToken.FingerPrintHash, authToken.FingerPrint+"@"+user.ID)
|
||||||
|
sessionstore.SetState(authToken.AccessToken.Token, authToken.FingerPrint+"@"+user.ID)
|
||||||
|
|
||||||
|
if authToken.RefreshToken != nil {
|
||||||
|
res.RefreshToken = &authToken.RefreshToken.Token
|
||||||
|
sessionstore.SetState(authToken.RefreshToken.Token, authToken.FingerPrint+"@"+user.ID)
|
||||||
|
}
|
||||||
|
|
||||||
|
go utils.SaveSessionInDB(gc, user.ID)
|
||||||
|
|
||||||
return res, nil
|
return res, nil
|
||||||
}
|
}
|
||||||
|
@@ -4,9 +4,9 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
|
|
||||||
"github.com/authorizerdev/authorizer/server/cookie"
|
"github.com/authorizerdev/authorizer/server/cookie"
|
||||||
|
"github.com/authorizerdev/authorizer/server/crypto"
|
||||||
"github.com/authorizerdev/authorizer/server/graph/model"
|
"github.com/authorizerdev/authorizer/server/graph/model"
|
||||||
"github.com/authorizerdev/authorizer/server/sessionstore"
|
"github.com/authorizerdev/authorizer/server/sessionstore"
|
||||||
"github.com/authorizerdev/authorizer/server/token"
|
|
||||||
"github.com/authorizerdev/authorizer/server/utils"
|
"github.com/authorizerdev/authorizer/server/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -18,34 +18,21 @@ func LogoutResolver(ctx context.Context) (*model.Response, error) {
|
|||||||
return res, err
|
return res, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// get refresh token
|
|
||||||
refreshToken, err := token.GetRefreshToken(gc)
|
|
||||||
if err != nil {
|
|
||||||
return res, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// get fingerprint hash
|
// get fingerprint hash
|
||||||
fingerprintHash, err := token.GetFingerPrint(gc)
|
fingerprintHash, err := cookie.GetSession(gc)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return res, err
|
return res, err
|
||||||
}
|
}
|
||||||
|
|
||||||
decryptedFingerPrint, err := utils.DecryptAES([]byte(fingerprintHash))
|
decryptedFingerPrint, err := crypto.DecryptAES(fingerprintHash)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return res, err
|
return res, err
|
||||||
}
|
}
|
||||||
|
|
||||||
fingerPrint := string(decryptedFingerPrint)
|
fingerPrint := string(decryptedFingerPrint)
|
||||||
|
|
||||||
// verify refresh token and fingerprint
|
sessionstore.RemoveState(fingerPrint)
|
||||||
claims, err := token.ParseJWTToken(refreshToken)
|
cookie.DeleteSession(gc)
|
||||||
if err != nil {
|
|
||||||
return res, err
|
|
||||||
}
|
|
||||||
|
|
||||||
userID := claims["id"].(string)
|
|
||||||
sessionstore.DeleteUserSession(userID, fingerPrint)
|
|
||||||
cookie.DeleteCookie(gc)
|
|
||||||
|
|
||||||
res = &model.Response{
|
res = &model.Response{
|
||||||
Message: "Logged out successfully",
|
Message: "Logged out successfully",
|
||||||
|
@@ -25,7 +25,7 @@ func MagicLinkLoginResolver(ctx context.Context, params model.MagicLinkLoginInpu
|
|||||||
return res, err
|
return res, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if envstore.EnvInMemoryStoreObj.GetBoolStoreEnvVariable(constants.EnvKeyDisableMagicLinkLogin) {
|
if envstore.EnvStoreObj.GetBoolStoreEnvVariable(constants.EnvKeyDisableMagicLinkLogin) {
|
||||||
return res, fmt.Errorf(`magic link login is disabled for this instance`)
|
return res, fmt.Errorf(`magic link login is disabled for this instance`)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -43,19 +43,22 @@ func MagicLinkLoginResolver(ctx context.Context, params model.MagicLinkLoginInpu
|
|||||||
|
|
||||||
// find user with email
|
// find user with email
|
||||||
existingUser, err := db.Provider.GetUserByEmail(params.Email)
|
existingUser, err := db.Provider.GetUserByEmail(params.Email)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
if envstore.EnvStoreObj.GetBoolStoreEnvVariable(constants.EnvKeyDisableSignUp) {
|
||||||
|
return res, fmt.Errorf(`signup is disabled for this instance`)
|
||||||
|
}
|
||||||
|
|
||||||
user.SignupMethods = constants.SignupMethodMagicLinkLogin
|
user.SignupMethods = constants.SignupMethodMagicLinkLogin
|
||||||
// define roles for new user
|
// define roles for new user
|
||||||
if len(params.Roles) > 0 {
|
if len(params.Roles) > 0 {
|
||||||
// check if roles exists
|
// check if roles exists
|
||||||
if !utils.IsValidRoles(envstore.EnvInMemoryStoreObj.GetSliceStoreEnvVariable(constants.EnvKeyRoles), params.Roles) {
|
if !utils.IsValidRoles(envstore.EnvStoreObj.GetSliceStoreEnvVariable(constants.EnvKeyRoles), params.Roles) {
|
||||||
return res, fmt.Errorf(`invalid roles`)
|
return res, fmt.Errorf(`invalid roles`)
|
||||||
} else {
|
} else {
|
||||||
inputRoles = params.Roles
|
inputRoles = params.Roles
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
inputRoles = envstore.EnvInMemoryStoreObj.GetSliceStoreEnvVariable(constants.EnvKeyDefaultRoles)
|
inputRoles = envstore.EnvStoreObj.GetSliceStoreEnvVariable(constants.EnvKeyDefaultRoles)
|
||||||
}
|
}
|
||||||
|
|
||||||
user.Roles = strings.Join(inputRoles, ",")
|
user.Roles = strings.Join(inputRoles, ",")
|
||||||
@@ -67,7 +70,14 @@ func MagicLinkLoginResolver(ctx context.Context, params model.MagicLinkLoginInpu
|
|||||||
// 2. user has not signed up for one of the available role but trying to signup.
|
// 2. user has not signed up for one of the available role but trying to signup.
|
||||||
// Need to modify roles in this case
|
// Need to modify roles in this case
|
||||||
|
|
||||||
|
if user.RevokedTimestamp != nil {
|
||||||
|
return res, fmt.Errorf(`user access has been revoked`)
|
||||||
|
}
|
||||||
|
|
||||||
// find the unassigned roles
|
// find the unassigned roles
|
||||||
|
if len(params.Roles) <= 0 {
|
||||||
|
inputRoles = envstore.EnvStoreObj.GetSliceStoreEnvVariable(constants.EnvKeyDefaultRoles)
|
||||||
|
}
|
||||||
existingRoles := strings.Split(existingUser.Roles, ",")
|
existingRoles := strings.Split(existingUser.Roles, ",")
|
||||||
unasignedRoles := []string{}
|
unasignedRoles := []string{}
|
||||||
for _, ir := range inputRoles {
|
for _, ir := range inputRoles {
|
||||||
@@ -80,7 +90,7 @@ func MagicLinkLoginResolver(ctx context.Context, params model.MagicLinkLoginInpu
|
|||||||
// check if it contains protected unassigned role
|
// check if it contains protected unassigned role
|
||||||
hasProtectedRole := false
|
hasProtectedRole := false
|
||||||
for _, ur := range unasignedRoles {
|
for _, ur := range unasignedRoles {
|
||||||
if utils.StringSliceContains(envstore.EnvInMemoryStoreObj.GetSliceStoreEnvVariable(constants.EnvKeyProtectedRoles), ur) {
|
if utils.StringSliceContains(envstore.EnvStoreObj.GetSliceStoreEnvVariable(constants.EnvKeyProtectedRoles), ur) {
|
||||||
hasProtectedRole = true
|
hasProtectedRole = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -107,24 +117,49 @@ func MagicLinkLoginResolver(ctx context.Context, params model.MagicLinkLoginInpu
|
|||||||
}
|
}
|
||||||
|
|
||||||
hostname := utils.GetHost(gc)
|
hostname := utils.GetHost(gc)
|
||||||
if !envstore.EnvInMemoryStoreObj.GetBoolStoreEnvVariable(constants.EnvKeyDisableEmailVerification) {
|
if !envstore.EnvStoreObj.GetBoolStoreEnvVariable(constants.EnvKeyDisableEmailVerification) {
|
||||||
// insert verification request
|
// insert verification request
|
||||||
|
_, nonceHash, err := utils.GenerateNonce()
|
||||||
|
if err != nil {
|
||||||
|
return res, err
|
||||||
|
}
|
||||||
|
redirectURLParams := "&roles=" + strings.Join(inputRoles, ",")
|
||||||
|
if params.State != nil {
|
||||||
|
redirectURLParams = redirectURLParams + "&state=" + *params.State
|
||||||
|
}
|
||||||
|
if params.Scope != nil && len(params.Scope) > 0 {
|
||||||
|
redirectURLParams = redirectURLParams + "&scope=" + strings.Join(params.Scope, " ")
|
||||||
|
}
|
||||||
|
redirectURL := utils.GetAppURL(gc)
|
||||||
|
if params.RedirectURI != nil {
|
||||||
|
redirectURL = *params.RedirectURI
|
||||||
|
}
|
||||||
|
|
||||||
|
if strings.Contains(redirectURL, "?") {
|
||||||
|
redirectURL = redirectURL + "&" + redirectURLParams
|
||||||
|
} else {
|
||||||
|
redirectURL = redirectURL + "?" + redirectURLParams
|
||||||
|
}
|
||||||
|
|
||||||
verificationType := constants.VerificationTypeMagicLinkLogin
|
verificationType := constants.VerificationTypeMagicLinkLogin
|
||||||
verificationToken, err := token.CreateVerificationToken(params.Email, verificationType, hostname)
|
verificationToken, err := token.CreateVerificationToken(params.Email, verificationType, hostname, nonceHash, redirectURL)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Println(`error generating token`, err)
|
log.Println(`error generating token`, err)
|
||||||
}
|
}
|
||||||
db.Provider.AddVerificationRequest(models.VerificationRequest{
|
_, err = db.Provider.AddVerificationRequest(models.VerificationRequest{
|
||||||
Token: verificationToken,
|
Token: verificationToken,
|
||||||
Identifier: verificationType,
|
Identifier: verificationType,
|
||||||
ExpiresAt: time.Now().Add(time.Minute * 30).Unix(),
|
ExpiresAt: time.Now().Add(time.Minute * 30).Unix(),
|
||||||
Email: params.Email,
|
Email: params.Email,
|
||||||
|
Nonce: nonceHash,
|
||||||
|
RedirectURI: redirectURL,
|
||||||
})
|
})
|
||||||
|
if err != nil {
|
||||||
|
return res, err
|
||||||
|
}
|
||||||
|
|
||||||
// exec it as go routin so that we can reduce the api latency
|
// exec it as go routing so that we can reduce the api latency
|
||||||
go func() {
|
go email.SendVerificationMail(params.Email, verificationToken, hostname)
|
||||||
email.SendVerificationMail(params.Email, verificationToken, hostname)
|
|
||||||
}()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
res = &model.Response{
|
res = &model.Response{
|
||||||
|
@@ -2,7 +2,6 @@ package resolvers
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"github.com/authorizerdev/authorizer/server/db"
|
"github.com/authorizerdev/authorizer/server/db"
|
||||||
"github.com/authorizerdev/authorizer/server/graph/model"
|
"github.com/authorizerdev/authorizer/server/graph/model"
|
||||||
@@ -18,13 +17,17 @@ func ProfileResolver(ctx context.Context) (*model.User, error) {
|
|||||||
return res, err
|
return res, err
|
||||||
}
|
}
|
||||||
|
|
||||||
claims, err := token.ValidateAccessToken(gc)
|
accessToken, err := token.GetAccessToken(gc)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return res, err
|
return res, err
|
||||||
}
|
}
|
||||||
|
|
||||||
userID := fmt.Sprintf("%v", claims["id"])
|
claims, err := token.ValidateAccessToken(gc, accessToken)
|
||||||
|
if err != nil {
|
||||||
|
return res, err
|
||||||
|
}
|
||||||
|
|
||||||
|
userID := claims["sub"].(string)
|
||||||
user, err := db.Provider.GetUserByID(userID)
|
user, err := db.Provider.GetUserByID(userID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return res, err
|
return res, err
|
||||||
|
@@ -44,7 +44,12 @@ func ResendVerifyEmailResolver(ctx context.Context, params model.ResendVerifyEma
|
|||||||
}
|
}
|
||||||
|
|
||||||
hostname := utils.GetHost(gc)
|
hostname := utils.GetHost(gc)
|
||||||
verificationToken, err := token.CreateVerificationToken(params.Email, params.Identifier, hostname)
|
_, nonceHash, err := utils.GenerateNonce()
|
||||||
|
if err != nil {
|
||||||
|
return res, err
|
||||||
|
}
|
||||||
|
|
||||||
|
verificationToken, err := token.CreateVerificationToken(params.Email, params.Identifier, hostname, nonceHash, verificationRequest.RedirectURI)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Println(`error generating token`, err)
|
log.Println(`error generating token`, err)
|
||||||
}
|
}
|
||||||
@@ -53,12 +58,12 @@ func ResendVerifyEmailResolver(ctx context.Context, params model.ResendVerifyEma
|
|||||||
Identifier: params.Identifier,
|
Identifier: params.Identifier,
|
||||||
ExpiresAt: time.Now().Add(time.Minute * 30).Unix(),
|
ExpiresAt: time.Now().Add(time.Minute * 30).Unix(),
|
||||||
Email: params.Email,
|
Email: params.Email,
|
||||||
|
Nonce: nonceHash,
|
||||||
|
RedirectURI: verificationRequest.RedirectURI,
|
||||||
})
|
})
|
||||||
|
|
||||||
// exec it as go routin so that we can reduce the api latency
|
// exec it as go routin so that we can reduce the api latency
|
||||||
go func() {
|
go email.SendVerificationMail(params.Email, verificationToken, hostname)
|
||||||
email.SendVerificationMail(params.Email, verificationToken, hostname)
|
|
||||||
}()
|
|
||||||
|
|
||||||
res = &model.Response{
|
res = &model.Response{
|
||||||
Message: `Verification email has been sent. Please check your inbox`,
|
Message: `Verification email has been sent. Please check your inbox`,
|
||||||
|
@@ -7,6 +7,7 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/authorizerdev/authorizer/server/constants"
|
"github.com/authorizerdev/authorizer/server/constants"
|
||||||
|
"github.com/authorizerdev/authorizer/server/crypto"
|
||||||
"github.com/authorizerdev/authorizer/server/db"
|
"github.com/authorizerdev/authorizer/server/db"
|
||||||
"github.com/authorizerdev/authorizer/server/envstore"
|
"github.com/authorizerdev/authorizer/server/envstore"
|
||||||
"github.com/authorizerdev/authorizer/server/graph/model"
|
"github.com/authorizerdev/authorizer/server/graph/model"
|
||||||
@@ -17,7 +18,11 @@ import (
|
|||||||
// ResetPasswordResolver is a resolver for reset password mutation
|
// ResetPasswordResolver is a resolver for reset password mutation
|
||||||
func ResetPasswordResolver(ctx context.Context, params model.ResetPasswordInput) (*model.Response, error) {
|
func ResetPasswordResolver(ctx context.Context, params model.ResetPasswordInput) (*model.Response, error) {
|
||||||
var res *model.Response
|
var res *model.Response
|
||||||
if envstore.EnvInMemoryStoreObj.GetBoolStoreEnvVariable(constants.EnvKeyDisableBasicAuthentication) {
|
gc, err := utils.GinContextFromContext(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return res, err
|
||||||
|
}
|
||||||
|
if envstore.EnvStoreObj.GetBoolStoreEnvVariable(constants.EnvKeyDisableBasicAuthentication) {
|
||||||
return res, fmt.Errorf(`basic authentication is disabled for this instance`)
|
return res, fmt.Errorf(`basic authentication is disabled for this instance`)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -30,18 +35,23 @@ func ResetPasswordResolver(ctx context.Context, params model.ResetPasswordInput)
|
|||||||
return res, fmt.Errorf(`passwords don't match`)
|
return res, fmt.Errorf(`passwords don't match`)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if !utils.IsValidPassword(params.Password) {
|
||||||
|
return res, fmt.Errorf(`password is not valid. It needs to be at least 6 characters long and contain at least one number, one uppercase letter, one lowercase letter and one special character`)
|
||||||
|
}
|
||||||
|
|
||||||
// verify if token exists in db
|
// verify if token exists in db
|
||||||
claim, err := token.ParseJWTToken(params.Token)
|
hostname := utils.GetHost(gc)
|
||||||
|
claim, err := token.ParseJWTToken(params.Token, hostname, verificationRequest.Nonce, verificationRequest.Email)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return res, fmt.Errorf(`invalid token`)
|
return res, fmt.Errorf(`invalid token`)
|
||||||
}
|
}
|
||||||
|
|
||||||
user, err := db.Provider.GetUserByEmail(claim["email"].(string))
|
user, err := db.Provider.GetUserByEmail(claim["sub"].(string))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return res, err
|
return res, err
|
||||||
}
|
}
|
||||||
|
|
||||||
password, _ := utils.EncryptPassword(params.Password)
|
password, _ := crypto.EncryptPassword(params.Password)
|
||||||
user.Password = &password
|
user.Password = &password
|
||||||
|
|
||||||
signupMethod := user.SignupMethods
|
signupMethod := user.SignupMethods
|
||||||
|
16
server/resolvers/revoke.go
Normal file
16
server/resolvers/revoke.go
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
package resolvers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"github.com/authorizerdev/authorizer/server/graph/model"
|
||||||
|
"github.com/authorizerdev/authorizer/server/sessionstore"
|
||||||
|
)
|
||||||
|
|
||||||
|
// RevokeResolver resolver to revoke refresh token
|
||||||
|
func RevokeResolver(ctx context.Context, params model.OAuthRevokeInput) (*model.Response, error) {
|
||||||
|
sessionstore.RemoveState(params.RefreshToken)
|
||||||
|
return &model.Response{
|
||||||
|
Message: "Token revoked",
|
||||||
|
}, nil
|
||||||
|
}
|
49
server/resolvers/revoke_access.go
Normal file
49
server/resolvers/revoke_access.go
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
package resolvers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/authorizerdev/authorizer/server/db"
|
||||||
|
"github.com/authorizerdev/authorizer/server/graph/model"
|
||||||
|
"github.com/authorizerdev/authorizer/server/sessionstore"
|
||||||
|
"github.com/authorizerdev/authorizer/server/token"
|
||||||
|
"github.com/authorizerdev/authorizer/server/utils"
|
||||||
|
)
|
||||||
|
|
||||||
|
// RevokeAccessResolver is a resolver for revoking user access
|
||||||
|
func RevokeAccessResolver(ctx context.Context, params model.UpdateAccessInput) (*model.Response, error) {
|
||||||
|
gc, err := utils.GinContextFromContext(ctx)
|
||||||
|
var res *model.Response
|
||||||
|
if err != nil {
|
||||||
|
return res, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if !token.IsSuperAdmin(gc) {
|
||||||
|
return res, fmt.Errorf("unauthorized")
|
||||||
|
}
|
||||||
|
|
||||||
|
user, err := db.Provider.GetUserByID(params.UserID)
|
||||||
|
if err != nil {
|
||||||
|
return res, err
|
||||||
|
}
|
||||||
|
|
||||||
|
now := time.Now().Unix()
|
||||||
|
user.RevokedTimestamp = &now
|
||||||
|
|
||||||
|
user, err = db.Provider.UpdateUser(user)
|
||||||
|
if err != nil {
|
||||||
|
log.Println("error updating user:", err)
|
||||||
|
return res, err
|
||||||
|
}
|
||||||
|
|
||||||
|
go sessionstore.DeleteAllUserSession(fmt.Sprintf("%x", user.ID))
|
||||||
|
|
||||||
|
res = &model.Response{
|
||||||
|
Message: `user access revoked successfully`,
|
||||||
|
}
|
||||||
|
|
||||||
|
return res, nil
|
||||||
|
}
|
@@ -2,7 +2,10 @@ package resolvers
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/authorizerdev/authorizer/server/cookie"
|
"github.com/authorizerdev/authorizer/server/cookie"
|
||||||
"github.com/authorizerdev/authorizer/server/db"
|
"github.com/authorizerdev/authorizer/server/db"
|
||||||
@@ -13,6 +16,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// SessionResolver is a resolver for session query
|
// SessionResolver is a resolver for session query
|
||||||
|
// TODO allow validating with code and code verifier instead of cookie (PKCE flow)
|
||||||
func SessionResolver(ctx context.Context, params *model.SessionQueryInput) (*model.AuthResponse, error) {
|
func SessionResolver(ctx context.Context, params *model.SessionQueryInput) (*model.AuthResponse, error) {
|
||||||
var res *model.AuthResponse
|
var res *model.AuthResponse
|
||||||
|
|
||||||
@@ -21,48 +25,29 @@ func SessionResolver(ctx context.Context, params *model.SessionQueryInput) (*mod
|
|||||||
return res, err
|
return res, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// get refresh token
|
sessionToken, err := cookie.GetSession(gc)
|
||||||
refreshToken, err := token.GetRefreshToken(gc)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return res, err
|
log.Println("error getting session token:", err)
|
||||||
|
return res, errors.New("unauthorized")
|
||||||
}
|
}
|
||||||
|
|
||||||
// get fingerprint hash
|
// get session from cookie
|
||||||
fingerprintHash, err := token.GetFingerPrint(gc)
|
claims, err := token.ValidateBrowserSession(gc, sessionToken)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return res, err
|
log.Println("session validation failed:", err)
|
||||||
|
return res, errors.New("unauthorized")
|
||||||
}
|
}
|
||||||
|
userID := claims.Subject
|
||||||
decryptedFingerPrint, err := utils.DecryptAES([]byte(fingerprintHash))
|
|
||||||
if err != nil {
|
|
||||||
return res, err
|
|
||||||
}
|
|
||||||
|
|
||||||
fingerPrint := string(decryptedFingerPrint)
|
|
||||||
|
|
||||||
// verify refresh token and fingerprint
|
|
||||||
claims, err := token.ParseJWTToken(refreshToken)
|
|
||||||
if err != nil {
|
|
||||||
return res, err
|
|
||||||
}
|
|
||||||
|
|
||||||
userID := claims["id"].(string)
|
|
||||||
|
|
||||||
persistedRefresh := sessionstore.GetUserSession(userID, fingerPrint)
|
|
||||||
if refreshToken != persistedRefresh {
|
|
||||||
return res, fmt.Errorf(`unauthorized`)
|
|
||||||
}
|
|
||||||
|
|
||||||
user, err := db.Provider.GetUserByID(userID)
|
user, err := db.Provider.GetUserByID(userID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return res, err
|
return res, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// refresh token has "roles" as claim
|
// refresh token has "roles" as claim
|
||||||
claimRoleInterface := claims["roles"].([]interface{})
|
claimRoleInterface := claims.Roles
|
||||||
claimRoles := []string{}
|
claimRoles := []string{}
|
||||||
for _, v := range claimRoleInterface {
|
for _, v := range claimRoleInterface {
|
||||||
claimRoles = append(claimRoles, v.(string))
|
claimRoles = append(claimRoles, v)
|
||||||
}
|
}
|
||||||
|
|
||||||
if params != nil && params.Roles != nil && len(params.Roles) > 0 {
|
if params != nil && params.Roles != nil && len(params.Roles) > 0 {
|
||||||
@@ -73,22 +58,39 @@ func SessionResolver(ctx context.Context, params *model.SessionQueryInput) (*mod
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// delete older session
|
scope := []string{"openid", "email", "profile"}
|
||||||
sessionstore.DeleteUserSession(userID, fingerPrint)
|
if params != nil && params.Scope != nil && len(scope) > 0 {
|
||||||
|
scope = params.Scope
|
||||||
|
}
|
||||||
|
|
||||||
authToken, err := token.CreateAuthToken(user, claimRoles)
|
authToken, err := token.CreateAuthToken(gc, user, claimRoles, scope)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return res, err
|
return res, err
|
||||||
}
|
}
|
||||||
sessionstore.SetUserSession(user.ID, authToken.FingerPrint, authToken.RefreshToken.Token)
|
|
||||||
cookie.SetCookie(gc, authToken.AccessToken.Token, authToken.RefreshToken.Token, authToken.FingerPrintHash)
|
// rollover the session for security
|
||||||
|
sessionstore.RemoveState(sessionToken)
|
||||||
|
sessionstore.SetState(authToken.FingerPrintHash, authToken.FingerPrint+"@"+user.ID)
|
||||||
|
sessionstore.SetState(authToken.AccessToken.Token, authToken.FingerPrint+"@"+user.ID)
|
||||||
|
cookie.SetSession(gc, authToken.FingerPrintHash)
|
||||||
|
|
||||||
|
expiresIn := authToken.AccessToken.ExpiresAt - time.Now().Unix()
|
||||||
|
if expiresIn <= 0 {
|
||||||
|
expiresIn = 1
|
||||||
|
}
|
||||||
|
|
||||||
res = &model.AuthResponse{
|
res = &model.AuthResponse{
|
||||||
Message: `Session token refreshed`,
|
Message: `Session token refreshed`,
|
||||||
AccessToken: &authToken.AccessToken.Token,
|
AccessToken: &authToken.AccessToken.Token,
|
||||||
ExpiresAt: &authToken.AccessToken.ExpiresAt,
|
ExpiresIn: &expiresIn,
|
||||||
|
IDToken: &authToken.IDToken.Token,
|
||||||
User: user.AsAPIUser(),
|
User: user.AsAPIUser(),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if authToken.RefreshToken != nil {
|
||||||
|
res.RefreshToken = &authToken.RefreshToken.Token
|
||||||
|
sessionstore.SetState(authToken.RefreshToken.Token, authToken.FingerPrint+"@"+user.ID)
|
||||||
|
}
|
||||||
|
|
||||||
return res, nil
|
return res, nil
|
||||||
}
|
}
|
||||||
|
@@ -9,6 +9,7 @@ import (
|
|||||||
|
|
||||||
"github.com/authorizerdev/authorizer/server/constants"
|
"github.com/authorizerdev/authorizer/server/constants"
|
||||||
"github.com/authorizerdev/authorizer/server/cookie"
|
"github.com/authorizerdev/authorizer/server/cookie"
|
||||||
|
"github.com/authorizerdev/authorizer/server/crypto"
|
||||||
"github.com/authorizerdev/authorizer/server/db"
|
"github.com/authorizerdev/authorizer/server/db"
|
||||||
"github.com/authorizerdev/authorizer/server/db/models"
|
"github.com/authorizerdev/authorizer/server/db/models"
|
||||||
"github.com/authorizerdev/authorizer/server/email"
|
"github.com/authorizerdev/authorizer/server/email"
|
||||||
@@ -27,13 +28,22 @@ func SignupResolver(ctx context.Context, params model.SignUpInput) (*model.AuthR
|
|||||||
return res, err
|
return res, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if envstore.EnvInMemoryStoreObj.GetBoolStoreEnvVariable(constants.EnvKeyDisableBasicAuthentication) {
|
if envstore.EnvStoreObj.GetBoolStoreEnvVariable(constants.EnvKeyDisableSignUp) {
|
||||||
|
return res, fmt.Errorf(`signup is disabled for this instance`)
|
||||||
|
}
|
||||||
|
|
||||||
|
if envstore.EnvStoreObj.GetBoolStoreEnvVariable(constants.EnvKeyDisableBasicAuthentication) {
|
||||||
return res, fmt.Errorf(`basic authentication is disabled for this instance`)
|
return res, fmt.Errorf(`basic authentication is disabled for this instance`)
|
||||||
}
|
}
|
||||||
|
|
||||||
if params.ConfirmPassword != params.Password {
|
if params.ConfirmPassword != params.Password {
|
||||||
return res, fmt.Errorf(`password and confirm password does not match`)
|
return res, fmt.Errorf(`password and confirm password does not match`)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if !utils.IsValidPassword(params.Password) {
|
||||||
|
return res, fmt.Errorf(`password is not valid. It needs to be at least 6 characters long and contain at least one number, one uppercase letter, one lowercase letter and one special character`)
|
||||||
|
}
|
||||||
|
|
||||||
params.Email = strings.ToLower(params.Email)
|
params.Email = strings.ToLower(params.Email)
|
||||||
|
|
||||||
if !utils.IsValidEmail(params.Email) {
|
if !utils.IsValidEmail(params.Email) {
|
||||||
@@ -57,13 +67,13 @@ func SignupResolver(ctx context.Context, params model.SignUpInput) (*model.AuthR
|
|||||||
|
|
||||||
if len(params.Roles) > 0 {
|
if len(params.Roles) > 0 {
|
||||||
// check if roles exists
|
// check if roles exists
|
||||||
if !utils.IsValidRoles(envstore.EnvInMemoryStoreObj.GetSliceStoreEnvVariable(constants.EnvKeyRoles), params.Roles) {
|
if !utils.IsValidRoles(envstore.EnvStoreObj.GetSliceStoreEnvVariable(constants.EnvKeyRoles), params.Roles) {
|
||||||
return res, fmt.Errorf(`invalid roles`)
|
return res, fmt.Errorf(`invalid roles`)
|
||||||
} else {
|
} else {
|
||||||
inputRoles = params.Roles
|
inputRoles = params.Roles
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
inputRoles = envstore.EnvInMemoryStoreObj.GetSliceStoreEnvVariable(constants.EnvKeyDefaultRoles)
|
inputRoles = envstore.EnvStoreObj.GetSliceStoreEnvVariable(constants.EnvKeyDefaultRoles)
|
||||||
}
|
}
|
||||||
|
|
||||||
user := models.User{
|
user := models.User{
|
||||||
@@ -72,7 +82,7 @@ func SignupResolver(ctx context.Context, params model.SignUpInput) (*model.AuthR
|
|||||||
|
|
||||||
user.Roles = strings.Join(inputRoles, ",")
|
user.Roles = strings.Join(inputRoles, ",")
|
||||||
|
|
||||||
password, _ := utils.EncryptPassword(params.Password)
|
password, _ := crypto.EncryptPassword(params.Password)
|
||||||
user.Password = &password
|
user.Password = &password
|
||||||
|
|
||||||
if params.GivenName != nil {
|
if params.GivenName != nil {
|
||||||
@@ -108,7 +118,7 @@ func SignupResolver(ctx context.Context, params model.SignUpInput) (*model.AuthR
|
|||||||
}
|
}
|
||||||
|
|
||||||
user.SignupMethods = constants.SignupMethodBasicAuth
|
user.SignupMethods = constants.SignupMethodBasicAuth
|
||||||
if envstore.EnvInMemoryStoreObj.GetBoolStoreEnvVariable(constants.EnvKeyDisableEmailVerification) {
|
if envstore.EnvStoreObj.GetBoolStoreEnvVariable(constants.EnvKeyDisableEmailVerification) {
|
||||||
now := time.Now().Unix()
|
now := time.Now().Unix()
|
||||||
user.EmailVerifiedAt = &now
|
user.EmailVerifiedAt = &now
|
||||||
}
|
}
|
||||||
@@ -120,43 +130,61 @@ func SignupResolver(ctx context.Context, params model.SignUpInput) (*model.AuthR
|
|||||||
userToReturn := user.AsAPIUser()
|
userToReturn := user.AsAPIUser()
|
||||||
|
|
||||||
hostname := utils.GetHost(gc)
|
hostname := utils.GetHost(gc)
|
||||||
if !envstore.EnvInMemoryStoreObj.GetBoolStoreEnvVariable(constants.EnvKeyDisableEmailVerification) {
|
if !envstore.EnvStoreObj.GetBoolStoreEnvVariable(constants.EnvKeyDisableEmailVerification) {
|
||||||
// insert verification request
|
// insert verification request
|
||||||
verificationType := constants.VerificationTypeBasicAuthSignup
|
_, nonceHash, err := utils.GenerateNonce()
|
||||||
verificationToken, err := token.CreateVerificationToken(params.Email, verificationType, hostname)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Println(`error generating token`, err)
|
return res, err
|
||||||
|
}
|
||||||
|
verificationType := constants.VerificationTypeBasicAuthSignup
|
||||||
|
redirectURL := utils.GetAppURL(gc)
|
||||||
|
if params.RedirectURI != nil {
|
||||||
|
redirectURL = *params.RedirectURI
|
||||||
|
}
|
||||||
|
verificationToken, err := token.CreateVerificationToken(params.Email, verificationType, hostname, nonceHash, redirectURL)
|
||||||
|
if err != nil {
|
||||||
|
return res, err
|
||||||
}
|
}
|
||||||
db.Provider.AddVerificationRequest(models.VerificationRequest{
|
db.Provider.AddVerificationRequest(models.VerificationRequest{
|
||||||
Token: verificationToken,
|
Token: verificationToken,
|
||||||
Identifier: verificationType,
|
Identifier: verificationType,
|
||||||
ExpiresAt: time.Now().Add(time.Minute * 30).Unix(),
|
ExpiresAt: time.Now().Add(time.Minute * 30).Unix(),
|
||||||
Email: params.Email,
|
Email: params.Email,
|
||||||
|
Nonce: nonceHash,
|
||||||
|
RedirectURI: redirectURL,
|
||||||
})
|
})
|
||||||
|
|
||||||
// exec it as go routin so that we can reduce the api latency
|
// exec it as go routin so that we can reduce the api latency
|
||||||
go func() {
|
go email.SendVerificationMail(params.Email, verificationToken, hostname)
|
||||||
email.SendVerificationMail(params.Email, verificationToken, hostname)
|
|
||||||
}()
|
|
||||||
|
|
||||||
res = &model.AuthResponse{
|
res = &model.AuthResponse{
|
||||||
Message: `Verification email has been sent. Please check your inbox`,
|
Message: `Verification email has been sent. Please check your inbox`,
|
||||||
User: userToReturn,
|
User: userToReturn,
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
scope := []string{"openid", "email", "profile"}
|
||||||
|
if params.Scope != nil && len(scope) > 0 {
|
||||||
|
scope = params.Scope
|
||||||
|
}
|
||||||
|
|
||||||
authToken, err := token.CreateAuthToken(user, roles)
|
authToken, err := token.CreateAuthToken(gc, user, roles, scope)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return res, err
|
return res, err
|
||||||
}
|
}
|
||||||
sessionstore.SetUserSession(user.ID, authToken.FingerPrint, authToken.RefreshToken.Token)
|
|
||||||
cookie.SetCookie(gc, authToken.AccessToken.Token, authToken.RefreshToken.Token, authToken.FingerPrintHash)
|
sessionstore.SetState(authToken.FingerPrintHash, authToken.FingerPrint+"@"+user.ID)
|
||||||
utils.SaveSessionInDB(user.ID, gc)
|
cookie.SetSession(gc, authToken.FingerPrintHash)
|
||||||
|
go utils.SaveSessionInDB(gc, user.ID)
|
||||||
|
|
||||||
|
expiresIn := authToken.AccessToken.ExpiresAt - time.Now().Unix()
|
||||||
|
if expiresIn <= 0 {
|
||||||
|
expiresIn = 1
|
||||||
|
}
|
||||||
|
|
||||||
res = &model.AuthResponse{
|
res = &model.AuthResponse{
|
||||||
Message: `Signed up successfully.`,
|
Message: `Signed up successfully.`,
|
||||||
AccessToken: &authToken.AccessToken.Token,
|
AccessToken: &authToken.AccessToken.Token,
|
||||||
ExpiresAt: &authToken.AccessToken.ExpiresAt,
|
ExpiresIn: &expiresIn,
|
||||||
User: userToReturn,
|
User: userToReturn,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -10,6 +10,7 @@ import (
|
|||||||
|
|
||||||
"github.com/authorizerdev/authorizer/server/constants"
|
"github.com/authorizerdev/authorizer/server/constants"
|
||||||
"github.com/authorizerdev/authorizer/server/cookie"
|
"github.com/authorizerdev/authorizer/server/cookie"
|
||||||
|
"github.com/authorizerdev/authorizer/server/crypto"
|
||||||
"github.com/authorizerdev/authorizer/server/db"
|
"github.com/authorizerdev/authorizer/server/db"
|
||||||
"github.com/authorizerdev/authorizer/server/envstore"
|
"github.com/authorizerdev/authorizer/server/envstore"
|
||||||
"github.com/authorizerdev/authorizer/server/graph/model"
|
"github.com/authorizerdev/authorizer/server/graph/model"
|
||||||
@@ -33,6 +34,78 @@ func UpdateEnvResolver(ctx context.Context, params model.UpdateEnvInput) (*model
|
|||||||
return res, fmt.Errorf("unauthorized")
|
return res, fmt.Errorf("unauthorized")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
updatedData := envstore.EnvStoreObj.GetEnvStoreClone()
|
||||||
|
|
||||||
|
isJWTUpdated := false
|
||||||
|
algo := updatedData.StringEnv[constants.EnvKeyJwtType]
|
||||||
|
if params.JwtType != nil {
|
||||||
|
algo = *params.JwtType
|
||||||
|
if !crypto.IsHMACA(algo) && !crypto.IsECDSA(algo) && !crypto.IsRSA(algo) {
|
||||||
|
return res, fmt.Errorf("invalid jwt type")
|
||||||
|
}
|
||||||
|
|
||||||
|
updatedData.StringEnv[constants.EnvKeyJwtType] = algo
|
||||||
|
isJWTUpdated = true
|
||||||
|
}
|
||||||
|
|
||||||
|
if params.JwtSecret != nil || params.JwtPublicKey != nil || params.JwtPrivateKey != nil {
|
||||||
|
isJWTUpdated = true
|
||||||
|
}
|
||||||
|
|
||||||
|
if isJWTUpdated {
|
||||||
|
// use to reset when type is changed from rsa, edsa -> hmac or vice a versa
|
||||||
|
defaultSecret := ""
|
||||||
|
defaultPublicKey := ""
|
||||||
|
defaultPrivateKey := ""
|
||||||
|
// check if jwt secret is provided
|
||||||
|
if crypto.IsHMACA(algo) {
|
||||||
|
if params.JwtSecret == nil {
|
||||||
|
return res, fmt.Errorf("jwt secret is required for HMAC algorithm")
|
||||||
|
}
|
||||||
|
|
||||||
|
// reset public key and private key
|
||||||
|
params.JwtPrivateKey = &defaultPrivateKey
|
||||||
|
params.JwtPublicKey = &defaultPublicKey
|
||||||
|
}
|
||||||
|
|
||||||
|
if crypto.IsRSA(algo) {
|
||||||
|
if params.JwtPrivateKey == nil || params.JwtPublicKey == nil {
|
||||||
|
return res, fmt.Errorf("jwt private and public key is required for RSA (PKCS1) / ECDSA algorithm")
|
||||||
|
}
|
||||||
|
|
||||||
|
// reset the jwt secret
|
||||||
|
params.JwtSecret = &defaultSecret
|
||||||
|
_, err = crypto.ParseRsaPrivateKeyFromPemStr(*params.JwtPrivateKey)
|
||||||
|
if err != nil {
|
||||||
|
return res, err
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err := crypto.ParseRsaPublicKeyFromPemStr(*params.JwtPublicKey)
|
||||||
|
if err != nil {
|
||||||
|
return res, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if crypto.IsECDSA(algo) {
|
||||||
|
if params.JwtPrivateKey == nil || params.JwtPublicKey == nil {
|
||||||
|
return res, fmt.Errorf("jwt private and public key is required for RSA (PKCS1) / ECDSA algorithm")
|
||||||
|
}
|
||||||
|
|
||||||
|
// reset the jwt secret
|
||||||
|
params.JwtSecret = &defaultSecret
|
||||||
|
_, err = crypto.ParseEcdsaPrivateKeyFromPemStr(*params.JwtPrivateKey)
|
||||||
|
if err != nil {
|
||||||
|
return res, err
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err := crypto.ParseEcdsaPublicKeyFromPemStr(*params.JwtPublicKey)
|
||||||
|
if err != nil {
|
||||||
|
return res, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
var data map[string]interface{}
|
var data map[string]interface{}
|
||||||
byteData, err := json.Marshal(params)
|
byteData, err := json.Marshal(params)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -50,7 +123,7 @@ func UpdateEnvResolver(ctx context.Context, params model.UpdateEnvInput) (*model
|
|||||||
return res, errors.New("admin secret and old admin secret are required for secret change")
|
return res, errors.New("admin secret and old admin secret are required for secret change")
|
||||||
}
|
}
|
||||||
|
|
||||||
if *params.OldAdminSecret != envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyAdminSecret) {
|
if *params.OldAdminSecret != envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyAdminSecret) {
|
||||||
return res, errors.New("old admin secret is not correct")
|
return res, errors.New("old admin secret is not correct")
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -61,7 +134,6 @@ func UpdateEnvResolver(ctx context.Context, params model.UpdateEnvInput) (*model
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
updatedData := envstore.EnvInMemoryStoreObj.GetEnvStoreClone()
|
|
||||||
for key, value := range data {
|
for key, value := range data {
|
||||||
if value != nil {
|
if value != nil {
|
||||||
fieldType := reflect.TypeOf(value).String()
|
fieldType := reflect.TypeOf(value).String()
|
||||||
@@ -116,9 +188,21 @@ func UpdateEnvResolver(ctx context.Context, params model.UpdateEnvInput) (*model
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Update local store
|
// Update local store
|
||||||
envstore.EnvInMemoryStoreObj.UpdateEnvStore(updatedData)
|
envstore.EnvStoreObj.UpdateEnvStore(updatedData)
|
||||||
sessionstore.InitSession()
|
jwk, err := crypto.GenerateJWKBasedOnEnv()
|
||||||
oauth.InitOAuth()
|
if err != nil {
|
||||||
|
return res, err
|
||||||
|
}
|
||||||
|
// updating jwk
|
||||||
|
envstore.EnvStoreObj.UpdateEnvVariable(constants.StringStoreIdentifier, constants.EnvKeyJWK, jwk)
|
||||||
|
err = sessionstore.InitSession()
|
||||||
|
if err != nil {
|
||||||
|
return res, err
|
||||||
|
}
|
||||||
|
err = oauth.InitOAuth()
|
||||||
|
if err != nil {
|
||||||
|
return res, err
|
||||||
|
}
|
||||||
|
|
||||||
// Fetch the current db store and update it
|
// Fetch the current db store and update it
|
||||||
env, err := db.Provider.GetEnv()
|
env, err := db.Provider.GetEnv()
|
||||||
@@ -127,14 +211,14 @@ func UpdateEnvResolver(ctx context.Context, params model.UpdateEnvInput) (*model
|
|||||||
}
|
}
|
||||||
|
|
||||||
if params.AdminSecret != nil {
|
if params.AdminSecret != nil {
|
||||||
hashedKey, err := utils.EncryptPassword(envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyAdminSecret))
|
hashedKey, err := crypto.EncryptPassword(envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyAdminSecret))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return res, err
|
return res, err
|
||||||
}
|
}
|
||||||
cookie.SetAdminCookie(gc, hashedKey)
|
cookie.SetAdminCookie(gc, hashedKey)
|
||||||
}
|
}
|
||||||
|
|
||||||
encryptedConfig, err := utils.EncryptEnvData(updatedData)
|
encryptedConfig, err := crypto.EncryptEnvData(updatedData)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return res, err
|
return res, err
|
||||||
}
|
}
|
||||||
|
@@ -9,9 +9,11 @@ import (
|
|||||||
|
|
||||||
"github.com/authorizerdev/authorizer/server/constants"
|
"github.com/authorizerdev/authorizer/server/constants"
|
||||||
"github.com/authorizerdev/authorizer/server/cookie"
|
"github.com/authorizerdev/authorizer/server/cookie"
|
||||||
|
"github.com/authorizerdev/authorizer/server/crypto"
|
||||||
"github.com/authorizerdev/authorizer/server/db"
|
"github.com/authorizerdev/authorizer/server/db"
|
||||||
"github.com/authorizerdev/authorizer/server/db/models"
|
"github.com/authorizerdev/authorizer/server/db/models"
|
||||||
"github.com/authorizerdev/authorizer/server/email"
|
"github.com/authorizerdev/authorizer/server/email"
|
||||||
|
"github.com/authorizerdev/authorizer/server/envstore"
|
||||||
"github.com/authorizerdev/authorizer/server/graph/model"
|
"github.com/authorizerdev/authorizer/server/graph/model"
|
||||||
"github.com/authorizerdev/authorizer/server/sessionstore"
|
"github.com/authorizerdev/authorizer/server/sessionstore"
|
||||||
"github.com/authorizerdev/authorizer/server/token"
|
"github.com/authorizerdev/authorizer/server/token"
|
||||||
@@ -27,7 +29,11 @@ func UpdateProfileResolver(ctx context.Context, params model.UpdateProfileInput)
|
|||||||
return res, err
|
return res, err
|
||||||
}
|
}
|
||||||
|
|
||||||
claims, err := token.ValidateAccessToken(gc)
|
accessToken, err := token.GetAccessToken(gc)
|
||||||
|
if err != nil {
|
||||||
|
return res, err
|
||||||
|
}
|
||||||
|
claims, err := token.ValidateAccessToken(gc, accessToken)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return res, err
|
return res, err
|
||||||
}
|
}
|
||||||
@@ -37,8 +43,8 @@ func UpdateProfileResolver(ctx context.Context, params model.UpdateProfileInput)
|
|||||||
return res, fmt.Errorf("please enter at least one param to update")
|
return res, fmt.Errorf("please enter at least one param to update")
|
||||||
}
|
}
|
||||||
|
|
||||||
userEmail := fmt.Sprintf("%v", claims["email"])
|
userID := claims["sub"].(string)
|
||||||
user, err := db.Provider.GetUserByEmail(userEmail)
|
user, err := db.Provider.GetUserByID(userID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return res, err
|
return res, err
|
||||||
}
|
}
|
||||||
@@ -92,7 +98,7 @@ func UpdateProfileResolver(ctx context.Context, params model.UpdateProfileInput)
|
|||||||
return res, fmt.Errorf(`password and confirm password does not match`)
|
return res, fmt.Errorf(`password and confirm password does not match`)
|
||||||
}
|
}
|
||||||
|
|
||||||
password, _ := utils.EncryptPassword(*params.NewPassword)
|
password, _ := crypto.EncryptPassword(*params.NewPassword)
|
||||||
|
|
||||||
user.Password = &password
|
user.Password = &password
|
||||||
}
|
}
|
||||||
@@ -107,22 +113,29 @@ func UpdateProfileResolver(ctx context.Context, params model.UpdateProfileInput)
|
|||||||
newEmail := strings.ToLower(*params.Email)
|
newEmail := strings.ToLower(*params.Email)
|
||||||
// check if user with new email exists
|
// check if user with new email exists
|
||||||
_, err := db.Provider.GetUserByEmail(newEmail)
|
_, err := db.Provider.GetUserByEmail(newEmail)
|
||||||
|
|
||||||
// err = nil means user exists
|
// err = nil means user exists
|
||||||
if err == nil {
|
if err == nil {
|
||||||
return res, fmt.Errorf("user with this email address already exists")
|
return res, fmt.Errorf("user with this email address already exists")
|
||||||
}
|
}
|
||||||
|
|
||||||
sessionstore.DeleteAllUserSession(fmt.Sprintf("%v", user.ID))
|
// TODO figure out how to delete all user sessions
|
||||||
cookie.DeleteCookie(gc)
|
go sessionstore.DeleteAllUserSession(user.ID)
|
||||||
|
|
||||||
hostname := utils.GetHost(gc)
|
cookie.DeleteSession(gc)
|
||||||
user.Email = newEmail
|
user.Email = newEmail
|
||||||
|
|
||||||
|
if !envstore.EnvStoreObj.GetBoolStoreEnvVariable(constants.EnvKeyDisableEmailVerification) {
|
||||||
|
hostname := utils.GetHost(gc)
|
||||||
user.EmailVerifiedAt = nil
|
user.EmailVerifiedAt = nil
|
||||||
hasEmailChanged = true
|
hasEmailChanged = true
|
||||||
// insert verification request
|
// insert verification request
|
||||||
|
_, nonceHash, err := utils.GenerateNonce()
|
||||||
|
if err != nil {
|
||||||
|
return res, err
|
||||||
|
}
|
||||||
verificationType := constants.VerificationTypeUpdateEmail
|
verificationType := constants.VerificationTypeUpdateEmail
|
||||||
verificationToken, err := token.CreateVerificationToken(newEmail, verificationType, hostname)
|
redirectURL := utils.GetAppURL(gc)
|
||||||
|
verificationToken, err := token.CreateVerificationToken(newEmail, verificationType, hostname, nonceHash, redirectURL)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Println(`error generating token`, err)
|
log.Println(`error generating token`, err)
|
||||||
}
|
}
|
||||||
@@ -131,14 +144,15 @@ func UpdateProfileResolver(ctx context.Context, params model.UpdateProfileInput)
|
|||||||
Identifier: verificationType,
|
Identifier: verificationType,
|
||||||
ExpiresAt: time.Now().Add(time.Minute * 30).Unix(),
|
ExpiresAt: time.Now().Add(time.Minute * 30).Unix(),
|
||||||
Email: newEmail,
|
Email: newEmail,
|
||||||
|
Nonce: nonceHash,
|
||||||
|
RedirectURI: redirectURL,
|
||||||
})
|
})
|
||||||
|
|
||||||
// exec it as go routin so that we can reduce the api latency
|
// exec it as go routin so that we can reduce the api latency
|
||||||
go func() {
|
go email.SendVerificationMail(newEmail, verificationToken, hostname)
|
||||||
email.SendVerificationMail(newEmail, verificationToken, hostname)
|
|
||||||
}()
|
|
||||||
}
|
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
_, err = db.Provider.UpdateUser(user)
|
_, err = db.Provider.UpdateUser(user)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Println("error updating user:", err)
|
log.Println("error updating user:", err)
|
||||||
|
@@ -8,7 +8,6 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/authorizerdev/authorizer/server/constants"
|
"github.com/authorizerdev/authorizer/server/constants"
|
||||||
"github.com/authorizerdev/authorizer/server/cookie"
|
|
||||||
"github.com/authorizerdev/authorizer/server/db"
|
"github.com/authorizerdev/authorizer/server/db"
|
||||||
"github.com/authorizerdev/authorizer/server/db/models"
|
"github.com/authorizerdev/authorizer/server/db/models"
|
||||||
"github.com/authorizerdev/authorizer/server/email"
|
"github.com/authorizerdev/authorizer/server/email"
|
||||||
@@ -95,15 +94,20 @@ func UpdateUserResolver(ctx context.Context, params model.UpdateUserInput) (*mod
|
|||||||
return res, fmt.Errorf("user with this email address already exists")
|
return res, fmt.Errorf("user with this email address already exists")
|
||||||
}
|
}
|
||||||
|
|
||||||
sessionstore.DeleteAllUserSession(fmt.Sprintf("%v", user.ID))
|
// TODO figure out how to do this
|
||||||
cookie.DeleteCookie(gc)
|
go sessionstore.DeleteAllUserSession(user.ID)
|
||||||
|
|
||||||
hostname := utils.GetHost(gc)
|
hostname := utils.GetHost(gc)
|
||||||
user.Email = newEmail
|
user.Email = newEmail
|
||||||
user.EmailVerifiedAt = nil
|
user.EmailVerifiedAt = nil
|
||||||
// insert verification request
|
// insert verification request
|
||||||
|
_, nonceHash, err := utils.GenerateNonce()
|
||||||
|
if err != nil {
|
||||||
|
return res, err
|
||||||
|
}
|
||||||
verificationType := constants.VerificationTypeUpdateEmail
|
verificationType := constants.VerificationTypeUpdateEmail
|
||||||
verificationToken, err := token.CreateVerificationToken(newEmail, verificationType, hostname)
|
redirectURL := utils.GetAppURL(gc)
|
||||||
|
verificationToken, err := token.CreateVerificationToken(newEmail, verificationType, hostname, nonceHash, redirectURL)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Println(`error generating token`, err)
|
log.Println(`error generating token`, err)
|
||||||
}
|
}
|
||||||
@@ -112,12 +116,13 @@ func UpdateUserResolver(ctx context.Context, params model.UpdateUserInput) (*mod
|
|||||||
Identifier: verificationType,
|
Identifier: verificationType,
|
||||||
ExpiresAt: time.Now().Add(time.Minute * 30).Unix(),
|
ExpiresAt: time.Now().Add(time.Minute * 30).Unix(),
|
||||||
Email: newEmail,
|
Email: newEmail,
|
||||||
|
Nonce: nonceHash,
|
||||||
|
RedirectURI: redirectURL,
|
||||||
})
|
})
|
||||||
|
|
||||||
// exec it as go routin so that we can reduce the api latency
|
// exec it as go routin so that we can reduce the api latency
|
||||||
go func() {
|
go email.SendVerificationMail(newEmail, verificationToken, hostname)
|
||||||
email.SendVerificationMail(newEmail, verificationToken, hostname)
|
|
||||||
}()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
rolesToSave := ""
|
rolesToSave := ""
|
||||||
@@ -128,7 +133,7 @@ func UpdateUserResolver(ctx context.Context, params model.UpdateUserInput) (*mod
|
|||||||
inputRoles = append(inputRoles, *item)
|
inputRoles = append(inputRoles, *item)
|
||||||
}
|
}
|
||||||
|
|
||||||
if !utils.IsValidRoles(inputRoles, append([]string{}, append(envstore.EnvInMemoryStoreObj.GetSliceStoreEnvVariable(constants.EnvKeyRoles), envstore.EnvInMemoryStoreObj.GetSliceStoreEnvVariable(constants.EnvKeyProtectedRoles)...)...)) {
|
if !utils.IsValidRoles(inputRoles, append([]string{}, append(envstore.EnvStoreObj.GetSliceStoreEnvVariable(constants.EnvKeyRoles), envstore.EnvStoreObj.GetSliceStoreEnvVariable(constants.EnvKeyProtectedRoles)...)...)) {
|
||||||
return res, fmt.Errorf("invalid list of roles")
|
return res, fmt.Errorf("invalid list of roles")
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -136,8 +141,7 @@ func UpdateUserResolver(ctx context.Context, params model.UpdateUserInput) (*mod
|
|||||||
rolesToSave = strings.Join(inputRoles, ",")
|
rolesToSave = strings.Join(inputRoles, ",")
|
||||||
}
|
}
|
||||||
|
|
||||||
sessionstore.DeleteAllUserSession(fmt.Sprintf("%v", user.ID))
|
go sessionstore.DeleteAllUserSession(user.ID)
|
||||||
cookie.DeleteCookie(gc)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if rolesToSave != "" {
|
if rolesToSave != "" {
|
||||||
@@ -150,6 +154,8 @@ func UpdateUserResolver(ctx context.Context, params model.UpdateUserInput) (*mod
|
|||||||
return res, err
|
return res, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
createdAt := user.CreatedAt
|
||||||
|
updatedAt := user.UpdatedAt
|
||||||
res = &model.User{
|
res = &model.User{
|
||||||
ID: params.ID,
|
ID: params.ID,
|
||||||
Email: user.Email,
|
Email: user.Email,
|
||||||
@@ -157,8 +163,8 @@ func UpdateUserResolver(ctx context.Context, params model.UpdateUserInput) (*mod
|
|||||||
GivenName: user.GivenName,
|
GivenName: user.GivenName,
|
||||||
FamilyName: user.FamilyName,
|
FamilyName: user.FamilyName,
|
||||||
Roles: strings.Split(user.Roles, ","),
|
Roles: strings.Split(user.Roles, ","),
|
||||||
CreatedAt: &user.CreatedAt,
|
CreatedAt: &createdAt,
|
||||||
UpdatedAt: &user.UpdatedAt,
|
UpdatedAt: &updatedAt,
|
||||||
}
|
}
|
||||||
return res, nil
|
return res, nil
|
||||||
}
|
}
|
||||||
|
86
server/resolvers/validate_jwt_token.go
Normal file
86
server/resolvers/validate_jwt_token.go
Normal file
@@ -0,0 +1,86 @@
|
|||||||
|
package resolvers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/authorizerdev/authorizer/server/graph/model"
|
||||||
|
"github.com/authorizerdev/authorizer/server/sessionstore"
|
||||||
|
"github.com/authorizerdev/authorizer/server/token"
|
||||||
|
"github.com/authorizerdev/authorizer/server/utils"
|
||||||
|
"github.com/golang-jwt/jwt"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ValidateJwtTokenResolver is used to validate a jwt token without its rotation
|
||||||
|
// this can be used at API level (backend)
|
||||||
|
// it can validate:
|
||||||
|
// access_token
|
||||||
|
// id_token
|
||||||
|
// refresh_token
|
||||||
|
func ValidateJwtTokenResolver(ctx context.Context, params model.ValidateJWTTokenInput) (*model.ValidateJWTTokenResponse, error) {
|
||||||
|
gc, err := utils.GinContextFromContext(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
tokenType := params.TokenType
|
||||||
|
if tokenType != "access_token" && tokenType != "refresh_token" && tokenType != "id_token" {
|
||||||
|
return nil, errors.New("invalid token type")
|
||||||
|
}
|
||||||
|
|
||||||
|
userID := ""
|
||||||
|
nonce := ""
|
||||||
|
// access_token and refresh_token should be validated from session store as well
|
||||||
|
if tokenType == "access_token" || tokenType == "refresh_token" {
|
||||||
|
savedSession := sessionstore.GetState(params.Token)
|
||||||
|
if savedSession == "" {
|
||||||
|
return &model.ValidateJWTTokenResponse{
|
||||||
|
IsValid: false,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
savedSessionSplit := strings.Split(savedSession, "@")
|
||||||
|
nonce = savedSessionSplit[0]
|
||||||
|
userID = savedSessionSplit[1]
|
||||||
|
}
|
||||||
|
|
||||||
|
hostname := utils.GetHost(gc)
|
||||||
|
var claimRoles []string
|
||||||
|
var claims jwt.MapClaims
|
||||||
|
|
||||||
|
// we cannot validate sub and nonce in case of id_token as that token is not persisted in session store
|
||||||
|
if userID != "" && nonce != "" {
|
||||||
|
claims, err = token.ParseJWTToken(params.Token, hostname, nonce, userID)
|
||||||
|
if err != nil {
|
||||||
|
return &model.ValidateJWTTokenResponse{
|
||||||
|
IsValid: false,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
claims, err = token.ParseJWTTokenWithoutNonce(params.Token, hostname)
|
||||||
|
if err != nil {
|
||||||
|
return &model.ValidateJWTTokenResponse{
|
||||||
|
IsValid: false,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
claimRolesInterface := claims["roles"]
|
||||||
|
roleSlice := utils.ConvertInterfaceToSlice(claimRolesInterface)
|
||||||
|
for _, v := range roleSlice {
|
||||||
|
claimRoles = append(claimRoles, v.(string))
|
||||||
|
}
|
||||||
|
|
||||||
|
if params.Roles != nil && len(params.Roles) > 0 {
|
||||||
|
for _, v := range params.Roles {
|
||||||
|
if !utils.StringSliceContains(claimRoles, v) {
|
||||||
|
return nil, fmt.Errorf(`unauthorized`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return &model.ValidateJWTTokenResponse{
|
||||||
|
IsValid: true,
|
||||||
|
}, nil
|
||||||
|
}
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user