Compare commits

..

119 Commits

Author SHA1 Message Date
Lakhan Samani
c6019e650b fix: add manual code generation 2022-10-20 15:35:26 +05:30
Lakhan Samani
b2e0a3371f fix: revert nonce 2022-10-20 00:14:06 +05:30
Lakhan Samani
a68876a6f4 fix: comment 2022-10-19 23:55:47 +05:30
Lakhan Samani
2c867b0314 fix: issuer token endpoint 2022-10-19 23:41:08 +05:30
Lakhan Samani
74b858ac24 fix: binding 2022-10-19 23:39:48 +05:30
Lakhan Samani
fedc3173fe fix: get nonce from query request if possible 2022-10-19 23:36:33 +05:30
Lakhan Samani
de4381261e fix: add nonce to supported claims 2022-10-19 23:17:13 +05:30
Lakhan Samani
a916b8c32c fix: add nonce 2022-10-19 19:04:15 +05:30
Lakhan Samani
89f08b6d31 fix: redirect from app 2022-10-19 12:20:22 +05:30
Lakhan Samani
cc23784df8 fix: add code to login query params 2022-10-19 12:01:34 +05:30
Lakhan Samani
7ff3b3018a fix: add code to query params 2022-10-19 11:29:49 +05:30
Lakhan Samani
2b52932e98 fix: add code to other response methods 2022-10-19 09:03:00 +05:30
Lakhan Samani
c716638725 fix(server): revert the state & code_challenge validation 2022-10-18 23:24:19 +05:30
Lakhan Samani
252cd1fa2d fix: make code_challenge optional 2022-10-18 23:14:24 +05:30
Lakhan Samani
7c2693b086 fix: form post template 2022-10-18 23:03:55 +05:30
Lakhan Samani
eaa10ec5bc fix: error detection 2022-10-18 22:34:57 +05:30
Lakhan Samani
253128ca0c fix: query params for code response 2022-10-18 22:00:54 +05:30
Lakhan Samani
cddfe1e088 fix: response 2022-10-18 21:46:37 +05:30
Lakhan Samani
8e655bcb5b fix: authorize response 2022-10-18 21:29:09 +05:30
Lakhan Samani
9a411e673c fix: reponse 2022-10-18 21:08:53 +05:30
Lakhan Samani
346c8e5a47 fix: handle response 2022-10-16 22:16:37 +05:30
Lakhan Samani
3cd99fe5f6 fix: open id config 2022-10-16 21:03:37 +05:30
Lakhan Samani
2bd92d6028 feat: add form_post method 2022-10-16 20:46:54 +05:30
Lakhan Samani
ff805e3ef2 fix: add comments 2022-10-12 13:10:24 +05:30
Lakhan Samani
0115128ee7 fix(server): authorizer as oauth provider 2022-10-09 19:48:13 +05:30
Lakhan Samani
d8eceadd7f Merge pull request #267 from authorizerdev/fix/sqlserver-text-type
fix(server): text type for sql server 2019
2022-10-08 15:38:32 +05:30
Lakhan Samani
e6c4fdff26 fix(server): text type for sql server 2019
Resolves #266
2022-10-07 10:13:20 +05:30
Lakhan Samani
e760a5598e Merge pull request #260 from authorizerdev/chore/add-prettier-app
chore(app): add prettier
2022-10-02 22:40:16 +05:30
Lakhan Samani
f62a22619b chore(app): add prettier 2022-10-02 22:39:47 +05:30
Lakhan Samani
c32a7fa1e4 Merge pull request #259 from authorizerdev/chore/add-prettier-dashboard
chore(dashboard): add prettier
2022-10-02 22:38:06 +05:30
Lakhan Samani
399b97079d chore(dashboard): add prettier 2022-10-02 22:37:20 +05:30
Lakhan Samani
fe687cb0ca Merge pull request #258 from authorizerdev/fix/cookie-security-features
feat(dashboard): allow setting admin / app cookie security
2022-10-02 22:03:43 +05:30
Lakhan Samani
9cb011e921 feat(dashboard): allow setting admin / app cookie security
Fixes #233
2022-10-02 22:01:22 +05:30
Lakhan Samani
4e1bba2ba8 Merge pull request #246 from jerebtw/main
feat: add app & admin cookie secure variable to dashboard
2022-10-02 21:36:36 +05:30
ruessej
f1509f90f0 feat: Update generated and models_gen 2022-10-01 18:12:49 +02:00
Jerebtw
bd4d48c7c5 fix: schema.graphqls 2022-10-01 17:57:23 +02:00
Jerebtw
0e3242372b feat: add app & admin cookie secure variable to dashboard
Todo: Generate graphql (i don't work on my PC (Windows))
2022-10-01 17:48:05 +02:00
Lakhan Samani
89cea39c41 Merge pull request #257 from authorizerdev/chore/update-go-gin-1.18.1
chore: update go-gin server to 1.18.1
2022-10-01 17:42:00 +05:30
Lakhan Samani
570a0b9531 chore: update go-gin server to 1.18.1 2022-10-01 17:41:16 +05:30
Lakhan Samani
686b3a4666 Merge pull request #255 from authorizerdev/chore/update-go-1.19.1
chore: update golang to 1.19.1
2022-10-01 17:20:48 +05:30
Lakhan Samani
b266a14108 chore: update golang to 1.19.1 2022-10-01 15:17:11 +05:30
Lakhan Samani
e5972a0dee Merge pull request #254 from authorizerdev/feat/update-gqlgen-0.17.20
chore: update gqlgen to 0.17.20
2022-10-01 15:15:13 +05:30
Lakhan Samani
6f46f1e6ef chore: update gqlgen to 0.17.20 2022-09-30 15:37:59 +05:30
Lakhan Samani
cfbce17ab8 fix: set same site cookie to none for cross site 2022-09-28 18:42:42 +05:30
Lakhan Samani
aa6601e62c fix: same site cookie 2022-09-28 18:30:30 +05:30
Lakhan Samani
d8ea0c656f Merge pull request #247 from authorizerdev/fix/same-site-cookie
fix(server): use sameSite as lax by default for app cookie
2022-09-28 11:18:03 +05:30
Lakhan Samani
f5323e0eec fix(server): update comments for host & cookies 2022-09-28 10:36:56 +05:30
Lakhan Samani
b1bc7b5370 fix(server): set default app cookie to lax mode 2022-09-28 09:51:04 +05:30
Lakhan Samani
536fd87c3c fix: debug log 2022-09-27 06:45:38 +05:30
Lakhan Samani
f8c96a9fee Merge pull request #244 from authorizerdev/fix/remove-user-verification-request-when-deleted
fix: remove entries from otp + verification when user is deleted
2022-09-27 06:44:22 +05:30
Lakhan Samani
837fc781de fix: remove entries from otp + verification when user is deleted
Resolves #234
2022-09-27 00:27:36 +05:30
Lakhan Samani
640bb8c9ed chore: bump app/authorizer-react 1.1.2 2022-09-27 00:07:52 +05:30
Lakhan Samani
d9bba0bbe7 Merge pull request #243 from authorizerdev/fix/bool-env-vars-secure-cookie
fix: app & admin cookie secure variable type while persisting info
2022-09-27 00:03:31 +05:30
Lakhan Samani
f91ec1880f fix: app & admin cookie secure variable type while persisting info
Resolves #241
2022-09-27 00:01:38 +05:30
Lakhan Samani
19e2153379 Update README.md 2022-09-15 12:24:47 +05:30
Lakhan Samani
221009bf0a Merge pull request #229 from ruessej/main
feat: Add a option to disable httpOnly cookies
2022-09-15 11:22:27 +05:30
ruessej
6085c2d535 Fix incorrect type 2022-09-14 12:24:19 +02:00
Jerebtw
8e0c5e4380 Make the default value true 2022-09-14 11:56:48 +02:00
Lakhan Samani
21b70e4b26 Merge pull request #230 from authorizerdev/fix/github-oauth-scopes
fix: scope for github auth
2022-09-14 11:46:46 +05:30
Lakhan Samani
993693884d fix: scope for github auth 2022-09-14 11:45:38 +05:30
Lakhan Samani
ed849fa6f6 Merge branch 'main' of https://github.com/authorizerdev/authorizer 2022-09-14 10:44:09 +05:30
Lakhan Samani
aec1f5df53 fix: github endpoint to get user emails 2022-09-14 10:44:01 +05:30
ruessej
195bd1bc6a Add a option to disable httpOnly cookies 2022-09-12 14:37:42 +02:00
Lakhan Samani
45b4c41bca Merge pull request #228 from Deep-Codes/main 2022-09-10 11:40:11 +05:30
Deepankar
63d486821e fix: lint 2022-09-10 11:39:01 +05:30
Deep-Codes
4b56afdc98 fix(type): __authorizer__ on window 2022-09-10 11:23:20 +05:30
Lakhan Samani
6455ff956a fix: remove varible log 2022-09-10 10:52:56 +05:30
Lakhan Samani
3898e43fff feat: add button to jwt config as json 2022-09-10 10:50:15 +05:30
Lakhan Samani
2c305e5bde Update README.md 2022-09-09 10:24:30 +05:30
Lakhan Samani
b8fd08e576 Update README.md 2022-09-09 09:29:27 +05:30
Lakhan Samani
6dafa45051 fix: invalid login message
Resolves #224
2022-09-03 21:48:33 +05:30
Lakhan Samani
ead3514113 chore: update railway template 2022-08-31 13:09:00 +05:30
Lakhan Samani
75a413e5f2 Merge branch 'main' of https://github.com/authorizerdev/authorizer 2022-08-31 11:02:50 +05:30
Lakhan Samani
91bf0e2478 fix: use replace all 2022-08-31 11:02:46 +05:30
Lakhan Samani
7a1305cf96 Merge pull request #222 from Deep-Codes/main 2022-08-31 07:04:20 +05:30
Deep-Codes
ff5a6ec301 feat(server): add log to show PORT 2022-08-30 23:35:43 +05:30
Lakhan Samani
b7b97b4f8d Merge pull request #221 from Deep-Codes/main
fix(dashboard): users table overflow
2022-08-30 22:38:49 +05:30
Deep-Codes
d9bc989c74 fix(dashboard): users table overflow 2022-08-30 21:56:28 +05:30
Lakhan Samani
d1f80d4088 feat: add support for twitter login 2022-08-29 08:37:53 +05:30
Lakhan Samani
4b299f0da2 fix: log 2022-08-29 08:19:11 +05:30
Lakhan Samani
ed8006db4c Merge branch 'main' of https://github.com/authorizerdev/authorizer 2022-08-29 08:18:42 +05:30
Lakhan Samani
97f6c7d50a fix: authorize endpoint setting user session 2022-08-29 08:18:20 +05:30
Lakhan Samani
5e3f68a180 Merge pull request #216 from szczepad/feat/twitter-login
Feat/twitter login
2022-08-24 08:53:52 +05:30
szczepad
f73d1fc588 feat: Adds login via twitter 2022-08-22 09:25:10 +02:00
szczepad
aa232de426 fix: Uses whitespace as seperator for oauth scopes in state-string
This is necessary, as the previous delimiter (,) was being redacted
after a redirect. This resulted in the scopes not being correctly
parseable and the state not being fetched correctly after the
oauth-callback
2022-08-22 09:25:10 +02:00
Lakhan Samani
34ce754ef6 feat: bootstrap twitter login config 2022-08-22 09:03:29 +02:00
Lakhan Samani
5f385b2016 fix: remove unused file 2022-08-18 07:21:50 +05:30
Lakhan Samani
da7c17271e Merge pull request #215 from wabscale/main
fix: rootless container
2022-08-18 06:11:32 +05:30
John McCann Cunniff Jr
69fbd631ff fix: rootless container 2022-08-17 20:33:05 -04:00
Lakhan Samani
deb209e358 Update README.md 2022-08-15 22:28:43 +05:30
Lakhan Samani
ea6b4cbc8d Update README.md 2022-08-15 22:28:13 +05:30
Lakhan Samani
2f21a09b2e chore: bump app/authorizer-react 1.0.0 2022-08-15 21:06:57 +05:30
Lakhan Samani
4ab775f2c1 fix: apple & linkedin env config 2022-08-13 12:37:04 +05:30
Lakhan Samani
b6e8023104 Merge pull request #211 from authorizerdev/fix/email-template
fix email template
2022-08-13 11:58:07 +05:30
Lakhan Samani
4f1597e5d2 fix: update note on features 2022-08-13 11:57:03 +05:30
Lakhan Samani
4f81d1969e fix email template
- fix verification types
- add design to cassandra db provider for email_template
- fix default email verification types to include update_email
2022-08-13 11:34:24 +05:30
Lakhan Samani
ad3e615ac7 Merge pull request #210 from authorizerdev/fix/dashboard-ui
Fix/dashboard UI
2022-08-13 03:57:19 +05:30
anik-ghosh-au7
e9a2301d2b feat: [dashboard] add env options for multi factor auth 2022-08-11 17:50:45 +05:30
anik-ghosh-au7
48bbfa31af fix: template editor design 2022-08-11 17:08:23 +05:30
anik-ghosh-au7
d7f5f563cc fix: add design to email template 2022-08-11 16:45:59 +05:30
anik-ghosh-au7
6c29149fbe fix: email template editor 2022-08-11 15:08:50 +05:30
Lakhan Samani
bbd4d43317 fix: add padding to editor 2022-08-09 12:10:50 +05:30
Lakhan Samani
c4d2f62657 fix: clear form on close 2022-08-09 11:55:55 +05:30
Lakhan Samani
5d78bf178f fix: email template info 2022-08-09 11:41:51 +05:30
Lakhan Samani
58749497bd fix: payload example for webhook 2022-08-09 10:04:06 +05:30
Lakhan Samani
5c6e643efb Merge pull request #209 from authorizerdev/feat/send-email-based-on-template
feat: send email based on template
2022-08-09 09:17:29 +05:30
Lakhan Samani
7792cdbc5e fix: template respone & ui 2022-08-09 09:07:47 +05:30
Lakhan Samani
65803c3763 fix: remove todos 2022-08-09 01:53:21 +05:30
Lakhan Samani
81fce1a471 feat: send email based on template 2022-08-09 01:43:37 +05:30
Lakhan Samani
0714b4360b Merge pull request #206 from authorizerdev/feat/2fa
feat: add mutifactor authentication
2022-08-07 11:11:56 +05:30
Lakhan Samani
8f69d5746e Merge pull request #207 from authorizerdev/feat/email-template-ui
feat: email template UI + subject
2022-08-07 11:10:44 +05:30
anik-ghosh-au7
85630a59c1 feat: add webhook payload example 2022-08-02 00:56:21 +05:30
anik-ghosh-au7
b4ef196bfb fix: update email template variables 2022-08-01 14:07:06 +05:30
anik-ghosh-au7
099b2a39b4 feat: add delete email template modal 2022-07-30 22:47:00 +05:30
anik-ghosh-au7
2d07baedf4 feat: fix update email template editor 2022-07-30 20:28:36 +05:30
anik-ghosh-au7
8b34e001ef feat: fix update email template editor 2022-07-30 20:15:49 +05:30
anik-ghosh-au7
617dcdde53 feat: fix update email template modal 2022-07-30 18:43:02 +05:30
anik-ghosh-au7
f2fb800323 feat: dashboard add email-template page 2022-07-30 16:05:35 +05:30
Lakhan Samani
db4d711cba feat: add subject to email template 2022-07-29 16:15:57 +05:30
111 changed files with 13145 additions and 4660 deletions

View File

@@ -2,17 +2,17 @@ on:
workflow_dispatch:
inputs:
logLevel:
description: 'Log level'
description: 'Log level'
required: true
default: 'warning'
default: 'warning'
type: choice
options:
- info
- warning
- debug
- debug
tags:
description: 'Tags'
required: false
required: false
type: boolean
release:
types: [created]
@@ -28,7 +28,7 @@ jobs:
node-version: '16'
- uses: actions/setup-go@v2
with:
go-version: '^1.17.3'
go-version: '^1.19.1'
- name: Install dependencies
run: |
sudo apt-get install build-essential wget zip gcc-mingw-w64 && \

View File

@@ -1,4 +1,4 @@
FROM golang:1.17-alpine as go-builder
FROM golang:1.19.1-alpine as go-builder
WORKDIR /authorizer
COPY server server
COPY Makefile .
@@ -21,13 +21,15 @@ RUN apk add build-base &&\
make build-dashboard
FROM alpine:latest
WORKDIR /root/
RUN adduser -D -h /authorizer -u 1000 -k /dev/null authorizer
WORKDIR /authorizer
RUN mkdir app dashboard
COPY --from=node-builder /authorizer/app/build app/build
COPY --from=node-builder /authorizer/app/favicon_io app/favicon_io
COPY --from=node-builder /authorizer/dashboard/build dashboard/build
COPY --from=node-builder /authorizer/dashboard/favicon_io dashboard/favicon_io
COPY --from=go-builder /authorizer/build build
COPY --from=node-builder --chown=nobody:nobody /authorizer/app/build app/build
COPY --from=node-builder --chown=nobody:nobody /authorizer/app/favicon_io app/favicon_io
COPY --from=node-builder --chown=nobody:nobody /authorizer/dashboard/build dashboard/build
COPY --from=node-builder --chown=nobody:nobody /authorizer/dashboard/favicon_io dashboard/favicon_io
COPY --from=go-builder --chown=nobody:nobody /authorizer/build build
COPY templates templates
EXPOSE 8080
USER authorizer
CMD [ "./build/server" ]

View File

@@ -33,5 +33,4 @@ test-all-db:
docker rm -vf authorizer_mongodb_db
docker rm -vf authorizer_arangodb
generate:
cd server && go get github.com/99designs/gqlgen/cmd@v0.14.0 && go run github.com/99designs/gqlgen generate
cd server && go run github.com/99designs/gqlgen generate && go mod tidy

View File

@@ -7,19 +7,17 @@
Authorizer
</h1>
**Authorizer** is an open-source authentication and authorization solution for your applications. Bring your database and have complete control over the user information. You can self-host authorizer instances and connect to any database (Currently supports [Postgres](https://www.postgresql.org/), [MySQL](https://www.mysql.com/), [SQLite](https://www.sqlite.org/index.html), [SQLServer](https://www.microsoft.com/en-us/sql-server/), [MongoDB](https://mongodb.com/), [ArangoDB](https://www.arangodb.com/)).
**Authorizer** is an open-source authentication and authorization solution for your applications. Bring your database and have complete control over the user information. You can self-host authorizer instances and connect to any database (Currently supports 11+ databases including [Postgres](https://www.postgresql.org/), [MySQL](https://www.mysql.com/), [SQLite](https://www.sqlite.org/index.html), [SQLServer](https://www.microsoft.com/en-us/sql-server/), [YugaByte](https://www.yugabyte.com/), [MariaDB](https://mariadb.org/), [PlanetScale](https://planetscale.com/), [CassandraDB](https://cassandra.apache.org/_/index.html), [ScyllaDB](https://www.scylladb.com/), [MongoDB](https://mongodb.com/), [ArangoDB](https://www.arangodb.com/)).
## Table of contents
For more information check:
- [Introduction](#introduction)
- [Getting Started](#getting-started)
- [Contributing](https://github.com/authorizerdev/authorizer/blob/main/.github/CONTRIBUTING.md)
- [Docs](http://docs.authorizer.dev/)
- [Join Community](https://discord.gg/Zv2D5h6kkK)
- [Discord Community](https://discord.gg/Zv2D5h6kkK)
- [Contributing Guide](https://github.com/authorizerdev/authorizer/blob/main/.github/CONTRIBUTING.md)
# Introduction
<img src="https://github.com/authorizerdev/authorizer/blob/main/assets/authorizer-architecture.png" style="height:20em"/>
<img src="https://docs.authorizer.dev/images/authorizer-arch.png" style="height:20em"/>
#### We offer the following functionality
@@ -29,20 +27,22 @@
- ✅ OAuth2 and OpenID compatible APIs
- ✅ APIs to update profile securely
- ✅ Forgot password flow using email
- ✅ Social logins (Google, Github, Facebook, more coming soon)
- ✅ Social logins (Google, Github, Facebook, LinkedIn, Apple more coming soon)
- ✅ Role-based access management
- ✅ Password-less login with magic link login
- ✅ Multi factor authentication
- ✅ Email templating
- ✅ Webhooks
## Roadmap
- 2 Factor authentication
- VueJS SDK
- Svelte SDK
- [VueJS SDK](https://github.com/authorizerdev/authorizer-vue)
- [Svelte SDK](https://github.com/authorizerdev/authorizer-svelte)
- [Golang SDK](https://github.com/authorizerdev/authorizer-go)
- React Native SDK
- Flutter SDK
- Android Native SDK
- iOS native SDK
- Golang SDK
- Python SDK
- PHP SDK
- WordPress plugin
@@ -63,11 +63,11 @@
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&amp;plugins=postgresql,redis"><img src="https://railway.app/button.svg" style="height: 44px" alt="Deploy on Railway"></a> | [docs](https://docs.authorizer.dev/deployment/railway) |
| Heroku | <a href="https://heroku.com/deploy?template=https://github.com/authorizerdev/authorizer-heroku"><img src="https://www.herokucdn.com/deploy/button.svg" alt="Deploy to Heroku" style="height: 44px;"></a> | [docs](https://docs.authorizer.dev/deployment/heroku) |
| Render | [![Deploy to Render](https://render.com/images/deploy-to-render-button.svg)](https://render.com/deploy?repo=https://github.com/authorizerdev/authorizer-render) | [docs](https://docs.authorizer.dev/deployment/render) |
| **Infra provider** | **One-click link** | **Additional information** |
| :----------------: | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :----------------------------------------------------: |
| Railway.app | <a href="https://railway.app/new/template/nwXp1C?referralCode=FEF4uT"><img src="https://railway.app/button.svg" style="height: 44px" alt="Deploy on Railway"></a> | [docs](https://docs.authorizer.dev/deployment/railway) |
| Heroku | <a href="https://heroku.com/deploy?template=https://github.com/authorizerdev/authorizer-heroku"><img src="https://www.herokucdn.com/deploy/button.svg" alt="Deploy to Heroku" style="height: 44px;"></a> | [docs](https://docs.authorizer.dev/deployment/heroku) |
| Render | [![Deploy to Render](https://render.com/images/deploy-to-render-button.svg)](https://render.com/deploy?repo=https://github.com/authorizerdev/authorizer-render) | [docs](https://docs.authorizer.dev/deployment/render) |
### Deploy Authorizer Using Source Code

6
app/.prettierrc.json Normal file
View File

@@ -0,0 +1,6 @@
{
"tabWidth": 2,
"singleQuote": true,
"trailingComma": "all",
"useTabs": false
}

86
app/package-lock.json generated
View File

@@ -9,7 +9,7 @@
"version": "1.0.0",
"license": "ISC",
"dependencies": {
"@authorizerdev/authorizer-react": "^0.26.0-beta.0",
"@authorizerdev/authorizer-react": "^1.1.2",
"@types/react": "^17.0.15",
"@types/react-dom": "^17.0.9",
"esbuild": "^0.12.17",
@@ -22,26 +22,27 @@
},
"devDependencies": {
"@types/react-router-dom": "^5.1.8",
"@types/styled-components": "^5.1.11"
"@types/styled-components": "^5.1.11",
"prettier": "2.7.1"
}
},
"node_modules/@authorizerdev/authorizer-js": {
"version": "0.17.0-beta.1",
"resolved": "https://registry.npmjs.org/@authorizerdev/authorizer-js/-/authorizer-js-0.17.0-beta.1.tgz",
"integrity": "sha512-jUlFUrs4Ys6LZ5hclPeRt84teygi+bA57d/IpV9GAqOrfifv70jkFeDln4+Bs0mZk74el23Xn+DR9380mqE4Cg==",
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/@authorizerdev/authorizer-js/-/authorizer-js-1.1.0.tgz",
"integrity": "sha512-MdEw1SjhIm7pXq20AscHSbnAta2PC3w7GNBY52/OzmlBXUGH3ooUQX/aszbYOse3FlhapcrGrRvg4sNM7faGAg==",
"dependencies": {
"node-fetch": "^2.6.1"
"cross-fetch": "^3.1.5"
},
"engines": {
"node": ">=10"
}
},
"node_modules/@authorizerdev/authorizer-react": {
"version": "0.26.0-beta.0",
"resolved": "https://registry.npmjs.org/@authorizerdev/authorizer-react/-/authorizer-react-0.26.0-beta.0.tgz",
"integrity": "sha512-YfyiGYBmbsp3tLWIxOrOZ/hUTCmdMXVE9SLE8m1xsFsxzJJlUhepp0AMahSbH5EyLj5bchOhOw/rzgpnDZDvMw==",
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/@authorizerdev/authorizer-react/-/authorizer-react-1.1.2.tgz",
"integrity": "sha512-uBmuKnOVX8gp8CEUuGJuz04ep+8qMEzJXWd5leEGKYMIgolHpu/lOinnMUXhjh8YL3pA4+EhvB+hQXxUX+rRHQ==",
"dependencies": {
"@authorizerdev/authorizer-js": "^0.17.0-beta.1",
"@authorizerdev/authorizer-js": "^1.1.0",
"final-form": "^4.20.2",
"react-final-form": "^6.5.3",
"styled-components": "^5.3.0"
@@ -404,6 +405,14 @@
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
"integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU="
},
"node_modules/cross-fetch": {
"version": "3.1.5",
"resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-3.1.5.tgz",
"integrity": "sha512-lvb1SBsI0Z7GDwmuid+mU3kWVBwTVUbe7S0H52yaaAdQOXq2YktTCZdlAcNKFzE6QtRz0snpw9bNiPeOIkkQvw==",
"dependencies": {
"node-fetch": "2.6.7"
}
},
"node_modules/css-color-keywords": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/css-color-keywords/-/css-color-keywords-1.0.0.tgz",
@@ -461,9 +470,9 @@
}
},
"node_modules/final-form": {
"version": "4.20.6",
"resolved": "https://registry.npmjs.org/final-form/-/final-form-4.20.6.tgz",
"integrity": "sha512-fCdwIj49KOaFfDRlXB57Eo+GghIMZQWrA9TakQI3C9uQxHwaFHXqZSNRlUdfnQmNNeySwGOaGPZCvjy58hyv4w==",
"version": "4.20.4",
"resolved": "https://registry.npmjs.org/final-form/-/final-form-4.20.4.tgz",
"integrity": "sha512-hyoOVVilPLpkTvgi+FSJkFZrh0Yhy4BhE6lk/NiBwrF4aRV8/ykKEyXYvQH/pfUbRkOosvpESYouFb+FscsLrw==",
"dependencies": {
"@babel/runtime": "^7.10.0"
},
@@ -609,6 +618,21 @@
"resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz",
"integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ=="
},
"node_modules/prettier": {
"version": "2.7.1",
"resolved": "https://registry.npmjs.org/prettier/-/prettier-2.7.1.tgz",
"integrity": "sha512-ujppO+MkdPqoVINuDFDRLClm7D78qbDt0/NR+wp5FqEZOoTNAjPHWj17QRhu7geIHJfcNhRk1XVQmF8Bp3ye+g==",
"dev": true,
"bin": {
"prettier": "bin-prettier.js"
},
"engines": {
"node": ">=10.13.0"
},
"funding": {
"url": "https://github.com/prettier/prettier?sponsor=1"
}
},
"node_modules/prop-types": {
"version": "15.7.2",
"resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.7.2.tgz",
@@ -852,19 +876,19 @@
},
"dependencies": {
"@authorizerdev/authorizer-js": {
"version": "0.17.0-beta.1",
"resolved": "https://registry.npmjs.org/@authorizerdev/authorizer-js/-/authorizer-js-0.17.0-beta.1.tgz",
"integrity": "sha512-jUlFUrs4Ys6LZ5hclPeRt84teygi+bA57d/IpV9GAqOrfifv70jkFeDln4+Bs0mZk74el23Xn+DR9380mqE4Cg==",
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/@authorizerdev/authorizer-js/-/authorizer-js-1.1.0.tgz",
"integrity": "sha512-MdEw1SjhIm7pXq20AscHSbnAta2PC3w7GNBY52/OzmlBXUGH3ooUQX/aszbYOse3FlhapcrGrRvg4sNM7faGAg==",
"requires": {
"node-fetch": "^2.6.1"
"cross-fetch": "^3.1.5"
}
},
"@authorizerdev/authorizer-react": {
"version": "0.26.0-beta.0",
"resolved": "https://registry.npmjs.org/@authorizerdev/authorizer-react/-/authorizer-react-0.26.0-beta.0.tgz",
"integrity": "sha512-YfyiGYBmbsp3tLWIxOrOZ/hUTCmdMXVE9SLE8m1xsFsxzJJlUhepp0AMahSbH5EyLj5bchOhOw/rzgpnDZDvMw==",
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/@authorizerdev/authorizer-react/-/authorizer-react-1.1.2.tgz",
"integrity": "sha512-uBmuKnOVX8gp8CEUuGJuz04ep+8qMEzJXWd5leEGKYMIgolHpu/lOinnMUXhjh8YL3pA4+EhvB+hQXxUX+rRHQ==",
"requires": {
"@authorizerdev/authorizer-js": "^0.17.0-beta.1",
"@authorizerdev/authorizer-js": "^1.1.0",
"final-form": "^4.20.2",
"react-final-form": "^6.5.3",
"styled-components": "^5.3.0"
@@ -1161,6 +1185,14 @@
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
"integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU="
},
"cross-fetch": {
"version": "3.1.5",
"resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-3.1.5.tgz",
"integrity": "sha512-lvb1SBsI0Z7GDwmuid+mU3kWVBwTVUbe7S0H52yaaAdQOXq2YktTCZdlAcNKFzE6QtRz0snpw9bNiPeOIkkQvw==",
"requires": {
"node-fetch": "2.6.7"
}
},
"css-color-keywords": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/css-color-keywords/-/css-color-keywords-1.0.0.tgz",
@@ -1200,9 +1232,9 @@
"integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ="
},
"final-form": {
"version": "4.20.6",
"resolved": "https://registry.npmjs.org/final-form/-/final-form-4.20.6.tgz",
"integrity": "sha512-fCdwIj49KOaFfDRlXB57Eo+GghIMZQWrA9TakQI3C9uQxHwaFHXqZSNRlUdfnQmNNeySwGOaGPZCvjy58hyv4w==",
"version": "4.20.4",
"resolved": "https://registry.npmjs.org/final-form/-/final-form-4.20.4.tgz",
"integrity": "sha512-hyoOVVilPLpkTvgi+FSJkFZrh0Yhy4BhE6lk/NiBwrF4aRV8/ykKEyXYvQH/pfUbRkOosvpESYouFb+FscsLrw==",
"requires": {
"@babel/runtime": "^7.10.0"
}
@@ -1313,6 +1345,12 @@
"resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz",
"integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ=="
},
"prettier": {
"version": "2.7.1",
"resolved": "https://registry.npmjs.org/prettier/-/prettier-2.7.1.tgz",
"integrity": "sha512-ujppO+MkdPqoVINuDFDRLClm7D78qbDt0/NR+wp5FqEZOoTNAjPHWj17QRhu7geIHJfcNhRk1XVQmF8Bp3ye+g==",
"dev": true
},
"prop-types": {
"version": "15.7.2",
"resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.7.2.tgz",

View File

@@ -5,13 +5,14 @@
"main": "index.js",
"scripts": {
"build": "rm -rf build && NODE_ENV=production node ./esbuild.config.js",
"start": "NODE_ENV=development node ./esbuild.config.js"
"start": "NODE_ENV=development node ./esbuild.config.js",
"format": "prettier --write --use-tabs 'src/**/*.(ts|tsx|js|jsx)'"
},
"keywords": [],
"author": "Lakhan Samani",
"license": "ISC",
"dependencies": {
"@authorizerdev/authorizer-react": "^0.26.0-beta.0",
"@authorizerdev/authorizer-react": "^1.1.2",
"@types/react": "^17.0.15",
"@types/react-dom": "^17.0.9",
"esbuild": "^0.12.17",
@@ -19,11 +20,12 @@
"react-dom": "^17.0.2",
"react-is": "^17.0.2",
"react-router-dom": "^5.2.0",
"typescript": "^4.3.5",
"styled-components": "^5.3.0"
"styled-components": "^5.3.0",
"typescript": "^4.3.5"
},
"devDependencies": {
"@types/react-router-dom": "^5.1.8",
"@types/styled-components": "^5.1.11"
"@types/styled-components": "^5.1.11",
"prettier": "2.7.1"
}
}

View File

@@ -4,6 +4,12 @@ import { AuthorizerProvider } from '@authorizerdev/authorizer-react';
import Root from './Root';
import { createRandomString } from './utils/common';
declare global {
interface Window {
__authorizer__: any;
}
}
export default function App() {
const searchParams = new URLSearchParams(window.location.search);
const state = searchParams.get('state') || createRandomString();
@@ -24,7 +30,6 @@ export default function App() {
urlProps.redirectURL = window.location.origin + '/app';
}
const globalState: Record<string, string> = {
// @ts-ignore
...window['__authorizer__'],
...urlProps,
};

View File

@@ -32,12 +32,14 @@ export default function Root({
const { token, loading, config } = useAuthorizer();
const searchParams = new URLSearchParams(
hasWindow() ? window.location.search : ``
hasWindow() ? window.location.search : ``,
);
const state = searchParams.get('state') || createRandomString();
const scope = searchParams.get('scope')
? searchParams.get('scope')?.toString().split(' ')
: ['openid', 'profile', 'email'];
const code = searchParams.get('code') || ''
const nonce = searchParams.get('nonce') || ''
const urlProps: Record<string, any> = {
state,
@@ -58,9 +60,19 @@ export default function Root({
if (token) {
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 (code !== '') {
params += `&code=${code}`
}
if (nonce !== '') {
params += `&nonce=${nonce}`
}
if (token.refresh_token) {
params += `&refresh_token=${token.refresh_token}`;
}
const url = new URL(redirectURL);
if (redirectURL.includes('?')) {
redirectURL = `${redirectURL}&${params}`;

View File

@@ -1,28 +1,28 @@
// colors: https://tailwindcss.com/docs/customizing-colors
export const theme = {
colors: {
primary: '#3B82F6',
primaryDisabled: '#60A5FA',
gray: '#D1D5DB',
danger: '#DC2626',
success: '#10B981',
textColor: '#374151',
},
fonts: {
// typography
fontStack: '-apple-system, system-ui, sans-serif',
colors: {
primary: '#3B82F6',
primaryDisabled: '#60A5FA',
gray: '#D1D5DB',
danger: '#DC2626',
success: '#10B981',
textColor: '#374151',
},
fonts: {
// typography
fontStack: '-apple-system, system-ui, sans-serif',
// font sizes
largeText: '18px',
mediumText: '14px',
smallText: '12px',
tinyText: '10px',
},
// font sizes
largeText: '18px',
mediumText: '14px',
smallText: '12px',
tinyText: '10px',
},
radius: {
card: '5px',
button: '5px',
input: '5px',
},
radius: {
card: '5px',
button: '5px',
input: '5px',
},
};

View File

@@ -8,7 +8,7 @@ export const createRandomString = () => {
'0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz-_~.';
let random = '';
const randomValues = Array.from(
getCrypto().getRandomValues(new Uint8Array(43))
getCrypto().getRandomValues(new Uint8Array(43)),
);
randomValues.forEach((v) => (random += charset[v % charset.length]));
return random;

View File

@@ -0,0 +1,6 @@
{
"tabWidth": 2,
"singleQuote": true,
"trailingComma": "all",
"useTabs": false
}

File diff suppressed because it is too large Load Diff

View File

@@ -5,7 +5,8 @@
"main": "index.js",
"scripts": {
"build": "rm -rf build && NODE_ENV=production node ./esbuild.config.js",
"start": "NODE_ENV=development node ./esbuild.config.js"
"start": "NODE_ENV=development node ./esbuild.config.js",
"format": "prettier --write --use-tabs 'src/**/*.(ts|tsx|js|jsx)'"
},
"keywords": [],
"author": "Lakhan Samani",
@@ -26,10 +27,16 @@
"lodash": "^4.17.21",
"react": "^17.0.2",
"react-dom": "^17.0.2",
"react-draft-wysiwyg": "^1.15.0",
"react-dropzone": "^12.0.4",
"react-email-editor": "^1.6.1",
"react-icons": "^4.3.1",
"react-router-dom": "^6.2.1",
"typescript": "^4.5.4",
"urql": "^2.0.6"
},
"devDependencies": {
"@types/react-email-editor": "^1.1.7",
"prettier": "2.7.1"
}
}

View File

@@ -0,0 +1,106 @@
import React from 'react';
import {
Button,
Center,
Flex,
MenuItem,
Modal,
ModalBody,
ModalCloseButton,
ModalContent,
ModalFooter,
ModalHeader,
ModalOverlay,
useDisclosure,
Text,
useToast,
} from '@chakra-ui/react';
import { useClient } from 'urql';
import { FaRegTrashAlt } from 'react-icons/fa';
import { DeleteEmailTemplate } from '../graphql/mutation';
import { capitalizeFirstLetter } from '../utils';
interface deleteEmailTemplateModalInputPropTypes {
emailTemplateId: string;
eventName: string;
fetchEmailTemplatesData: Function;
}
const DeleteEmailTemplateModal = ({
emailTemplateId,
eventName,
fetchEmailTemplatesData,
}: deleteEmailTemplateModalInputPropTypes) => {
const client = useClient();
const toast = useToast();
const { isOpen, onOpen, onClose } = useDisclosure();
const deleteHandler = async () => {
const res = await client
.mutation(DeleteEmailTemplate, { params: { id: emailTemplateId } })
.toPromise();
if (res.error) {
toast({
title: capitalizeFirstLetter(res.error.message),
isClosable: true,
status: 'error',
position: 'bottom-right',
});
return;
} else if (res.data?._delete_email_template) {
toast({
title: capitalizeFirstLetter(res.data?._delete_email_template.message),
isClosable: true,
status: 'success',
position: 'bottom-right',
});
}
onClose();
fetchEmailTemplatesData();
};
return (
<>
<MenuItem onClick={onOpen}>Delete</MenuItem>
<Modal isOpen={isOpen} onClose={onClose}>
<ModalOverlay />
<ModalContent>
<ModalHeader>Delete Email Template</ModalHeader>
<ModalCloseButton />
<ModalBody>
<Text fontSize="md">Are you sure?</Text>
<Flex
padding="5%"
marginTop="5%"
marginBottom="2%"
border="1px solid #ff7875"
borderRadius="5px"
flexDirection="column"
>
<Text fontSize="sm">
Email template for event <b>{eventName}</b> will be deleted
permanently!
</Text>
</Flex>
</ModalBody>
<ModalFooter>
<Button
leftIcon={<FaRegTrashAlt />}
colorScheme="red"
variant="solid"
onClick={deleteHandler}
isDisabled={false}
>
<Center h="100%" pt="5%">
Delete
</Center>
</Button>
</ModalFooter>
</ModalContent>
</Modal>
</>
);
};
export default DeleteEmailTemplateModal;

View File

@@ -82,7 +82,7 @@ const EditUserModal = ({
// @ts-ignore
[property]: userData[property],
}),
{}
{},
);
const res = await client
.mutation(UpdateUser, { params: { ...updatedUserData, id: userData.id } })

View File

@@ -1,65 +1,65 @@
import React from "react";
import { Flex, Stack, Text, useMediaQuery } from "@chakra-ui/react";
import InputField from "../../components/InputField";
import { TextInputType, TextAreaInputType } from "../../constants";
import React from 'react';
import { Flex, Stack, Text, useMediaQuery } from '@chakra-ui/react';
import InputField from '../../components/InputField';
import { TextInputType, TextAreaInputType } from '../../constants';
const AccessToken = ({ variables, setVariables }: any) => {
const [isNotSmallerScreen] = useMediaQuery("(min-width:600px)");
return (
<div>
{" "}
<Text fontSize="md" paddingTop="2%" fontWeight="bold" mb={5}>
Access Token
</Text>
<Stack spacing={6} padding="2% 0%">
<Flex direction={isNotSmallerScreen ? "row" : "column"}>
<Flex
w={isNotSmallerScreen ? "30%" : "50%"}
justifyContent="start"
alignItems="center"
>
<Text fontSize="sm">Access Token Expiry Time:</Text>
</Flex>
<Flex
w={isNotSmallerScreen ? "70%" : "100%"}
mt={isNotSmallerScreen ? "0" : "3"}
>
<InputField
borderRadius={5}
variables={variables}
setVariables={setVariables}
inputType={TextInputType.ACCESS_TOKEN_EXPIRY_TIME}
placeholder="0h15m0s"
/>
</Flex>
</Flex>
<Flex direction={isNotSmallerScreen ? "row" : "column"}>
<Flex
w={isNotSmallerScreen ? "30%" : "60%"}
justifyContent="start"
direction="column"
>
<Text fontSize="sm">Custom Scripts:</Text>
<Text fontSize="xs" color="blackAlpha.500">
(Used to add custom fields in ID token)
</Text>
</Flex>
<Flex
w={isNotSmallerScreen ? "70%" : "100%"}
mt={isNotSmallerScreen ? "0" : "3"}
>
<InputField
variables={variables}
setVariables={setVariables}
inputType={TextAreaInputType.CUSTOM_ACCESS_TOKEN_SCRIPT}
placeholder="Add script here"
minH="25vh"
/>
</Flex>
</Flex>
</Stack>
</div>
);
const [isNotSmallerScreen] = useMediaQuery('(min-width:600px)');
return (
<div>
{' '}
<Text fontSize="md" paddingTop="2%" fontWeight="bold" mb={5}>
Access Token
</Text>
<Stack spacing={6} padding="2% 0%">
<Flex direction={isNotSmallerScreen ? 'row' : 'column'}>
<Flex
w={isNotSmallerScreen ? '30%' : '50%'}
justifyContent="start"
alignItems="center"
>
<Text fontSize="sm">Access Token Expiry Time:</Text>
</Flex>
<Flex
w={isNotSmallerScreen ? '70%' : '100%'}
mt={isNotSmallerScreen ? '0' : '3'}
>
<InputField
borderRadius={5}
variables={variables}
setVariables={setVariables}
inputType={TextInputType.ACCESS_TOKEN_EXPIRY_TIME}
placeholder="0h15m0s"
/>
</Flex>
</Flex>
<Flex direction={isNotSmallerScreen ? 'row' : 'column'}>
<Flex
w={isNotSmallerScreen ? '30%' : '60%'}
justifyContent="start"
direction="column"
>
<Text fontSize="sm">Custom Scripts:</Text>
<Text fontSize="xs" color="blackAlpha.500">
(Used to add custom fields in ID token)
</Text>
</Flex>
<Flex
w={isNotSmallerScreen ? '70%' : '100%'}
mt={isNotSmallerScreen ? '0' : '3'}
>
<InputField
variables={variables}
setVariables={setVariables}
inputType={TextAreaInputType.CUSTOM_ACCESS_TOKEN_SCRIPT}
placeholder="Add script here"
minH="25vh"
/>
</Flex>
</Flex>
</Stack>
</div>
);
};
export default AccessToken;
export default AccessToken;

View File

@@ -1,35 +1,35 @@
import React from "react";
import { Flex, Stack, Center, Text, useMediaQuery } from "@chakra-ui/react";
import InputField from "../../components/InputField";
import { ArrayInputType} from "../../constants";
import React from 'react';
import { Flex, Stack, Center, Text, useMediaQuery } from '@chakra-ui/react';
import InputField from '../../components/InputField';
import { ArrayInputType } from '../../constants';
const DomainWhiteListing = ({ variables, setVariables }: any) => {
const [isNotSmallerScreen] = useMediaQuery("(min-width:600px)");
return (
<div>
{" "}
<Text fontSize="md" paddingTop="2%" fontWeight="bold" mb={5}>
Domain White Listing
</Text>
<Stack spacing={6} padding="2% 0%">
<Flex direction={isNotSmallerScreen ? "row" : "column"}>
<Flex w="30%" justifyContent="start" alignItems="center">
<Text fontSize="sm">Allowed Origins:</Text>
</Flex>
<Center
w={isNotSmallerScreen ? "70%" : "100%"}
mt={isNotSmallerScreen ? "0" : "3"}
>
<InputField
variables={variables}
setVariables={setVariables}
inputType={ArrayInputType.ALLOWED_ORIGINS}
/>
</Center>
</Flex>
</Stack>
</div>
);
const [isNotSmallerScreen] = useMediaQuery('(min-width:600px)');
return (
<div>
{' '}
<Text fontSize="md" paddingTop="2%" fontWeight="bold" mb={5}>
Domain White Listing
</Text>
<Stack spacing={6} padding="2% 0%">
<Flex direction={isNotSmallerScreen ? 'row' : 'column'}>
<Flex w="30%" justifyContent="start" alignItems="center">
<Text fontSize="sm">Allowed Origins:</Text>
</Flex>
<Center
w={isNotSmallerScreen ? '70%' : '100%'}
mt={isNotSmallerScreen ? '0' : '3'}
>
<InputField
variables={variables}
setVariables={setVariables}
inputType={ArrayInputType.ALLOWED_ORIGINS}
/>
</Center>
</Flex>
</Stack>
</div>
);
};
export default DomainWhiteListing;
export default DomainWhiteListing;

View File

@@ -1,114 +1,114 @@
import React from "react";
import { Flex, Stack, Center, Text, useMediaQuery } from "@chakra-ui/react";
import InputField from "../../components/InputField";
import { TextInputType, HiddenInputType} from "../../constants";
import React from 'react';
import { Flex, Stack, Center, Text, useMediaQuery } from '@chakra-ui/react';
import InputField from '../../components/InputField';
import { TextInputType, HiddenInputType } from '../../constants';
const EmailConfigurations = ({
variables,
setVariables,
fieldVisibility,
setFieldVisibility,
variables,
setVariables,
fieldVisibility,
setFieldVisibility,
}: any) => {
const [isNotSmallerScreen] = useMediaQuery("(min-width:600px)");
return (
<div>
{" "}
<Text fontSize="md" paddingTop="2%" fontWeight="bold" mb={5}>
Email Configurations
</Text>
<Stack spacing={6} padding="2% 0%">
<Flex direction={isNotSmallerScreen ? "row" : "column"}>
<Flex w="30%" justifyContent="start" alignItems="center">
<Text fontSize="sm">SMTP Host:</Text>
</Flex>
<Center
w={isNotSmallerScreen ? "70%" : "100%"}
mt={isNotSmallerScreen ? "0" : "3"}
>
<InputField
borderRadius={5}
variables={variables}
setVariables={setVariables}
inputType={TextInputType.SMTP_HOST}
/>
</Center>
</Flex>
<Flex direction={isNotSmallerScreen ? "row" : "column"}>
<Flex w="30%" justifyContent="start" alignItems="center">
<Text fontSize="sm">SMTP Port:</Text>
</Flex>
<Center
w={isNotSmallerScreen ? "70%" : "100%"}
mt={isNotSmallerScreen ? "0" : "3"}
>
<InputField
borderRadius={5}
variables={variables}
setVariables={setVariables}
inputType={TextInputType.SMTP_PORT}
/>
</Center>
</Flex>
<Flex direction={isNotSmallerScreen ? "row" : "column"}>
<Flex
w={isNotSmallerScreen ? "30%" : "40%"}
justifyContent="start"
alignItems="center"
>
<Text fontSize="sm">SMTP Username:</Text>
</Flex>
<Center
w={isNotSmallerScreen ? "70%" : "100%"}
mt={isNotSmallerScreen ? "0" : "3"}
>
<InputField
borderRadius={5}
variables={variables}
setVariables={setVariables}
inputType={TextInputType.SMTP_USERNAME}
/>
</Center>
</Flex>
<Flex direction={isNotSmallerScreen ? "row" : "column"}>
<Flex
w={isNotSmallerScreen ? "30%" : "40%"}
justifyContent="start"
alignItems="center"
>
<Text fontSize="sm">SMTP Password:</Text>
</Flex>
<Center
w={isNotSmallerScreen ? "70%" : "100%"}
mt={isNotSmallerScreen ? "0" : "3"}
>
<InputField
borderRadius={5}
variables={variables}
setVariables={setVariables}
fieldVisibility={fieldVisibility}
setFieldVisibility={setFieldVisibility}
inputType={HiddenInputType.SMTP_PASSWORD}
/>
</Center>
</Flex>
<Flex direction={isNotSmallerScreen ? "row" : "column"}>
<Flex w="30%" justifyContent="start" alignItems="center">
<Text fontSize="sm">From Email:</Text>
</Flex>
<Center
w={isNotSmallerScreen ? "70%" : "100%"}
mt={isNotSmallerScreen ? "0" : "3"}
>
<InputField
borderRadius={5}
variables={variables}
setVariables={setVariables}
inputType={TextInputType.SENDER_EMAIL}
/>
</Center>
</Flex>
</Stack>
</div>
);
const [isNotSmallerScreen] = useMediaQuery('(min-width:600px)');
return (
<div>
{' '}
<Text fontSize="md" paddingTop="2%" fontWeight="bold" mb={5}>
Email Configurations
</Text>
<Stack spacing={6} padding="2% 0%">
<Flex direction={isNotSmallerScreen ? 'row' : 'column'}>
<Flex w="30%" justifyContent="start" alignItems="center">
<Text fontSize="sm">SMTP Host:</Text>
</Flex>
<Center
w={isNotSmallerScreen ? '70%' : '100%'}
mt={isNotSmallerScreen ? '0' : '3'}
>
<InputField
borderRadius={5}
variables={variables}
setVariables={setVariables}
inputType={TextInputType.SMTP_HOST}
/>
</Center>
</Flex>
<Flex direction={isNotSmallerScreen ? 'row' : 'column'}>
<Flex w="30%" justifyContent="start" alignItems="center">
<Text fontSize="sm">SMTP Port:</Text>
</Flex>
<Center
w={isNotSmallerScreen ? '70%' : '100%'}
mt={isNotSmallerScreen ? '0' : '3'}
>
<InputField
borderRadius={5}
variables={variables}
setVariables={setVariables}
inputType={TextInputType.SMTP_PORT}
/>
</Center>
</Flex>
<Flex direction={isNotSmallerScreen ? 'row' : 'column'}>
<Flex
w={isNotSmallerScreen ? '30%' : '40%'}
justifyContent="start"
alignItems="center"
>
<Text fontSize="sm">SMTP Username:</Text>
</Flex>
<Center
w={isNotSmallerScreen ? '70%' : '100%'}
mt={isNotSmallerScreen ? '0' : '3'}
>
<InputField
borderRadius={5}
variables={variables}
setVariables={setVariables}
inputType={TextInputType.SMTP_USERNAME}
/>
</Center>
</Flex>
<Flex direction={isNotSmallerScreen ? 'row' : 'column'}>
<Flex
w={isNotSmallerScreen ? '30%' : '40%'}
justifyContent="start"
alignItems="center"
>
<Text fontSize="sm">SMTP Password:</Text>
</Flex>
<Center
w={isNotSmallerScreen ? '70%' : '100%'}
mt={isNotSmallerScreen ? '0' : '3'}
>
<InputField
borderRadius={5}
variables={variables}
setVariables={setVariables}
fieldVisibility={fieldVisibility}
setFieldVisibility={setFieldVisibility}
inputType={HiddenInputType.SMTP_PASSWORD}
/>
</Center>
</Flex>
<Flex direction={isNotSmallerScreen ? 'row' : 'column'}>
<Flex w="30%" justifyContent="start" alignItems="center">
<Text fontSize="sm">From Email:</Text>
</Flex>
<Center
w={isNotSmallerScreen ? '70%' : '100%'}
mt={isNotSmallerScreen ? '0' : '3'}
>
<InputField
borderRadius={5}
variables={variables}
setVariables={setVariables}
inputType={TextInputType.SENDER_EMAIL}
/>
</Center>
</Flex>
</Stack>
</div>
);
};
export default EmailConfigurations;
export default EmailConfigurations;

View File

@@ -1,5 +1,5 @@
import React from 'react';
import { Flex, Stack, Text } from '@chakra-ui/react';
import { Divider, Flex, Stack, Text } from '@chakra-ui/react';
import InputField from '../InputField';
import { SwitchInputType } from '../../constants';
@@ -10,7 +10,7 @@ const Features = ({ variables, setVariables }: any) => {
<Text fontSize="md" paddingTop="2%" fontWeight="bold" mb={5}>
Disable Features
</Text>
<Stack spacing={6} padding="2% 0%">
<Stack spacing={6}>
<Flex>
<Flex w="100%" justifyContent="start" alignItems="center">
<Text fontSize="sm">Disable Login Page:</Text>
@@ -83,6 +83,82 @@ const Features = ({ variables, setVariables }: any) => {
/>
</Flex>
</Flex>
<Flex alignItems="center">
<Flex w="100%" alignItems="baseline" flexDir="column">
<Text fontSize="sm">
Disable Multi Factor Authentication (MFA):
</Text>
<Text fontSize="x-small">
Note: Enabling this will ignore Enforcing MFA shown below and will
also ignore the user MFA setting.
</Text>
</Flex>
<Flex justifyContent="start" mb={3}>
<InputField
variables={variables}
setVariables={setVariables}
inputType={SwitchInputType.DISABLE_MULTI_FACTOR_AUTHENTICATION}
/>
</Flex>
</Flex>
</Stack>
<Divider paddingY={5} />
<Text fontSize="md" paddingTop={5} fontWeight="bold" mb={5}>
Enable Features
</Text>
<Stack spacing={6}>
<Flex alignItems="center">
<Flex w="100%" alignItems="baseline" flexDir="column">
<Text fontSize="sm">
Enforce Multi Factor Authentication (MFA):
</Text>
<Text fontSize="x-small">
Note: If you disable enforcing after it was enabled, it will still
keep MFA enabled for older users.
</Text>
</Flex>
<Flex justifyContent="start" mb={3}>
<InputField
variables={variables}
setVariables={setVariables}
inputType={SwitchInputType.ENFORCE_MULTI_FACTOR_AUTHENTICATION}
/>
</Flex>
</Flex>
</Stack>
<Divider paddingY={5} />
<Text fontSize="md" paddingTop={5} fontWeight="bold" mb={5}>
Cookie Security Features
</Text>
<Stack spacing={6}>
<Flex>
<Flex w="100%" alignItems="baseline" flexDir="column">
<Text fontSize="sm">Use Secure App Cookie:</Text>
<Text fontSize="x-small">
Note: If you set this to insecure, it will set{' '}
<code>sameSite</code> property of cookie to <code>lax</code> mode
</Text>
</Flex>
<Flex justifyContent="start">
<InputField
variables={variables}
setVariables={setVariables}
inputType={SwitchInputType.APP_COOKIE_SECURE}
/>
</Flex>
</Flex>
<Flex>
<Flex w="100%" alignItems="baseline" flexDir="column">
<Text fontSize="sm">Use Secure Admin Cookie:</Text>
</Flex>
<Flex justifyContent="start">
<InputField
variables={variables}
setVariables={setVariables}
inputType={SwitchInputType.ADMIN_COOKIE_SECURE}
/>
</Flex>
</Flex>
</Stack>
</div>
);

View File

@@ -1,154 +1,201 @@
import React from "react";
import { Flex, Stack, Center, Text, useMediaQuery } from "@chakra-ui/react";
import React from 'react';
import {
HiddenInputType,
TextInputType,
TextAreaInputType,
} from "../../constants";
import GenerateKeysModal from "../GenerateKeysModal";
import InputField from "../InputField";
Flex,
Stack,
Center,
Text,
useMediaQuery,
Button,
useToast,
} from '@chakra-ui/react';
import {
HiddenInputType,
TextInputType,
TextAreaInputType,
} from '../../constants';
import GenerateKeysModal from '../GenerateKeysModal';
import InputField from '../InputField';
import { copyTextToClipboard } from '../../utils';
const JSTConfigurations = ({
variables,
setVariables,
fieldVisibility,
setFieldVisibility,
SelectInputType,
getData,
HMACEncryptionType,
RSAEncryptionType,
ECDSAEncryptionType,
variables,
setVariables,
fieldVisibility,
setFieldVisibility,
SelectInputType,
getData,
HMACEncryptionType,
RSAEncryptionType,
ECDSAEncryptionType,
}: any) => {
const [isNotSmallerScreen] = useMediaQuery("(min-width:600px)");
const [isNotSmallerScreen] = useMediaQuery('(min-width:600px)');
const toast = useToast();
return (
<div>
{" "}
<Flex
borderRadius={5}
width="100%"
justifyContent="space-between"
alignItems="center"
paddingTop="2%"
>
<Text
fontSize={isNotSmallerScreen ? "md" : "sm"}
fontWeight="bold"
mb={5}
>
JWT (JSON Web Tokens) Configurations
</Text>
<Flex mb={7}>
<GenerateKeysModal jwtType={variables.JWT_TYPE} getData={getData} />
</Flex>
</Flex>
<Stack spacing={6} padding="2% 0%">
<Flex direction={isNotSmallerScreen ? "row" : "column"}>
<Flex w="30%" justifyContent="start" alignItems="center">
<Text fontSize="sm">JWT Type:</Text>
</Flex>
<Flex
w={isNotSmallerScreen ? "70%" : "100%"}
mt={isNotSmallerScreen ? "0" : "2"}
>
<InputField
borderRadius={5}
variables={variables}
setVariables={setVariables}
inputType={SelectInputType}
value={SelectInputType}
options={{
...HMACEncryptionType,
...RSAEncryptionType,
...ECDSAEncryptionType,
}}
/>
</Flex>
</Flex>
{Object.values(HMACEncryptionType).includes(variables.JWT_TYPE) ? (
<Flex direction={isNotSmallerScreen ? "row" : "column"}>
<Flex w="30%" justifyContent="start" alignItems="center">
<Text fontSize="sm">JWT Secret</Text>
</Flex>
<Center
w={isNotSmallerScreen ? "70%" : "100%"}
mt={isNotSmallerScreen ? "0" : "2"}
>
<InputField
borderRadius={5}
variables={variables}
setVariables={setVariables}
fieldVisibility={fieldVisibility}
setFieldVisibility={setFieldVisibility}
inputType={HiddenInputType.JWT_SECRET}
/>
</Center>
</Flex>
) : (
<>
<Flex direction={isNotSmallerScreen ? "row" : "column"}>
<Flex w="30%" justifyContent="start" alignItems="center">
<Text fontSize="sm">Public Key</Text>
</Flex>
<Center
w={isNotSmallerScreen ? "70%" : "100%"}
mt={isNotSmallerScreen ? "0" : "2"}
>
<InputField
borderRadius={5}
variables={variables}
setVariables={setVariables}
inputType={TextAreaInputType.JWT_PUBLIC_KEY}
placeholder="Add public key here"
minH="25vh"
/>
</Center>
</Flex>
<Flex direction={isNotSmallerScreen ? "row" : "column"}>
<Flex w="30%" justifyContent="start" alignItems="center">
<Text fontSize="sm">Private Key</Text>
</Flex>
<Center
w={isNotSmallerScreen ? "70%" : "100%"}
mt={isNotSmallerScreen ? "0" : "2"}
>
<InputField
borderRadius={5}
variables={variables}
setVariables={setVariables}
inputType={TextAreaInputType.JWT_PRIVATE_KEY}
placeholder="Add private key here"
minH="25vh"
/>
</Center>
</Flex>
</>
)}
<Flex direction={isNotSmallerScreen ? "row" : "column"}>
<Flex
w={isNotSmallerScreen ? "30%" : "40%"}
justifyContent="start"
alignItems="center"
>
<Text fontSize="sm" orientation="vertical">
JWT Role Claim:
</Text>
</Flex>
<Center
w={isNotSmallerScreen ? "70%" : "100%"}
mt={isNotSmallerScreen ? "0" : "2"}
>
<InputField
borderRadius={5}
variables={variables}
setVariables={setVariables}
inputType={TextInputType.JWT_ROLE_CLAIM}
/>
</Center>
</Flex>
</Stack>
</div>
);
const copyJSON = async () => {
try {
await copyTextToClipboard(
JSON.stringify({
type: variables.JWT_TYPE,
key: variables.JWT_PUBLIC_KEY || variables.JWT_SECRET,
}),
);
toast({
title: `JWT config copied successfully`,
isClosable: true,
status: 'success',
position: 'bottom-right',
});
} catch (err) {
console.error({
message: `Failed to copy JWT config`,
error: err,
});
toast({
title: `Failed to copy JWT config`,
isClosable: true,
status: 'error',
position: 'bottom-right',
});
}
};
return (
<div>
{' '}
<Flex
borderRadius={5}
width="100%"
justifyContent="space-between"
alignItems="center"
paddingTop="2%"
>
<Text
fontSize={isNotSmallerScreen ? 'md' : 'sm'}
fontWeight="bold"
mb={5}
>
JWT (JSON Web Tokens) Configurations
</Text>
<Flex mb={7}>
<Button
colorScheme="blue"
h="1.75rem"
size="sm"
variant="ghost"
onClick={copyJSON}
>
Copy As JSON Config
</Button>
<GenerateKeysModal jwtType={variables.JWT_TYPE} getData={getData} />
</Flex>
</Flex>
<Stack spacing={6} padding="2% 0%">
<Flex direction={isNotSmallerScreen ? 'row' : 'column'}>
<Flex w="30%" justifyContent="start" alignItems="center">
<Text fontSize="sm">JWT Type:</Text>
</Flex>
<Flex
w={isNotSmallerScreen ? '70%' : '100%'}
mt={isNotSmallerScreen ? '0' : '2'}
>
<InputField
borderRadius={5}
variables={variables}
setVariables={setVariables}
inputType={SelectInputType}
value={SelectInputType}
options={{
...HMACEncryptionType,
...RSAEncryptionType,
...ECDSAEncryptionType,
}}
/>
</Flex>
</Flex>
{Object.values(HMACEncryptionType).includes(variables.JWT_TYPE) ? (
<Flex direction={isNotSmallerScreen ? 'row' : 'column'}>
<Flex w="30%" justifyContent="start" alignItems="center">
<Text fontSize="sm">JWT Secret</Text>
</Flex>
<Center
w={isNotSmallerScreen ? '70%' : '100%'}
mt={isNotSmallerScreen ? '0' : '2'}
>
<InputField
borderRadius={5}
variables={variables}
setVariables={setVariables}
fieldVisibility={fieldVisibility}
setFieldVisibility={setFieldVisibility}
inputType={HiddenInputType.JWT_SECRET}
/>
</Center>
</Flex>
) : (
<>
<Flex direction={isNotSmallerScreen ? 'row' : 'column'}>
<Flex w="30%" justifyContent="start" alignItems="center">
<Text fontSize="sm">Public Key</Text>
</Flex>
<Center
w={isNotSmallerScreen ? '70%' : '100%'}
mt={isNotSmallerScreen ? '0' : '2'}
>
<InputField
borderRadius={5}
variables={variables}
setVariables={setVariables}
inputType={TextAreaInputType.JWT_PUBLIC_KEY}
placeholder="Add public key here"
minH="25vh"
/>
</Center>
</Flex>
<Flex direction={isNotSmallerScreen ? 'row' : 'column'}>
<Flex w="30%" justifyContent="start" alignItems="center">
<Text fontSize="sm">Private Key</Text>
</Flex>
<Center
w={isNotSmallerScreen ? '70%' : '100%'}
mt={isNotSmallerScreen ? '0' : '2'}
>
<InputField
borderRadius={5}
variables={variables}
setVariables={setVariables}
inputType={TextAreaInputType.JWT_PRIVATE_KEY}
placeholder="Add private key here"
minH="25vh"
/>
</Center>
</Flex>
</>
)}
<Flex direction={isNotSmallerScreen ? 'row' : 'column'}>
<Flex
w={isNotSmallerScreen ? '30%' : '40%'}
justifyContent="start"
alignItems="center"
>
<Text fontSize="sm" orientation="vertical">
JWT Role Claim:
</Text>
</Flex>
<Center
w={isNotSmallerScreen ? '70%' : '100%'}
mt={isNotSmallerScreen ? '0' : '2'}
>
<InputField
borderRadius={5}
variables={variables}
setVariables={setVariables}
inputType={TextInputType.JWT_ROLE_CLAIM}
/>
</Center>
</Flex>
</Stack>
</div>
);
};
export default JSTConfigurations;
export default JSTConfigurations;

View File

@@ -15,6 +15,7 @@ import {
FaFacebookF,
FaLinkedin,
FaApple,
FaTwitter,
} from 'react-icons/fa';
import { TextInputType, HiddenInputType } from '../../constants';
@@ -264,6 +265,44 @@ const OAuthConfig = ({
/>
</Center>
</Flex>
<Flex direction={isNotSmallerScreen ? 'row' : 'column'}>
<Center
w={isNotSmallerScreen ? '55px' : '35px'}
h="35px"
marginRight="1.5%"
border="1px solid #3b5998"
borderRadius="5px"
>
<FaTwitter />
</Center>
<Center
w={isNotSmallerScreen ? '70%' : '100%'}
mt={isNotSmallerScreen ? '0' : '3'}
marginRight="1.5%"
>
<InputField
borderRadius={5}
variables={envVariables}
setVariables={setVariables}
inputType={TextInputType.TWITTER_CLIENT_ID}
placeholder="Twitter Client ID"
/>
</Center>
<Center
w={isNotSmallerScreen ? '70%' : '100%'}
mt={isNotSmallerScreen ? '0' : '3'}
>
<InputField
borderRadius={5}
variables={envVariables}
setVariables={setVariables}
fieldVisibility={fieldVisibility}
setFieldVisibility={setFieldVisibility}
inputType={HiddenInputType.TWITTER_CLIENT_SECRET}
placeholder="Twitter Client Secret"
/>
</Center>
</Flex>
</Stack>
</Box>
</div>

View File

@@ -1,60 +1,60 @@
import React from "react";
import { Flex, Stack, Center, Text, useMediaQuery } from "@chakra-ui/react";
import InputField from "../InputField";
import { TextInputType } from "../../constants";
import React from 'react';
import { Flex, Stack, Center, Text, useMediaQuery } from '@chakra-ui/react';
import InputField from '../InputField';
import { TextInputType } from '../../constants';
const OrganizationInfo = ({ variables, setVariables }: any) => {
const [isNotSmallerScreen] = useMediaQuery("(min-width:600px)");
return (
<div>
{" "}
<Text fontSize="md" paddingTop="2%" fontWeight="bold" mb={5}>
Organization Information
</Text>
<Stack spacing={6} padding="2% 0%">
<Flex direction={isNotSmallerScreen ? "row" : "column"}>
<Flex
w={isNotSmallerScreen ? "30%" : "40%"}
justifyContent="start"
alignItems="center"
>
<Text fontSize="sm">Organization Name:</Text>
</Flex>
<Center
w={isNotSmallerScreen ? "70%" : "100%"}
mt={isNotSmallerScreen ? "0" : "3"}
>
<InputField
borderRadius={5}
variables={variables}
setVariables={setVariables}
inputType={TextInputType.ORGANIZATION_NAME}
/>
</Center>
</Flex>
<Flex direction={isNotSmallerScreen ? "row" : "column"}>
<Flex
w={isNotSmallerScreen ? "30%" : "40%"}
justifyContent="start"
alignItems="center"
>
<Text fontSize="sm">Organization Logo:</Text>
</Flex>
<Center
w={isNotSmallerScreen ? "70%" : "100%"}
mt={isNotSmallerScreen ? "0" : "3"}
>
<InputField
borderRadius={5}
variables={variables}
setVariables={setVariables}
inputType={TextInputType.ORGANIZATION_LOGO}
/>
</Center>
</Flex>
</Stack>
</div>
);
const [isNotSmallerScreen] = useMediaQuery('(min-width:600px)');
return (
<div>
{' '}
<Text fontSize="md" paddingTop="2%" fontWeight="bold" mb={5}>
Organization Information
</Text>
<Stack spacing={6} padding="2% 0%">
<Flex direction={isNotSmallerScreen ? 'row' : 'column'}>
<Flex
w={isNotSmallerScreen ? '30%' : '40%'}
justifyContent="start"
alignItems="center"
>
<Text fontSize="sm">Organization Name:</Text>
</Flex>
<Center
w={isNotSmallerScreen ? '70%' : '100%'}
mt={isNotSmallerScreen ? '0' : '3'}
>
<InputField
borderRadius={5}
variables={variables}
setVariables={setVariables}
inputType={TextInputType.ORGANIZATION_NAME}
/>
</Center>
</Flex>
<Flex direction={isNotSmallerScreen ? 'row' : 'column'}>
<Flex
w={isNotSmallerScreen ? '30%' : '40%'}
justifyContent="start"
alignItems="center"
>
<Text fontSize="sm">Organization Logo:</Text>
</Flex>
<Center
w={isNotSmallerScreen ? '70%' : '100%'}
mt={isNotSmallerScreen ? '0' : '3'}
>
<InputField
borderRadius={5}
variables={variables}
setVariables={setVariables}
inputType={TextInputType.ORGANIZATION_LOGO}
/>
</Center>
</Flex>
</Stack>
</div>
);
};
export default OrganizationInfo;
export default OrganizationInfo;

View File

@@ -1,138 +1,138 @@
import React from "react";
import React from 'react';
import {
Flex,
Stack,
Center,
Text,
Input,
InputGroup,
InputRightElement,
useMediaQuery,
} from "@chakra-ui/react";
import { FaRegEyeSlash, FaRegEye } from "react-icons/fa";
import InputField from "../InputField";
import { HiddenInputType } from "../../constants";
Flex,
Stack,
Center,
Text,
Input,
InputGroup,
InputRightElement,
useMediaQuery,
} from '@chakra-ui/react';
import { FaRegEyeSlash, FaRegEye } from 'react-icons/fa';
import InputField from '../InputField';
import { HiddenInputType } from '../../constants';
const SecurityAdminSecret = ({
variables,
setVariables,
fieldVisibility,
setFieldVisibility,
validateAdminSecretHandler,
adminSecret,
variables,
setVariables,
fieldVisibility,
setFieldVisibility,
validateAdminSecretHandler,
adminSecret,
}: any) => {
const [isNotSmallerScreen] = useMediaQuery("(min-width:600px)");
return (
<div>
{" "}
<Text fontSize="md" paddingTop="2%" fontWeight="bold">
Security (Admin Secret)
</Text>
<Stack
spacing={6}
padding="0 5%"
marginTop="3%"
border="1px solid #ff7875"
borderRadius="5px"
>
<Flex
marginTop={isNotSmallerScreen ? "3%" : "5%"}
direction={isNotSmallerScreen ? "row" : "column"}
>
<Flex
mt={3}
w={isNotSmallerScreen ? "30%" : "40%"}
justifyContent="start"
alignItems="center"
>
<Text fontSize="sm">Old Admin Secret:</Text>
</Flex>
<Center
w={isNotSmallerScreen ? "70%" : "100%"}
mt={isNotSmallerScreen ? "0" : "3"}
>
<InputGroup size="sm">
<Input
borderRadius={5}
size="sm"
placeholder="Enter Old Admin Secret"
value={adminSecret.value as string}
onChange={(event: any) => validateAdminSecretHandler(event)}
type={
!fieldVisibility[HiddenInputType.OLD_ADMIN_SECRET]
? "password"
: "text"
}
/>
<InputRightElement
right="5px"
children={
<Flex>
{fieldVisibility[HiddenInputType.OLD_ADMIN_SECRET] ? (
<Center
w="25px"
margin="0 1.5%"
cursor="pointer"
onClick={() =>
setFieldVisibility({
...fieldVisibility,
[HiddenInputType.OLD_ADMIN_SECRET]: false,
})
}
>
<FaRegEyeSlash color="#bfbfbf" />
</Center>
) : (
<Center
w="25px"
margin="0 1.5%"
cursor="pointer"
onClick={() =>
setFieldVisibility({
...fieldVisibility,
[HiddenInputType.OLD_ADMIN_SECRET]: true,
})
}
>
<FaRegEye color="#bfbfbf" />
</Center>
)}
</Flex>
}
/>
</InputGroup>
</Center>
</Flex>
<Flex
paddingBottom="3%"
direction={isNotSmallerScreen ? "row" : "column"}
>
<Flex
w={isNotSmallerScreen ? "30%" : "50%"}
justifyContent="start"
alignItems="center"
>
<Text fontSize="sm">New Admin Secret:</Text>
</Flex>
<Center
w={isNotSmallerScreen ? "70%" : "100%"}
mt={isNotSmallerScreen ? "0" : "3"}
>
<InputField
borderRadius={5}
mb={3}
variables={variables}
setVariables={setVariables}
inputType={HiddenInputType.ADMIN_SECRET}
fieldVisibility={fieldVisibility}
setFieldVisibility={setFieldVisibility}
isDisabled={adminSecret.disableInputField}
placeholder="Enter New Admin Secret"
/>
</Center>
</Flex>
</Stack>
</div>
);
const [isNotSmallerScreen] = useMediaQuery('(min-width:600px)');
return (
<div>
{' '}
<Text fontSize="md" paddingTop="2%" fontWeight="bold">
Security (Admin Secret)
</Text>
<Stack
spacing={6}
padding="0 5%"
marginTop="3%"
border="1px solid #ff7875"
borderRadius="5px"
>
<Flex
marginTop={isNotSmallerScreen ? '3%' : '5%'}
direction={isNotSmallerScreen ? 'row' : 'column'}
>
<Flex
mt={3}
w={isNotSmallerScreen ? '30%' : '40%'}
justifyContent="start"
alignItems="center"
>
<Text fontSize="sm">Old Admin Secret:</Text>
</Flex>
<Center
w={isNotSmallerScreen ? '70%' : '100%'}
mt={isNotSmallerScreen ? '0' : '3'}
>
<InputGroup size="sm">
<Input
borderRadius={5}
size="sm"
placeholder="Enter Old Admin Secret"
value={adminSecret.value as string}
onChange={(event: any) => validateAdminSecretHandler(event)}
type={
!fieldVisibility[HiddenInputType.OLD_ADMIN_SECRET]
? 'password'
: 'text'
}
/>
<InputRightElement
right="5px"
children={
<Flex>
{fieldVisibility[HiddenInputType.OLD_ADMIN_SECRET] ? (
<Center
w="25px"
margin="0 1.5%"
cursor="pointer"
onClick={() =>
setFieldVisibility({
...fieldVisibility,
[HiddenInputType.OLD_ADMIN_SECRET]: false,
})
}
>
<FaRegEyeSlash color="#bfbfbf" />
</Center>
) : (
<Center
w="25px"
margin="0 1.5%"
cursor="pointer"
onClick={() =>
setFieldVisibility({
...fieldVisibility,
[HiddenInputType.OLD_ADMIN_SECRET]: true,
})
}
>
<FaRegEye color="#bfbfbf" />
</Center>
)}
</Flex>
}
/>
</InputGroup>
</Center>
</Flex>
<Flex
paddingBottom="3%"
direction={isNotSmallerScreen ? 'row' : 'column'}
>
<Flex
w={isNotSmallerScreen ? '30%' : '50%'}
justifyContent="start"
alignItems="center"
>
<Text fontSize="sm">New Admin Secret:</Text>
</Flex>
<Center
w={isNotSmallerScreen ? '70%' : '100%'}
mt={isNotSmallerScreen ? '0' : '3'}
>
<InputField
borderRadius={5}
mb={3}
variables={variables}
setVariables={setVariables}
inputType={HiddenInputType.ADMIN_SECRET}
fieldVisibility={fieldVisibility}
setFieldVisibility={setFieldVisibility}
isDisabled={adminSecret.disableInputField}
placeholder="Enter New Admin Secret"
/>
</Center>
</Flex>
</Stack>
</div>
);
};
export default SecurityAdminSecret;
export default SecurityAdminSecret;

View File

@@ -167,7 +167,7 @@ const GenerateKeysModal = ({ jwtType, getData }: propTypes) => {
) : (
<>
{Object.values(HMACEncryptionType).includes(
stateVariables.JWT_TYPE
stateVariables.JWT_TYPE,
) ? (
<Flex marginTop="8">
<Flex w="23%" justifyContent="start" alignItems="center">

View File

@@ -12,7 +12,6 @@ import {
Select,
Textarea,
Switch,
Code,
Text,
} from '@chakra-ui/react';
import {
@@ -65,7 +64,7 @@ const InputField = ({
const updateInputHandler = (
type: string,
operation: any,
role: string = ''
role: string = '',
) => {
if (operation === ArrayInputOperations.APPEND) {
if (inputData[type] !== '') {
@@ -79,7 +78,7 @@ const InputField = ({
}
if (operation === ArrayInputOperations.REMOVE) {
let updatedEnvVars = variables[type].filter(
(item: string) => item !== role
(item: string) => item !== role,
);
setVariables({
...variables,
@@ -96,7 +95,7 @@ const InputField = ({
onChange={(
event: Event & {
target: HTMLInputElement;
}
},
) =>
setVariables({
...variables,
@@ -121,7 +120,7 @@ const InputField = ({
onChange={(
event: Event & {
target: HTMLInputElement;
}
},
) =>
setVariables({
...variables,
@@ -208,7 +207,7 @@ const InputField = ({
updateInputHandler(
inputType,
ArrayInputOperations.REMOVE,
role
role,
)
}
/>
@@ -289,7 +288,7 @@ const InputField = ({
onChange={(
event: Event & {
target: HTMLInputElement;
}
},
) =>
setVariables({
...variables,

View File

@@ -304,7 +304,7 @@ const InviteMembersModal = ({
onClick={() =>
updateEmailListHandler(
ArrayInputOperations.REMOVE,
index
index,
)
}
>

View File

@@ -31,6 +31,7 @@ import {
FiUsers,
FiChevronDown,
FiLink,
FiFileText,
} from 'react-icons/fi';
import { BiCustomize } from 'react-icons/bi';
import { AiOutlineKey } from 'react-icons/ai';
@@ -113,6 +114,7 @@ const LinkItems: Array<LinkItemProps> = [
},
{ name: 'Users', icon: FiUsers, route: '/users' },
{ name: 'Webhooks', icon: FiLink, route: '/webhooks' },
{ name: 'Email Templates', icon: FiFileText, route: '/email-templates' },
];
interface SidebarProps extends BoxProps {
@@ -216,7 +218,7 @@ export const Sidebar = ({ onClose, ...rest }: SidebarProps) => {
</NavItem>{' '}
</Text>
</NavLink>
)
),
)}
<Link
href="/playground"

View File

@@ -0,0 +1,457 @@
import React, { useEffect, useRef, useState } from 'react';
import {
Button,
Center,
Flex,
Input,
InputGroup,
MenuItem,
Modal,
ModalBody,
ModalCloseButton,
ModalContent,
ModalFooter,
ModalHeader,
ModalOverlay,
Select,
Text,
useDisclosure,
useToast,
Alert,
AlertIcon,
Collapse,
Box,
TableContainer,
Table,
Thead,
Tr,
Th,
Tbody,
Td,
Code,
} from '@chakra-ui/react';
import { FaPlus, FaAngleDown, FaAngleUp } from 'react-icons/fa';
import { useClient } from 'urql';
import EmailEditor from 'react-email-editor';
import {
UpdateModalViews,
EmailTemplateInputDataFields,
emailTemplateEventNames,
emailTemplateVariables,
} from '../constants';
import { capitalizeFirstLetter } from '../utils';
import { AddEmailTemplate, EditEmailTemplate } from '../graphql/mutation';
interface selectedEmailTemplateDataTypes {
[EmailTemplateInputDataFields.ID]: string;
[EmailTemplateInputDataFields.EVENT_NAME]: string;
[EmailTemplateInputDataFields.SUBJECT]: string;
[EmailTemplateInputDataFields.CREATED_AT]: number;
[EmailTemplateInputDataFields.TEMPLATE]: string;
[EmailTemplateInputDataFields.DESIGN]: string;
}
interface UpdateEmailTemplateInputPropTypes {
view: UpdateModalViews;
selectedTemplate?: selectedEmailTemplateDataTypes;
fetchEmailTemplatesData: Function;
}
interface templateVariableDataTypes {
text: string;
value: string;
description: string;
}
interface emailTemplateDataType {
[EmailTemplateInputDataFields.EVENT_NAME]: string;
[EmailTemplateInputDataFields.SUBJECT]: string;
}
interface validatorDataType {
[EmailTemplateInputDataFields.SUBJECT]: boolean;
}
const initTemplateData: emailTemplateDataType = {
[EmailTemplateInputDataFields.EVENT_NAME]: emailTemplateEventNames.Signup,
[EmailTemplateInputDataFields.SUBJECT]: '',
};
const initTemplateValidatorData: validatorDataType = {
[EmailTemplateInputDataFields.SUBJECT]: true,
};
const UpdateEmailTemplate = ({
view,
selectedTemplate,
fetchEmailTemplatesData,
}: UpdateEmailTemplateInputPropTypes) => {
const client = useClient();
const toast = useToast();
const emailEditorRef = useRef(null);
const { isOpen, onOpen, onClose } = useDisclosure();
const [loading, setLoading] = useState<boolean>(false);
const [templateVariables, setTemplateVariables] = useState<
templateVariableDataTypes[]
>([]);
const [templateData, setTemplateData] = useState<emailTemplateDataType>({
...initTemplateData,
});
const [validator, setValidator] = useState<validatorDataType>({
...initTemplateValidatorData,
});
const [isDynamicVariableInfoOpen, setIsDynamicVariableInfoOpen] =
useState<boolean>(false);
const onReady = () => {
if (selectedTemplate) {
const { design } = selectedTemplate;
try {
const designData = JSON.parse(design);
// @ts-ignore
emailEditorRef.current.editor.loadDesign(designData);
} catch (error) {
console.error(error);
onClose();
}
}
};
const inputChangehandler = (inputType: string, value: any) => {
if (inputType !== EmailTemplateInputDataFields.EVENT_NAME) {
setValidator({
...validator,
[inputType]: value?.trim().length,
});
}
setTemplateData({ ...templateData, [inputType]: value });
};
const validateData = () => {
return (
!loading &&
templateData[EmailTemplateInputDataFields.EVENT_NAME].length > 0 &&
templateData[EmailTemplateInputDataFields.SUBJECT].length > 0 &&
validator[EmailTemplateInputDataFields.SUBJECT]
);
};
const saveData = async () => {
if (!validateData()) return;
setLoading(true);
// @ts-ignore
return await emailEditorRef.current.editor.exportHtml(async (data) => {
const { design, html } = data;
if (!html || !design) {
setLoading(false);
return;
}
const params = {
[EmailTemplateInputDataFields.EVENT_NAME]:
templateData[EmailTemplateInputDataFields.EVENT_NAME],
[EmailTemplateInputDataFields.SUBJECT]:
templateData[EmailTemplateInputDataFields.SUBJECT],
[EmailTemplateInputDataFields.TEMPLATE]: html.trim(),
[EmailTemplateInputDataFields.DESIGN]: JSON.stringify(design),
};
let res: any = {};
if (
view === UpdateModalViews.Edit &&
selectedTemplate?.[EmailTemplateInputDataFields.ID]
) {
res = await client
.mutation(EditEmailTemplate, {
params: {
...params,
id: selectedTemplate[EmailTemplateInputDataFields.ID],
},
})
.toPromise();
} else {
res = await client.mutation(AddEmailTemplate, { params }).toPromise();
}
setLoading(false);
if (res.error) {
toast({
title: capitalizeFirstLetter(res.error.message),
isClosable: true,
status: 'error',
position: 'bottom-right',
});
} else if (
res.data?._add_email_template ||
res.data?._update_email_template
) {
toast({
title: capitalizeFirstLetter(
res.data?._add_email_template?.message ||
res.data?._update_email_template?.message,
),
isClosable: true,
status: 'success',
position: 'bottom-right',
});
setTemplateData({
...initTemplateData,
});
setValidator({ ...initTemplateValidatorData });
fetchEmailTemplatesData();
}
view === UpdateModalViews.ADD && onClose();
});
};
const resetData = () => {
if (selectedTemplate) {
setTemplateData(selectedTemplate);
} else {
setTemplateData({ ...initTemplateData });
}
};
useEffect(() => {
if (
isOpen &&
view === UpdateModalViews.Edit &&
selectedTemplate &&
Object.keys(selectedTemplate || {}).length
) {
const { id, created_at, template, design, ...rest } = selectedTemplate;
setTemplateData(rest);
}
}, [isOpen]);
useEffect(() => {
const updatedTemplateVariables = Object.entries(
emailTemplateVariables,
).reduce((acc, [key, val]): any => {
if (
(templateData[EmailTemplateInputDataFields.EVENT_NAME] !==
emailTemplateEventNames['Verify Otp'] &&
val === emailTemplateVariables.otp) ||
(templateData[EmailTemplateInputDataFields.EVENT_NAME] ===
emailTemplateEventNames['Verify Otp'] &&
val === emailTemplateVariables.verification_url)
) {
return acc;
}
return [
...acc,
{
text: key,
value: val.value,
description: val.description,
},
];
}, []);
setTemplateVariables(updatedTemplateVariables);
}, [templateData[EmailTemplateInputDataFields.EVENT_NAME]]);
return (
<>
{view === UpdateModalViews.ADD ? (
<Button
leftIcon={<FaPlus />}
colorScheme="blue"
variant="solid"
onClick={onOpen}
isDisabled={false}
size="sm"
>
<Center h="100%">Add Template</Center>{' '}
</Button>
) : (
<MenuItem onClick={onOpen}>Edit</MenuItem>
)}
<Modal
isOpen={isOpen}
onClose={() => {
resetData();
onClose();
}}
size="6xl"
>
<ModalOverlay />
<ModalContent>
<ModalHeader>
{view === UpdateModalViews.ADD
? 'Add New Email Template'
: 'Edit Email Template'}
</ModalHeader>
<ModalCloseButton />
<ModalBody>
<Flex
flexDirection="column"
border="1px"
borderRadius="md"
borderColor="gray.200"
p="5"
>
<Alert
status="info"
onClick={() =>
setIsDynamicVariableInfoOpen(!isDynamicVariableInfoOpen)
}
borderRadius="5"
marginBottom={5}
cursor="pointer"
fontSize="sm"
>
<AlertIcon />
<Flex
width="100%"
justifyContent="space-between"
alignItems="center"
>
<Box width="85%">
<b>Note:</b> You can add set of dynamic variables to subject
and email body. Click here to see the set of dynamic
variables.
</Box>
{isDynamicVariableInfoOpen ? <FaAngleUp /> : <FaAngleDown />}
</Flex>
</Alert>
<Collapse
style={{
width: '100%',
}}
in={isDynamicVariableInfoOpen}
>
<TableContainer
background="gray.100"
borderRadius={5}
height={200}
width="100%"
overflowY="auto"
overflowWrap="break-word"
>
<Table variant="simple">
<Thead>
<Tr>
<Th>Variable</Th>
<Th>Description</Th>
</Tr>
</Thead>
<Tbody>
{templateVariables.map((i) => (
<Tr key={i.text}>
<Td>
<Code fontSize="sm">{`{{.${i.text}}}`}</Code>
</Td>
<Td>
<Text
size="sm"
fontSize="sm"
overflowWrap="break-word"
width="100%"
>
{i.description}
</Text>
</Td>
</Tr>
))}
</Tbody>
</Table>
</TableContainer>
</Collapse>
<Flex
width="100%"
justifyContent="space-between"
alignItems="center"
marginBottom="2%"
>
<Flex flex="1">Event Name</Flex>
<Flex flex="3">
<Select
size="md"
value={
templateData[EmailTemplateInputDataFields.EVENT_NAME]
}
onChange={(e) =>
inputChangehandler(
EmailTemplateInputDataFields.EVENT_NAME,
e.currentTarget.value,
)
}
>
{Object.entries(emailTemplateEventNames).map(
([key, value]: any) => (
<option value={value} key={key}>
{key}
</option>
),
)}
</Select>
</Flex>
</Flex>
<Flex
width="100%"
justifyContent="start"
alignItems="center"
marginBottom="2%"
>
<Flex flex="1">Subject</Flex>
<Flex flex="3">
<InputGroup size="md">
<Input
pr="4.5rem"
type="text"
placeholder="Subject Line"
value={templateData[EmailTemplateInputDataFields.SUBJECT]}
isInvalid={
!validator[EmailTemplateInputDataFields.SUBJECT]
}
onChange={(e) =>
inputChangehandler(
EmailTemplateInputDataFields.SUBJECT,
e.currentTarget.value,
)
}
/>
</InputGroup>
</Flex>
</Flex>
<Flex
width="100%"
justifyContent="flex-start"
alignItems="center"
marginBottom="2%"
>
Template Body
</Flex>
<Flex
width="100%"
justifyContent="flex-start"
alignItems="center"
border="1px solid"
borderColor="gray.200"
>
<EmailEditor ref={emailEditorRef} onReady={onReady} />
</Flex>
</Flex>
</ModalBody>
<ModalFooter>
<Button
variant="outline"
onClick={resetData}
isDisabled={loading}
marginRight="5"
>
Reset
</Button>
<Button
colorScheme="blue"
variant="solid"
isLoading={loading}
onClick={saveData}
isDisabled={!validateData()}
>
<Center h="100%" pt="5%">
Save
</Center>
</Button>
</ModalFooter>
</ModalContent>
</Modal>
</>
);
};
export default UpdateEmailTemplate;

View File

@@ -2,6 +2,8 @@ import React, { useEffect, useState } from 'react';
import {
Button,
Center,
Code,
Collapse,
Flex,
Input,
InputGroup,
@@ -19,20 +21,33 @@ import {
Text,
useDisclosure,
useToast,
Alert,
AlertIcon,
Divider,
} from '@chakra-ui/react';
import { FaMinusCircle, FaPlus } from 'react-icons/fa';
import {
FaAngleDown,
FaAngleUp,
FaMinusCircle,
FaPlus,
FaRegClone,
} from 'react-icons/fa';
import { useClient } from 'urql';
import {
webhookEventNames,
ArrayInputOperations,
WebhookInputDataFields,
WebhookInputHeaderFields,
UpdateWebhookModalViews,
UpdateModalViews,
webhookVerifiedStatus,
webhookPayloadExample,
} from '../constants';
import { capitalizeFirstLetter, validateURI } from '../utils';
import {
capitalizeFirstLetter,
copyTextToClipboard,
validateURI,
} from '../utils';
import { AddWebhook, EditWebhook, TestEndpoint } from '../graphql/mutation';
import { rest } from 'lodash';
import { BiCheckCircle, BiError, BiErrorCircle } from 'react-icons/bi';
interface headersDataType {
@@ -54,7 +69,7 @@ interface selecetdWebhookDataTypes {
}
interface UpdateWebhookModalInputPropTypes {
view: UpdateWebhookModalViews;
view: UpdateModalViews;
selectedWebhook?: selecetdWebhookDataTypes;
fetchWebookData: Function;
}
@@ -82,7 +97,7 @@ interface validatorDataType {
}
const initWebhookData: webhookDataType = {
[WebhookInputDataFields.EVENT_NAME]: webhookEventNames.USER_LOGIN,
[WebhookInputDataFields.EVENT_NAME]: webhookEventNames['User login'],
[WebhookInputDataFields.ENDPOINT]: '',
[WebhookInputDataFields.ENABLED]: true,
[WebhookInputDataFields.HEADERS]: [{ ...initHeadersData }],
@@ -103,6 +118,7 @@ const UpdateWebhookModal = ({
const { isOpen, onOpen, onClose } = useDisclosure();
const [loading, setLoading] = useState<boolean>(false);
const [verifyingEndpoint, setVerifyingEndpoint] = useState<boolean>(false);
const [isShowingPayload, setIsShowingPayload] = useState<boolean>(false);
const [webhook, setWebhook] = useState<webhookDataType>({
...initWebhookData,
});
@@ -110,13 +126,13 @@ const UpdateWebhookModal = ({
...initWebhookValidatorData,
});
const [verifiedStatus, setVerifiedStatus] = useState<webhookVerifiedStatus>(
webhookVerifiedStatus.PENDING
webhookVerifiedStatus.PENDING,
);
const inputChangehandler = (
inputType: string,
value: any,
headerInputType: string = WebhookInputHeaderFields.KEY,
headerIndex: number = 0
headerIndex: number = 0,
) => {
if (
verifiedStatus !== webhookVerifiedStatus.PENDING &&
@@ -222,7 +238,7 @@ const UpdateWebhookModal = ({
validator[WebhookInputDataFields.ENDPOINT] &&
!validator[WebhookInputDataFields.HEADERS].some(
(headerData: headersValidatorDataType) =>
!headerData.key || !headerData.value
!headerData.key || !headerData.value,
)
);
};
@@ -240,7 +256,7 @@ const UpdateWebhookModal = ({
(acc, data) => {
return data.key ? { ...acc, [data.key]: data.value } : acc;
},
{}
{},
);
if (Object.keys(headers).length) {
params[WebhookInputDataFields.HEADERS] = headers;
@@ -254,7 +270,7 @@ const UpdateWebhookModal = ({
const params = getParams();
let res: any = {};
if (
view === UpdateWebhookModalViews.Edit &&
view === UpdateModalViews.Edit &&
selectedWebhook?.[WebhookInputDataFields.ID]
) {
res = await client
@@ -279,7 +295,7 @@ const UpdateWebhookModal = ({
} else if (res.data?._add_webhook || res.data?._update_webhook) {
toast({
title: capitalizeFirstLetter(
res.data?._add_webhook?.message || res.data?._update_webhook?.message
res.data?._add_webhook?.message || res.data?._update_webhook?.message,
),
isClosable: true,
status: 'success',
@@ -292,12 +308,12 @@ const UpdateWebhookModal = ({
setValidator({ ...initWebhookValidatorData });
fetchWebookData();
}
view === UpdateWebhookModalViews.ADD && onClose();
view === UpdateModalViews.ADD && onClose();
};
useEffect(() => {
if (
isOpen &&
view === UpdateWebhookModalViews.Edit &&
view === UpdateModalViews.Edit &&
selectedWebhook &&
Object.keys(selectedWebhook || {}).length
) {
@@ -317,7 +333,7 @@ const UpdateWebhookModal = ({
setValidator({
...validator,
[WebhookInputDataFields.HEADERS]: new Array(
formattedHeadersData.length
formattedHeadersData.length,
)
.fill({})
.map(() => ({ ...initHeadersValidatorData })),
@@ -347,7 +363,7 @@ const UpdateWebhookModal = ({
};
return (
<>
{view === UpdateWebhookModalViews.ADD ? (
{view === UpdateModalViews.ADD ? (
<Button
leftIcon={<FaPlus />}
colorScheme="blue"
@@ -365,9 +381,7 @@ const UpdateWebhookModal = ({
<ModalOverlay />
<ModalContent>
<ModalHeader>
{view === UpdateWebhookModalViews.ADD
? 'Add New Webhook'
: 'Edit Webhook'}
{view === UpdateModalViews.ADD ? 'Add New Webhook' : 'Edit Webhook'}
</ModalHeader>
<ModalCloseButton />
<ModalBody>
@@ -392,7 +406,7 @@ const UpdateWebhookModal = ({
onChange={(e) =>
inputChangehandler(
WebhookInputDataFields.EVENT_NAME,
e.currentTarget.value
e.currentTarget.value,
)
}
>
@@ -401,7 +415,7 @@ const UpdateWebhookModal = ({
<option value={value} key={key}>
{key}
</option>
)
),
)}
</Select>
</Flex>
@@ -424,7 +438,7 @@ const UpdateWebhookModal = ({
onChange={(e) =>
inputChangehandler(
WebhookInputDataFields.ENDPOINT,
e.currentTarget.value
e.currentTarget.value,
)
}
/>
@@ -448,7 +462,7 @@ const UpdateWebhookModal = ({
onChange={() =>
inputChangehandler(
WebhookInputDataFields.ENABLED,
!webhook[WebhookInputDataFields.ENABLED]
!webhook[WebhookInputDataFields.ENABLED],
)
}
/>
@@ -457,11 +471,12 @@ const UpdateWebhookModal = ({
</Text>
</Flex>
</Flex>
<Flex
width="100%"
justifyContent="space-between"
alignItems="center"
marginBottom="2%"
marginBottom="5%"
>
<Flex>Headers</Flex>
<Flex>
@@ -478,7 +493,8 @@ const UpdateWebhookModal = ({
</Button>
</Flex>
</Flex>
<Flex flexDirection="column" maxH={220} overflowY="scroll">
<Flex flexDirection="column" maxH={220} overflowY="auto">
{webhook[WebhookInputDataFields.HEADERS]?.map(
(headerData, index) => (
<Flex
@@ -501,7 +517,7 @@ const UpdateWebhookModal = ({
WebhookInputDataFields.HEADERS,
e.target.value,
WebhookInputHeaderFields.KEY,
index
index,
)
}
width="30%"
@@ -524,7 +540,7 @@ const UpdateWebhookModal = ({
WebhookInputDataFields.HEADERS,
e.target.value,
WebhookInputHeaderFields.VALUE,
index
index,
)
}
width="65%"
@@ -544,9 +560,57 @@ const UpdateWebhookModal = ({
</InputRightElement>
</InputGroup>
</Flex>
)
),
)}
</Flex>
<Divider marginY={5} />
<Alert
status="info"
onClick={() => setIsShowingPayload(!isShowingPayload)}
borderRadius="5"
cursor="pointer"
fontSize="sm"
>
<AlertIcon />
<Flex
width="100%"
justifyContent="space-between"
alignItems="center"
>
Checkout the example payload
{isShowingPayload ? <FaAngleUp /> : <FaAngleDown />}
</Flex>
</Alert>
<Collapse
style={{
marginTop: 10,
width: '100%',
}}
in={isShowingPayload}
>
<Code
width="inherit"
borderRadius={5}
padding={2}
position="relative"
>
<pre style={{ overflow: 'auto' }}>
{webhookPayloadExample}
</pre>
{isShowingPayload && (
<Flex
position="absolute"
top={4}
right={4}
cursor="pointer"
onClick={() => copyTextToClipboard(webhookPayloadExample)}
>
<FaRegClone color="#bfbfbf" />
</Flex>
)}
</Code>
</Collapse>
</Flex>
</ModalBody>
<ModalFooter>

View File

@@ -161,15 +161,15 @@ const ViewWebhookLogsModal = ({
<Td>
<Text fontSize="sm">{`${logData.id.substring(
0,
5
5,
)}***${logData.id.substring(
logData.id.length - 5,
logData.id.length
logData.id.length,
)}`}</Text>
</Td>
<Td>
{dayjs(logData.created_at * 1000).format(
'MMM DD, YYYY'
'MMM DD, YYYY',
)}
</Td>
<Td>

View File

@@ -9,6 +9,7 @@ export const TextInputType = {
FACEBOOK_CLIENT_ID: 'FACEBOOK_CLIENT_ID',
LINKEDIN_CLIENT_ID: 'LINKEDIN_CLIENT_ID',
APPLE_CLIENT_ID: 'APPLE_CLIENT_ID',
TWITTER_CLIENT_ID: 'TWITTER_CLIENT_ID',
JWT_ROLE_CLAIM: 'JWT_ROLE_CLAIM',
REDIS_URL: 'REDIS_URL',
SMTP_HOST: 'SMTP_HOST',
@@ -35,6 +36,7 @@ export const HiddenInputType = {
FACEBOOK_CLIENT_SECRET: 'FACEBOOK_CLIENT_SECRET',
LINKEDIN_CLIENT_SECRET: 'LINKEDIN_CLIENT_SECRET',
APPLE_CLIENT_SECRET: 'APPLE_CLIENT_SECRET',
TWITTER_CLIENT_SECRET: 'TWITTER_CLIENT_SECRET',
JWT_SECRET: 'JWT_SECRET',
SMTP_PASSWORD: 'SMTP_PASSWORD',
ADMIN_SECRET: 'ADMIN_SECRET',
@@ -61,6 +63,8 @@ export const TextAreaInputType = {
};
export const SwitchInputType = {
APP_COOKIE_SECURE: 'APP_COOKIE_SECURE',
ADMIN_COOKIE_SECURE: 'ADMIN_COOKIE_SECURE',
DISABLE_LOGIN_PAGE: 'DISABLE_LOGIN_PAGE',
DISABLE_MAGIC_LINK_LOGIN: 'DISABLE_MAGIC_LINK_LOGIN',
DISABLE_EMAIL_VERIFICATION: 'DISABLE_EMAIL_VERIFICATION',
@@ -68,6 +72,8 @@ export const SwitchInputType = {
DISABLE_SIGN_UP: 'DISABLE_SIGN_UP',
DISABLE_REDIS_FOR_ENV: 'DISABLE_REDIS_FOR_ENV',
DISABLE_STRONG_PASSWORD: 'DISABLE_STRONG_PASSWORD',
DISABLE_MULTI_FACTOR_AUTHENTICATION: 'DISABLE_MULTI_FACTOR_AUTHENTICATION',
ENFORCE_MULTI_FACTOR_AUTHENTICATION: 'ENFORCE_MULTI_FACTOR_AUTHENTICATION',
};
export const DateInputType = {
@@ -108,6 +114,8 @@ export interface envVarTypes {
LINKEDIN_CLIENT_SECRET: string;
APPLE_CLIENT_ID: string;
APPLE_CLIENT_SECRET: string;
TWITTER_CLIENT_ID: string;
TWITTER_CLIENT_SECRET: string;
ROLES: [string] | [];
DEFAULT_ROLES: [string] | [];
PROTECTED_ROLES: [string] | [];
@@ -127,6 +135,8 @@ export interface envVarTypes {
ORGANIZATION_LOGO: string;
CUSTOM_ACCESS_TOKEN_SCRIPT: string;
ADMIN_SECRET: string;
APP_COOKIE_SECURE: boolean;
ADMIN_COOKIE_SECURE: boolean;
DISABLE_LOGIN_PAGE: boolean;
DISABLE_MAGIC_LINK_LOGIN: boolean;
DISABLE_EMAIL_VERIFICATION: boolean;
@@ -138,6 +148,8 @@ export interface envVarTypes {
DATABASE_TYPE: string;
DATABASE_URL: string;
ACCESS_TOKEN_EXPIRY_TIME: string;
DISABLE_MULTI_FACTOR_AUTHENTICATION: boolean;
ENFORCE_MULTI_FACTOR_AUTHENTICATION: boolean;
}
export const envSubViews = {
@@ -162,12 +174,21 @@ export enum WebhookInputDataFields {
HEADERS = 'headers',
}
export enum EmailTemplateInputDataFields {
ID = 'id',
EVENT_NAME = 'event_name',
SUBJECT = 'subject',
CREATED_AT = 'created_at',
TEMPLATE = 'template',
DESIGN = 'design',
}
export enum WebhookInputHeaderFields {
KEY = 'key',
VALUE = 'value',
}
export enum UpdateWebhookModalViews {
export enum UpdateModalViews {
ADD = 'add',
Edit = 'edit',
}
@@ -175,12 +196,21 @@ export enum UpdateWebhookModalViews {
export const pageLimits: number[] = [5, 10, 15];
export const webhookEventNames = {
USER_SIGNUP: 'user.signup',
USER_CREATED: 'user.created',
USER_LOGIN: 'user.login',
USER_DELETED: 'user.deleted',
USER_ACCESS_ENABLED: 'user.access_enabled',
USER_ACCESS_REVOKED: 'user.access_revoked',
'User signup': 'user.signup',
'User created': 'user.created',
'User login': 'user.login',
'User deleted': 'user.deleted',
'User access enabled': 'user.access_enabled',
'User access revoked': 'user.access_revoked',
};
export const emailTemplateEventNames = {
Signup: 'basic_auth_signup',
'Magic Link Login': 'magic_link_login',
'Update Email': 'update_email',
'Forgot Password': 'forgot_password',
'Verify Otp': 'verify_otp',
'Invite member': 'invite_member',
};
export enum webhookVerifiedStatus {
@@ -188,3 +218,117 @@ export enum webhookVerifiedStatus {
NOT_VERIFIED = 'not_verified',
PENDING = 'verification_pending',
}
export const emailTemplateVariables = {
'user.id': {
description: `User identifier`,
value: '{.user.id}}',
},
'user.email': {
description: 'User email address',
value: '{.user.email}}',
},
'user.given_name': {
description: `User first name`,
value: '{.user.given_name}}',
},
'user.family_name': {
description: `User last name`,
value: '{.user.family_name}}',
},
'user.middle_name': {
description: `Middle name of user`,
value: '{.user.middle_name}}',
},
'user.nickname': {
description: `Nick name of user`,
value: '{.user.nickname}}',
},
'user.preferred_username': {
description: `Username, by default it is email`,
value: '{.user.preferred_username}}',
},
'user.signup_methods': {
description: `Comma separated list of methods using which user has signed up`,
value: '{.user.signup_methods}}',
},
'user.email_verified': {
description: `Whether email is verified or not`,
value: '{.user.email_verified}}',
},
'user.picture': {
description: `URL of the user profile picture`,
value: '{.user.picture}}',
},
'user.roles': {
description: `Comma separated list of roles assigned to user`,
value: '{.user.roles}}',
},
'user.gender': {
description: `Gender of user`,
value: '{.user.gender}}',
},
'user.birthdate': {
description: `BirthDate of user`,
value: '{.user.birthdate}}',
},
'user.phone_number': {
description: `Phone number of user`,
value: '{.user.phone_number}}',
},
'user.phone_number_verified': {
description: `Whether phone number is verified or not`,
value: '{.user.phone_number_verified}}',
},
'user.created_at': {
description: `User created at time`,
value: '{.user.created_at}}',
},
'user.updated_at': {
description: `Last updated time at user`,
value: '{.user.updated_at}}',
},
'organization.name': {
description: `Organization name`,
value: '{.organization.name}}',
},
'organization.logo': {
description: `Organization logo`,
value: '{.organization.logo}}',
},
verification_url: {
description: `Verification URL in case of events other than verify otp`,
value: '{.verification_url}}',
},
otp: {
description: `OTP sent during login with Multi factor authentication`,
value: '{.otp}}',
},
};
export const webhookPayloadExample: string = `{
"event_name":"user.login",
"user":{
"birthdate":null,
"created_at":1657524721,
"email":"lakhan.m.samani@gmail.com",
"email_verified":true,
"family_name":"Samani",
"gender":null,
"given_name":"Lakhan",
"id":"466d0b31-1b87-420e-bea5-09d05d79c586",
"middle_name":null,
"nickname":null,
"phone_number":null,
"phone_number_verified":false,
"picture":"https://lh3.googleusercontent.com/a-/AFdZucppvU6a2zIDkX0wvhhapVjT0ZMKDlYCkQDi3NxcUg=s96-c",
"preferred_username":"lakhan.m.samani@gmail.com",
"revoked_timestamp":null,
"roles":[
"user"
],
"signup_methods":"google",
"updated_at":1657526492
},
"auth_recipe":"google"
}`;

View File

@@ -112,3 +112,27 @@ export const TestEndpoint = `
}
}
`;
export const AddEmailTemplate = `
mutation addEmailTemplate($params: AddEmailTemplateRequest!) {
_add_email_template(params: $params) {
message
}
}
`;
export const EditEmailTemplate = `
mutation editEmailTemplate($params: UpdateEmailTemplateRequest!) {
_update_email_template(params: $params) {
message
}
}
`;
export const DeleteEmailTemplate = `
mutation deleteEmailTemplate($params: DeleteEmailTemplateRequest!) {
_delete_email_template(params: $params) {
message
}
}
`;

View File

@@ -18,48 +18,54 @@ export const AdminSessionQuery = `
export const EnvVariablesQuery = `
query {
_env{
CLIENT_ID,
CLIENT_SECRET,
GOOGLE_CLIENT_ID,
GOOGLE_CLIENT_SECRET,
GITHUB_CLIENT_ID,
GITHUB_CLIENT_SECRET,
FACEBOOK_CLIENT_ID,
FACEBOOK_CLIENT_SECRET,
LINKEDIN_CLIENT_ID,
LINKEDIN_CLIENT_SECRET,
APPLE_CLIENT_ID,
APPLE_CLIENT_SECRET,
DEFAULT_ROLES,
PROTECTED_ROLES,
ROLES,
JWT_TYPE,
JWT_SECRET,
JWT_ROLE_CLAIM,
JWT_PRIVATE_KEY,
JWT_PUBLIC_KEY,
REDIS_URL,
SMTP_HOST,
SMTP_PORT,
SMTP_USERNAME,
SMTP_PASSWORD,
SENDER_EMAIL,
ALLOWED_ORIGINS,
ORGANIZATION_NAME,
ORGANIZATION_LOGO,
ADMIN_SECRET,
DISABLE_LOGIN_PAGE,
DISABLE_MAGIC_LINK_LOGIN,
DISABLE_EMAIL_VERIFICATION,
DISABLE_BASIC_AUTHENTICATION,
DISABLE_SIGN_UP,
DISABLE_STRONG_PASSWORD,
DISABLE_REDIS_FOR_ENV,
CUSTOM_ACCESS_TOKEN_SCRIPT,
DATABASE_NAME,
DATABASE_TYPE,
DATABASE_URL,
ACCESS_TOKEN_EXPIRY_TIME,
CLIENT_ID
CLIENT_SECRET
GOOGLE_CLIENT_ID
GOOGLE_CLIENT_SECRET
GITHUB_CLIENT_ID
GITHUB_CLIENT_SECRET
FACEBOOK_CLIENT_ID
FACEBOOK_CLIENT_SECRET
LINKEDIN_CLIENT_ID
LINKEDIN_CLIENT_SECRET
APPLE_CLIENT_ID
APPLE_CLIENT_SECRET
TWITTER_CLIENT_ID
TWITTER_CLIENT_SECRET
DEFAULT_ROLES
PROTECTED_ROLES
ROLES
JWT_TYPE
JWT_SECRET
JWT_ROLE_CLAIM
JWT_PRIVATE_KEY
JWT_PUBLIC_KEY
REDIS_URL
SMTP_HOST
SMTP_PORT
SMTP_USERNAME
SMTP_PASSWORD
SENDER_EMAIL
ALLOWED_ORIGINS
ORGANIZATION_NAME
ORGANIZATION_LOGO
ADMIN_SECRET
APP_COOKIE_SECURE
ADMIN_COOKIE_SECURE
DISABLE_LOGIN_PAGE
DISABLE_MAGIC_LINK_LOGIN
DISABLE_EMAIL_VERIFICATION
DISABLE_BASIC_AUTHENTICATION
DISABLE_SIGN_UP
DISABLE_STRONG_PASSWORD
DISABLE_REDIS_FOR_ENV
CUSTOM_ACCESS_TOKEN_SCRIPT
DATABASE_NAME
DATABASE_TYPE
DATABASE_URL
ACCESS_TOKEN_EXPIRY_TIME
DISABLE_MULTI_FACTOR_AUTHENTICATION
ENFORCE_MULTI_FACTOR_AUTHENTICATION
}
}
`;
@@ -123,6 +129,27 @@ export const WebhooksDataQuery = `
}
`;
export const EmailTemplatesQuery = `
query getEmailTemplates($params: PaginatedInput!) {
_email_templates(params: $params) {
email_templates {
id
event_name
subject
created_at
template
design
}
pagination {
limit
page
offset
total
}
}
}
`;
export const WebhookLogsQuery = `
query getWebhookLogs($params: ListWebhookLogRequest!) {
_webhook_logs(params: $params) {

View File

@@ -6,5 +6,5 @@ ReactDOM.render(
<div>
<App />
</div>,
document.getElementById('root')
document.getElementById('root'),
);

View File

@@ -0,0 +1,348 @@
import React, { useEffect, useState } from 'react';
import { useClient } from 'urql';
import {
Box,
Button,
Center,
Flex,
IconButton,
Menu,
MenuButton,
MenuList,
NumberDecrementStepper,
NumberIncrementStepper,
NumberInput,
NumberInputField,
NumberInputStepper,
Select,
Spinner,
Table,
TableCaption,
Tbody,
Td,
Text,
Th,
Thead,
Tooltip,
Tr,
} from '@chakra-ui/react';
import {
FaAngleDoubleLeft,
FaAngleDoubleRight,
FaAngleDown,
FaAngleLeft,
FaAngleRight,
FaExclamationCircle,
} from 'react-icons/fa';
import UpdateEmailTemplateModal from '../components/UpdateEmailTemplateModal';
import {
pageLimits,
UpdateModalViews,
EmailTemplateInputDataFields,
} from '../constants';
import { EmailTemplatesQuery } from '../graphql/queries';
import dayjs from 'dayjs';
import DeleteEmailTemplateModal from '../components/DeleteEmailTemplateModal';
interface paginationPropTypes {
limit: number;
page: number;
offset: number;
total: number;
maxPages: number;
}
interface EmailTemplateDataType {
[EmailTemplateInputDataFields.ID]: string;
[EmailTemplateInputDataFields.EVENT_NAME]: string;
[EmailTemplateInputDataFields.SUBJECT]: string;
[EmailTemplateInputDataFields.CREATED_AT]: number;
[EmailTemplateInputDataFields.TEMPLATE]: string;
[EmailTemplateInputDataFields.DESIGN]: string;
}
const EmailTemplates = () => {
const client = useClient();
const [loading, setLoading] = useState<boolean>(false);
const [emailTemplatesData, setEmailTemplatesData] = useState<
EmailTemplateDataType[]
>([]);
const [paginationProps, setPaginationProps] = useState<paginationPropTypes>({
limit: 5,
page: 1,
offset: 0,
total: 0,
maxPages: 1,
});
const getMaxPages = (pagination: paginationPropTypes) => {
const { limit, total } = pagination;
if (total > 1) {
return total % limit === 0
? total / limit
: parseInt(`${total / limit}`) + 1;
} else return 1;
};
const fetchEmailTemplatesData = async () => {
setLoading(true);
const res = await client
.query(EmailTemplatesQuery, {
params: {
pagination: {
limit: paginationProps.limit,
page: paginationProps.page,
},
},
})
.toPromise();
if (res.data?._email_templates) {
const { pagination, email_templates: emailTemplates } =
res.data?._email_templates;
const maxPages = getMaxPages(pagination);
if (emailTemplates?.length) {
setEmailTemplatesData(emailTemplates);
setPaginationProps({ ...paginationProps, ...pagination, maxPages });
} else {
if (paginationProps.page !== 1) {
setPaginationProps({
...paginationProps,
...pagination,
maxPages,
page: 1,
});
}
}
}
setLoading(false);
};
const paginationHandler = (value: Record<string, number>) => {
setPaginationProps({ ...paginationProps, ...value });
};
useEffect(() => {
fetchEmailTemplatesData();
}, [paginationProps.page, paginationProps.limit]);
return (
<Box m="5" py="5" px="10" bg="white" rounded="md">
<Flex margin="2% 0" justifyContent="space-between" alignItems="center">
<Text fontSize="md" fontWeight="bold">
Email Templates
</Text>
<UpdateEmailTemplateModal
view={UpdateModalViews.ADD}
fetchEmailTemplatesData={fetchEmailTemplatesData}
/>
</Flex>
{!loading ? (
emailTemplatesData.length ? (
<Table variant="simple">
<Thead>
<Tr>
<Th>Event Name</Th>
<Th>Subject</Th>
<Th>Created At</Th>
<Th>Actions</Th>
</Tr>
</Thead>
<Tbody>
{emailTemplatesData.map((templateData: EmailTemplateDataType) => (
<Tr
key={templateData[EmailTemplateInputDataFields.ID]}
style={{ fontSize: 14 }}
>
<Td maxW="300">
{templateData[EmailTemplateInputDataFields.EVENT_NAME]}
</Td>
<Td>{templateData[EmailTemplateInputDataFields.SUBJECT]}</Td>
<Td>
{dayjs(templateData.created_at * 1000).format(
'MMM DD, YYYY',
)}
</Td>
<Td>
<Menu>
<MenuButton as={Button} variant="unstyled" size="sm">
<Flex
justifyContent="space-between"
alignItems="center"
>
<Text fontSize="sm" fontWeight="light">
Menu
</Text>
<FaAngleDown style={{ marginLeft: 10 }} />
</Flex>
</MenuButton>
<MenuList>
<UpdateEmailTemplateModal
view={UpdateModalViews.Edit}
selectedTemplate={templateData}
fetchEmailTemplatesData={fetchEmailTemplatesData}
/>
<DeleteEmailTemplateModal
emailTemplateId={
templateData[EmailTemplateInputDataFields.ID]
}
eventName={
templateData[
EmailTemplateInputDataFields.EVENT_NAME
]
}
fetchEmailTemplatesData={fetchEmailTemplatesData}
/>
</MenuList>
</Menu>
</Td>
</Tr>
))}
</Tbody>
{(paginationProps.maxPages > 1 || paginationProps.total >= 5) && (
<TableCaption>
<Flex
justifyContent="space-between"
alignItems="center"
m="2% 0"
>
<Flex flex="1">
<Tooltip label="First Page">
<IconButton
aria-label="icon button"
onClick={() =>
paginationHandler({
page: 1,
})
}
isDisabled={paginationProps.page <= 1}
mr={4}
icon={<FaAngleDoubleLeft />}
/>
</Tooltip>
<Tooltip label="Previous Page">
<IconButton
aria-label="icon button"
onClick={() =>
paginationHandler({
page: paginationProps.page - 1,
})
}
isDisabled={paginationProps.page <= 1}
icon={<FaAngleLeft />}
/>
</Tooltip>
</Flex>
<Flex
flex="8"
justifyContent="space-evenly"
alignItems="center"
>
<Text mr={8}>
Page{' '}
<Text fontWeight="bold" as="span">
{paginationProps.page}
</Text>{' '}
of{' '}
<Text fontWeight="bold" as="span">
{paginationProps.maxPages}
</Text>
</Text>
<Flex alignItems="center">
<Text flexShrink="0">Go to page:</Text>{' '}
<NumberInput
ml={2}
mr={8}
w={28}
min={1}
max={paginationProps.maxPages}
onChange={(value) =>
paginationHandler({
page: parseInt(value),
})
}
value={paginationProps.page}
>
<NumberInputField />
<NumberInputStepper>
<NumberIncrementStepper />
<NumberDecrementStepper />
</NumberInputStepper>
</NumberInput>
</Flex>
<Select
w={32}
value={paginationProps.limit}
onChange={(e) =>
paginationHandler({
page: 1,
limit: parseInt(e.target.value),
})
}
>
{pageLimits.map((pageSize) => (
<option key={pageSize} value={pageSize}>
Show {pageSize}
</option>
))}
</Select>
</Flex>
<Flex flex="1">
<Tooltip label="Next Page">
<IconButton
aria-label="icon button"
onClick={() =>
paginationHandler({
page: paginationProps.page + 1,
})
}
isDisabled={
paginationProps.page >= paginationProps.maxPages
}
icon={<FaAngleRight />}
/>
</Tooltip>
<Tooltip label="Last Page">
<IconButton
aria-label="icon button"
onClick={() =>
paginationHandler({
page: paginationProps.maxPages,
})
}
isDisabled={
paginationProps.page >= paginationProps.maxPages
}
ml={4}
icon={<FaAngleDoubleRight />}
/>
</Tooltip>
</Flex>
</Flex>
</TableCaption>
)}
</Table>
) : (
<Flex
flexDirection="column"
minH="25vh"
justifyContent="center"
alignItems="center"
>
<Center w="50px" marginRight="1.5%">
<FaExclamationCircle style={{ color: '#f0f0f0', fontSize: 70 }} />
</Center>
<Text
fontSize="2xl"
paddingRight="1%"
fontWeight="bold"
color="#d9d9d9"
>
No Data
</Text>
</Flex>
)
) : (
<Center minH="25vh">
<Spinner />
</Center>
)}
</Box>
);
};
export default EmailTemplates;

View File

@@ -50,6 +50,8 @@ const Environment = () => {
LINKEDIN_CLIENT_SECRET: '',
APPLE_CLIENT_ID: '',
APPLE_CLIENT_SECRET: '',
TWITTER_CLIENT_ID: '',
TWITTER_CLIENT_SECRET: '',
ROLES: [],
DEFAULT_ROLES: [],
PROTECTED_ROLES: [],
@@ -69,6 +71,8 @@ const Environment = () => {
ORGANIZATION_LOGO: '',
CUSTOM_ACCESS_TOKEN_SCRIPT: '',
ADMIN_SECRET: '',
APP_COOKIE_SECURE: false,
ADMIN_COOKIE_SECURE: false,
DISABLE_LOGIN_PAGE: false,
DISABLE_MAGIC_LINK_LOGIN: false,
DISABLE_EMAIL_VERIFICATION: false,
@@ -80,6 +84,8 @@ const Environment = () => {
DATABASE_TYPE: '',
DATABASE_URL: '',
ACCESS_TOKEN_EXPIRY_TIME: '',
DISABLE_MULTI_FACTOR_AUTHENTICATION: false,
ENFORCE_MULTI_FACTOR_AUTHENTICATION: false,
});
const [fieldVisibility, setFieldVisibility] = React.useState<
@@ -90,6 +96,7 @@ const Environment = () => {
FACEBOOK_CLIENT_SECRET: false,
LINKEDIN_CLIENT_SECRET: false,
APPLE_CLIENT_SECRET: false,
TWITTER_CLIENT_SECRET: false,
JWT_SECRET: false,
SMTP_PASSWORD: false,
ADMIN_SECRET: false,
@@ -150,7 +157,7 @@ const Environment = () => {
// @ts-ignore
[property]: envVariables[property],
}),
{}
{},
);
if (
updatedEnvVariables[HiddenInputType.ADMIN_SECRET] === '' ||

View File

@@ -29,6 +29,7 @@ import {
MenuItem,
useToast,
Spinner,
TableContainer,
} from '@chakra-ui/react';
import {
FaAngleLeft,
@@ -194,7 +195,7 @@ export default function Users() {
const updateAccessHandler = async (
id: string,
action: updateAccessActions
action: updateAccessActions,
) => {
switch (action) {
case updateAccessActions.ENABLE:
@@ -293,256 +294,266 @@ export default function Users() {
</Flex>
{!loading ? (
userList.length > 0 ? (
<Table variant="simple">
<Thead>
<Tr>
<Th>Email</Th>
<Th>Created At</Th>
<Th>Signup Methods</Th>
<Th>Roles</Th>
<Th>Verified</Th>
<Th>Access</Th>
<Th>MFA</Th>
<Th>Actions</Th>
</Tr>
</Thead>
<Tbody>
{userList.map((user: userDataTypes) => {
const { email_verified, created_at, ...rest }: any = user;
return (
<Tr key={user.id} style={{ fontSize: 14 }}>
<Td maxW="300">{user.email}</Td>
<Td>
{dayjs(user.created_at * 1000).format('MMM DD, YYYY')}
</Td>
<Td>{user.signup_methods}</Td>
<Td>{user.roles.join(', ')}</Td>
<Td>
<Tag
size="sm"
variant="outline"
colorScheme={user.email_verified ? 'green' : 'yellow'}
>
{user.email_verified.toString()}
</Tag>
</Td>
<Td>
<Tag
size="sm"
variant="outline"
colorScheme={user.revoked_timestamp ? 'red' : 'green'}
>
{user.revoked_timestamp ? 'Revoked' : 'Enabled'}
</Tag>
</Td>
<Td>
<Tag
size="sm"
variant="outline"
colorScheme={
user.is_multi_factor_auth_enabled ? 'green' : 'red'
}
>
{user.is_multi_factor_auth_enabled
? 'Enabled'
: 'Disabled'}
</Tag>
</Td>
<Td>
<Menu>
<MenuButton as={Button} variant="unstyled" size="sm">
<Flex
justifyContent="space-between"
alignItems="center"
>
<Text fontSize="sm" fontWeight="light">
Menu
</Text>
<FaAngleDown style={{ marginLeft: 10 }} />
</Flex>
</MenuButton>
<MenuList>
{!user.email_verified && (
<MenuItem
onClick={() => userVerificationHandler(user)}
<TableContainer>
<Table variant="simple">
<Thead>
<Tr>
<Th>Email</Th>
<Th>Created At</Th>
<Th>Signup Methods</Th>
<Th>Roles</Th>
<Th>Verified</Th>
<Th>Access</Th>
<Th>
<Tooltip label="MultiFactor Authentication Enabled / Disabled">
MFA
</Tooltip>
</Th>
<Th>Actions</Th>
</Tr>
</Thead>
<Tbody>
{userList.map((user: userDataTypes) => {
const { email_verified, created_at, ...rest }: any = user;
return (
<Tr key={user.id} style={{ fontSize: 14 }}>
<Td maxW="300">{user.email}</Td>
<Td>
{dayjs(user.created_at * 1000).format('MMM DD, YYYY')}
</Td>
<Td>{user.signup_methods}</Td>
<Td>{user.roles.join(', ')}</Td>
<Td>
<Tag
size="sm"
variant="outline"
colorScheme={user.email_verified ? 'green' : 'yellow'}
>
{user.email_verified.toString()}
</Tag>
</Td>
<Td>
<Tag
size="sm"
variant="outline"
colorScheme={user.revoked_timestamp ? 'red' : 'green'}
>
{user.revoked_timestamp ? 'Revoked' : 'Enabled'}
</Tag>
</Td>
<Td>
<Tag
size="sm"
variant="outline"
colorScheme={
user.is_multi_factor_auth_enabled ? 'green' : 'red'
}
>
{user.is_multi_factor_auth_enabled
? 'Enabled'
: 'Disabled'}
</Tag>
</Td>
<Td>
<Menu>
<MenuButton as={Button} variant="unstyled" size="sm">
<Flex
justifyContent="space-between"
alignItems="center"
>
Verify User
</MenuItem>
)}
<EditUserModal
user={rest}
updateUserList={updateUserList}
/>
<DeleteUserModal
user={rest}
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>
)}
{user.is_multi_factor_auth_enabled ? (
<MenuItem
onClick={() => multiFactorAuthUpdateHandler(user)}
>
Disable MFA
</MenuItem>
) : (
<MenuItem
onClick={() => multiFactorAuthUpdateHandler(user)}
>
Enable MFA
</MenuItem>
)}
</MenuList>
</Menu>
</Td>
</Tr>
);
})}
</Tbody>
{(paginationProps.maxPages > 1 || paginationProps.total >= 5) && (
<TableCaption>
<Flex
justifyContent="space-between"
alignItems="center"
m="2% 0"
>
<Flex flex="1">
<Tooltip label="First Page">
<IconButton
aria-label="icon button"
onClick={() =>
<Text fontSize="sm" fontWeight="light">
Menu
</Text>
<FaAngleDown style={{ marginLeft: 10 }} />
</Flex>
</MenuButton>
<MenuList>
{!user.email_verified && (
<MenuItem
onClick={() => userVerificationHandler(user)}
>
Verify User
</MenuItem>
)}
<EditUserModal
user={rest}
updateUserList={updateUserList}
/>
<DeleteUserModal
user={rest}
updateUserList={updateUserList}
/>
{user.revoked_timestamp ? (
<MenuItem
onClick={() =>
updateAccessHandler(
user.id,
updateAccessActions.ENABLE,
)
}
>
Enable Access
</MenuItem>
) : (
<MenuItem
onClick={() =>
updateAccessHandler(
user.id,
updateAccessActions.REVOKE,
)
}
>
Revoke Access
</MenuItem>
)}
{user.is_multi_factor_auth_enabled ? (
<MenuItem
onClick={() =>
multiFactorAuthUpdateHandler(user)
}
>
Disable MultiFactor Authentication
</MenuItem>
) : (
<MenuItem
onClick={() =>
multiFactorAuthUpdateHandler(user)
}
>
Enable MultiFactor Authentication
</MenuItem>
)}
</MenuList>
</Menu>
</Td>
</Tr>
);
})}
</Tbody>
{(paginationProps.maxPages > 1 || paginationProps.total >= 5) && (
<TableCaption>
<Flex
justifyContent="space-between"
alignItems="center"
m="2% 0"
>
<Flex flex="1">
<Tooltip label="First Page">
<IconButton
aria-label="icon button"
onClick={() =>
paginationHandler({
page: 1,
})
}
isDisabled={paginationProps.page <= 1}
mr={4}
icon={<FaAngleDoubleLeft />}
/>
</Tooltip>
<Tooltip label="Previous Page">
<IconButton
aria-label="icon button"
onClick={() =>
paginationHandler({
page: paginationProps.page - 1,
})
}
isDisabled={paginationProps.page <= 1}
icon={<FaAngleLeft />}
/>
</Tooltip>
</Flex>
<Flex
flex="8"
justifyContent="space-evenly"
alignItems="center"
>
<Text mr={8}>
Page{' '}
<Text fontWeight="bold" as="span">
{paginationProps.page}
</Text>{' '}
of{' '}
<Text fontWeight="bold" as="span">
{paginationProps.maxPages}
</Text>
</Text>
<Flex alignItems="center">
<Text flexShrink="0">Go to page:</Text>{' '}
<NumberInput
ml={2}
mr={8}
w={28}
min={1}
max={paginationProps.maxPages}
onChange={(value) =>
paginationHandler({
page: parseInt(value),
})
}
value={paginationProps.page}
>
<NumberInputField />
<NumberInputStepper>
<NumberIncrementStepper />
<NumberDecrementStepper />
</NumberInputStepper>
</NumberInput>
</Flex>
<Select
w={32}
value={paginationProps.limit}
onChange={(e) =>
paginationHandler({
page: 1,
limit: parseInt(e.target.value),
})
}
isDisabled={paginationProps.page <= 1}
mr={4}
icon={<FaAngleDoubleLeft />}
/>
</Tooltip>
<Tooltip label="Previous Page">
<IconButton
aria-label="icon button"
onClick={() =>
paginationHandler({
page: paginationProps.page - 1,
})
}
isDisabled={paginationProps.page <= 1}
icon={<FaAngleLeft />}
/>
</Tooltip>
</Flex>
<Flex
flex="8"
justifyContent="space-evenly"
alignItems="center"
>
<Text mr={8}>
Page{' '}
<Text fontWeight="bold" as="span">
{paginationProps.page}
</Text>{' '}
of{' '}
<Text fontWeight="bold" as="span">
{paginationProps.maxPages}
</Text>
</Text>
<Flex alignItems="center">
<Text flexShrink="0">Go to page:</Text>{' '}
<NumberInput
ml={2}
mr={8}
w={28}
min={1}
max={paginationProps.maxPages}
onChange={(value) =>
paginationHandler({
page: parseInt(value),
})
}
value={paginationProps.page}
>
<NumberInputField />
<NumberInputStepper>
<NumberIncrementStepper />
<NumberDecrementStepper />
</NumberInputStepper>
</NumberInput>
{getLimits(paginationProps).map((pageSize) => (
<option key={pageSize} value={pageSize}>
Show {pageSize}
</option>
))}
</Select>
</Flex>
<Flex flex="1">
<Tooltip label="Next Page">
<IconButton
aria-label="icon button"
onClick={() =>
paginationHandler({
page: paginationProps.page + 1,
})
}
isDisabled={
paginationProps.page >= paginationProps.maxPages
}
icon={<FaAngleRight />}
/>
</Tooltip>
<Tooltip label="Last Page">
<IconButton
aria-label="icon button"
onClick={() =>
paginationHandler({
page: paginationProps.maxPages,
})
}
isDisabled={
paginationProps.page >= paginationProps.maxPages
}
ml={4}
icon={<FaAngleDoubleRight />}
/>
</Tooltip>
</Flex>
<Select
w={32}
value={paginationProps.limit}
onChange={(e) =>
paginationHandler({
page: 1,
limit: parseInt(e.target.value),
})
}
>
{getLimits(paginationProps).map((pageSize) => (
<option key={pageSize} value={pageSize}>
Show {pageSize}
</option>
))}
</Select>
</Flex>
<Flex flex="1">
<Tooltip label="Next Page">
<IconButton
aria-label="icon button"
onClick={() =>
paginationHandler({
page: paginationProps.page + 1,
})
}
isDisabled={
paginationProps.page >= paginationProps.maxPages
}
icon={<FaAngleRight />}
/>
</Tooltip>
<Tooltip label="Last Page">
<IconButton
aria-label="icon button"
onClick={() =>
paginationHandler({
page: paginationProps.maxPages,
})
}
isDisabled={
paginationProps.page >= paginationProps.maxPages
}
ml={4}
icon={<FaAngleDoubleRight />}
/>
</Tooltip>
</Flex>
</Flex>
</TableCaption>
)}
</Table>
</TableCaption>
)}
</Table>
</TableContainer>
) : (
<Flex
flexDirection="column"

View File

@@ -8,7 +8,6 @@ import {
IconButton,
Menu,
MenuButton,
MenuItem,
MenuList,
NumberDecrementStepper,
NumberIncrementStepper,
@@ -40,7 +39,7 @@ import UpdateWebhookModal from '../components/UpdateWebhookModal';
import {
pageLimits,
WebhookInputDataFields,
UpdateWebhookModalViews,
UpdateModalViews,
} from '../constants';
import { WebhooksDataQuery } from '../graphql/queries';
import DeleteWebhookModal from '../components/DeleteWebhookModal';
@@ -125,7 +124,7 @@ const Webhooks = () => {
Webhooks
</Text>
<UpdateWebhookModal
view={UpdateWebhookModalViews.ADD}
view={UpdateModalViews.ADD}
fetchWebookData={fetchWebookData}
/>
</Flex>
@@ -171,12 +170,12 @@ const Webhooks = () => {
label={JSON.stringify(
webhook[WebhookInputDataFields.HEADERS],
null,
' '
' ',
)}
>
<Tag size="sm" variant="outline" colorScheme="gray">
{Object.keys(
webhook[WebhookInputDataFields.HEADERS] || {}
webhook[WebhookInputDataFields.HEADERS] || {},
)?.length.toString()}
</Tag>
</Tooltip>
@@ -196,7 +195,7 @@ const Webhooks = () => {
</MenuButton>
<MenuList>
<UpdateWebhookModal
view={UpdateWebhookModalViews.Edit}
view={UpdateModalViews.Edit}
selectedWebhook={webhook}
fetchWebookData={fetchWebookData}
/>

View File

@@ -3,6 +3,7 @@ import { Outlet, Route, Routes } from 'react-router-dom';
import { useAuthContext } from '../contexts/AuthContext';
import { DashboardLayout } from '../layouts/DashboardLayout';
import EmailTemplates from '../pages/EmailTemplates';
const Auth = lazy(() => import('../pages/Auth'));
const Environment = lazy(() => import('../pages/Environment'));
@@ -31,6 +32,7 @@ export const AppRoutes = () => {
</Route>
<Route path="users" element={<Users />} />
<Route path="webhooks" element={<Webhooks />} />
<Route path="email-templates" element={<EmailTemplates />} />
<Route path="*" element={<Home />} />
</Route>
</Routes>

View File

@@ -29,19 +29,16 @@ const fallbackCopyTextToClipboard = (text: string) => {
document.body.removeChild(textArea);
};
export const copyTextToClipboard = (text: string) => {
export const copyTextToClipboard = async (text: string) => {
if (!navigator.clipboard) {
fallbackCopyTextToClipboard(text);
return;
}
navigator.clipboard.writeText(text).then(
() => {
console.log('Async: Copying to clipboard was successful!');
},
(err) => {
console.error('Async: Could not copy text: ', err);
}
);
try {
navigator.clipboard.writeText(text);
} catch (err) {
throw err;
}
};
export const getObjectDiff = (obj1: any, obj2: any) => {
@@ -70,7 +67,7 @@ export const validateEmail = (email: string) => {
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,}))$/
/^(([^<>()[\]\\.,;:\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;
@@ -81,7 +78,7 @@ export const validateURI = (uri: string) => {
return uri
.toLowerCase()
.match(
/(?:^|\s)((https?:\/\/)?(?:localhost|[\w-]+(?:\.[\w-]+)+)(:\d+)?(\/\S*)?)/
/(?:^|\s)((https?:\/\/)?(?:localhost|[\w-]+(?:\.[\w-]+)+)(:\d+)?(\/\S*)?)/,
)
? true
: false;

View File

@@ -27,7 +27,7 @@ const parseCSV = (file: File, delimiter: string): Promise<dataTypes[]> => {
value: email.trim(),
isInvalid: !validateEmail(email.trim()),
};
})
}),
);
};

View File

@@ -15,4 +15,6 @@ const (
AuthRecipeMethodLinkedIn = "linkedin"
// AuthRecipeMethodApple is the apple auth method
AuthRecipeMethodApple = "apple"
// AuthRecipeMethodTwitter is the twitter auth method
AuthRecipeMethodTwitter = "twitter"
)

View File

@@ -49,6 +49,10 @@ const (
EnvKeySenderEmail = "SENDER_EMAIL"
// EnvKeyIsEmailServiceEnabled key for env variable IS_EMAIL_SERVICE_ENABLED
EnvKeyIsEmailServiceEnabled = "IS_EMAIL_SERVICE_ENABLED"
// EnvKeyAppCookieSecure key for env variable APP_COOKIE_SECURE
EnvKeyAppCookieSecure = "APP_COOKIE_SECURE"
// EnvKeyAdminCookieSecure key for env variable ADMIN_COOKIE_SECURE
EnvKeyAdminCookieSecure = "ADMIN_COOKIE_SECURE"
// EnvKeyJwtType key for env variable JWT_TYPE
EnvKeyJwtType = "JWT_TYPE"
// EnvKeyJwtSecret key for env variable JWT_SECRET
@@ -85,6 +89,10 @@ const (
EnvKeyAppleClientID = "APPLE_CLIENT_ID"
// EnvKeyAppleClientSecret key for env variable APPLE_CLIENT_SECRET
EnvKeyAppleClientSecret = "APPLE_CLIENT_SECRET"
// EnvKeyTwitterClientID key for env variable TWITTER_CLIENT_ID
EnvKeyTwitterClientID = "TWITTER_CLIENT_ID"
// EnvKeyTwitterClientSecret key for env variable TWITTER_CLIENT_SECRET
EnvKeyTwitterClientSecret = "TWITTER_CLIENT_SECRET"
// EnvKeyOrganizationName key for env variable ORGANIZATION_NAME
EnvKeyOrganizationName = "ORGANIZATION_NAME"
// EnvKeyOrganizationLogo key for env variable ORGANIZATION_LOGO

View File

@@ -0,0 +1,19 @@
package constants
const (
// - query: for Authorization Code grant. 302 Found triggers redirect.
ResponseModeQuery = "query"
// - fragment: for Implicit grant. 302 Found triggers redirect.
ResponseModeFragment = "fragment"
// - form_post: 200 OK with response parameters embedded in an HTML form as hidden parameters.
ResponseModeFormPost = "form_post"
// - web_message: For Silent Authentication. Uses HTML5 web messaging.
ResponseModeWebMessage = "web_message"
// For the Authorization Code grant, use response_type=code to include the authorization code.
ResponseTypeCode = "code"
// For the Implicit grant, use response_type=token to include an access token.
ResponseTypeToken = "token"
// For the Implicit grant of id_token, use response_type=id_token to include an identifier token.
ResponseTypeIDToken = "id_token"
)

View File

@@ -9,9 +9,11 @@ const (
// Ref: https://docs.github.com/en/developers/apps/building-github-apps/identifying-and-authorizing-users-for-github-apps#3-your-github-app-accesses-the-api-with-the-users-access-token
GithubUserInfoURL = "https://api.github.com/user"
// Get github user emails when user info email is empty Ref: https://stackoverflow.com/a/35387123
GithubUserEmails = "https://api/github.com/user/emails"
GithubUserEmails = "https://api.github.com/user/emails"
// Ref: https://docs.microsoft.com/en-us/linkedin/shared/integrations/people/profile-api
LinkedInUserInfoURL = "https://api.linkedin.com/v2/me?projection=(id,localizedFirstName,localizedLastName,emailAddress,profilePicture(displayImage~:playableStreams))"
LinkedInEmailURL = "https://api.linkedin.com/v2/emailAddress?q=members&projection=(elements*(handle~))"
TwitterUserInfoURL = "https://api.twitter.com/2/users/me?user.fields=id,name,profile_image_url,username"
)

View File

@@ -9,4 +9,19 @@ const (
VerificationTypeUpdateEmail = "update_email"
// VerificationTypeForgotPassword is the forgot_password verification type
VerificationTypeForgotPassword = "forgot_password"
// VerificationTypeInviteMember is the invite_member verification type
VerificationTypeInviteMember = "invite_member"
// VerificationTypeOTP is the otp verification type
VerificationTypeOTP = "verify_otp"
)
var (
// VerificationTypes is slice of all verification types
VerificationTypes = []string{
VerificationTypeBasicAuthSignup,
VerificationTypeMagicLinkLogin,
VerificationTypeUpdateEmail,
VerificationTypeForgotPassword,
VerificationTypeInviteMember,
}
)

View File

@@ -3,15 +3,24 @@ package cookie
import (
"net/url"
log "github.com/sirupsen/logrus"
"github.com/authorizerdev/authorizer/server/constants"
"github.com/authorizerdev/authorizer/server/memorystore"
"github.com/authorizerdev/authorizer/server/parsers"
"github.com/gin-gonic/gin"
)
// SetAdminCookie sets the admin cookie in the response
func SetAdminCookie(gc *gin.Context, token string) {
secure := true
httpOnly := true
adminCookieSecure, err := memorystore.Provider.GetBoolStoreEnvVariable(constants.EnvKeyAdminCookieSecure)
if err != nil {
log.Debug("Error while getting admin cookie secure from env variable: %v", err)
adminCookieSecure = true
}
secure := adminCookieSecure
httpOnly := adminCookieSecure
hostname := parsers.GetHost(gc)
host, _ := parsers.GetHostParts(hostname)
gc.SetCookie(constants.AdminCookieName, token, 3600, "/", host, secure, httpOnly)
@@ -35,8 +44,14 @@ func GetAdminCookie(gc *gin.Context) (string, error) {
// DeleteAdminCookie sets the response cookie to empty
func DeleteAdminCookie(gc *gin.Context) {
secure := true
httpOnly := true
adminCookieSecure, err := memorystore.Provider.GetBoolStoreEnvVariable(constants.EnvKeyAdminCookieSecure)
if err != nil {
log.Debug("Error while getting admin cookie secure from env variable: %v", err)
adminCookieSecure = true
}
secure := adminCookieSecure
httpOnly := adminCookieSecure
hostname := parsers.GetHost(gc)
host, _ := parsers.GetHostParts(hostname)
gc.SetCookie(constants.AdminCookieName, "", -1, "/", host, secure, httpOnly)

View File

@@ -4,15 +4,24 @@ import (
"net/http"
"net/url"
log "github.com/sirupsen/logrus"
"github.com/authorizerdev/authorizer/server/constants"
"github.com/authorizerdev/authorizer/server/memorystore"
"github.com/authorizerdev/authorizer/server/parsers"
"github.com/gin-gonic/gin"
)
// SetSession sets the session cookie in the response
func SetSession(gc *gin.Context, sessionID string) {
secure := true
httpOnly := true
appCookieSecure, err := memorystore.Provider.GetBoolStoreEnvVariable(constants.EnvKeyAppCookieSecure)
if err != nil {
log.Debug("Error while getting app cookie secure from env variable: %v", err)
appCookieSecure = true
}
secure := appCookieSecure
httpOnly := appCookieSecure
hostname := parsers.GetHost(gc)
host, _ := parsers.GetHostParts(hostname)
domain := parsers.GetDomainName(hostname)
@@ -20,18 +29,34 @@ func SetSession(gc *gin.Context, sessionID string) {
domain = "." + domain
}
// Since app cookie can come from cross site it becomes important to set this in lax mode when insecure.
// Example person using custom UI on their app domain and making request to authorizer domain.
// For more information check:
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie/SameSite
// https://github.com/gin-gonic/gin/blob/master/context.go#L86
// TODO add ability to sameSite = none / strict from dashboard
if !appCookieSecure {
gc.SetSameSite(http.SameSiteLaxMode)
} else {
gc.SetSameSite(http.SameSiteNoneMode)
}
// TODO allow configuring from dashboard
year := 60 * 60 * 24 * 365
gc.SetSameSite(http.SameSiteNoneMode)
gc.SetCookie(constants.AppCookieName+"_session", sessionID, year, "/", host, secure, httpOnly)
gc.SetCookie(constants.AppCookieName+"_session_domain", sessionID, year, "/", domain, secure, httpOnly)
}
// DeleteSession sets session cookies to expire
func DeleteSession(gc *gin.Context) {
secure := true
httpOnly := true
appCookieSecure, err := memorystore.Provider.GetBoolStoreEnvVariable(constants.EnvKeyAppCookieSecure)
if err != nil {
log.Debug("Error while getting app cookie secure from env variable: %v", err)
appCookieSecure = true
}
secure := appCookieSecure
httpOnly := appCookieSecure
hostname := parsers.GetHost(gc)
host, _ := parsers.GetHostParts(hostname)
domain := parsers.GetDomainName(hostname)

View File

@@ -12,7 +12,9 @@ type EmailTemplate struct {
Key string `json:"_key,omitempty" bson:"_key,omitempty" cql:"_key,omitempty"` // for arangodb
ID string `gorm:"primaryKey;type:char(36)" json:"_id" bson:"_id" cql:"id"`
EventName string `gorm:"unique" json:"event_name" bson:"event_name" cql:"event_name"`
Template string `gorm:"type:text" json:"template" bson:"template" cql:"template"`
Subject string `json:"subject" bson:"subject" cql:"subject"`
Template string `json:"template" bson:"template" cql:"template"`
Design string `json:"design" bson:"design" cql:"design"`
CreatedAt int64 `json:"created_at" bson:"created_at" cql:"created_at"`
UpdatedAt int64 `json:"updated_at" bson:"updated_at" cql:"updated_at"`
}
@@ -26,7 +28,9 @@ func (e *EmailTemplate) AsAPIEmailTemplate() *model.EmailTemplate {
return &model.EmailTemplate{
ID: id,
EventName: e.EventName,
Subject: e.Subject,
Template: e.Template,
Design: e.Design,
CreatedAt: refs.NewInt64Ref(e.CreatedAt),
UpdatedAt: refs.NewInt64Ref(e.UpdatedAt),
}

View File

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

View File

@@ -1,6 +1,7 @@
package models
import (
"encoding/json"
"strings"
"github.com/authorizerdev/authorizer/server/graph/model"
@@ -16,7 +17,7 @@ type User struct {
Email string `gorm:"unique" json:"email" bson:"email" cql:"email"`
EmailVerifiedAt *int64 `json:"email_verified_at" bson:"email_verified_at" cql:"email_verified_at"`
Password *string `gorm:"type:text" json:"password" bson:"password" cql:"password"`
Password *string `json:"password" bson:"password" cql:"password"`
SignupMethods string `json:"signup_methods" bson:"signup_methods" cql:"signup_methods"`
GivenName *string `json:"given_name" bson:"given_name" cql:"given_name"`
FamilyName *string `json:"family_name" bson:"family_name" cql:"family_name"`
@@ -26,7 +27,7 @@ type User struct {
Birthdate *string `json:"birthdate" bson:"birthdate" cql:"birthdate"`
PhoneNumber *string `gorm:"unique" json:"phone_number" bson:"phone_number" cql:"phone_number"`
PhoneNumberVerifiedAt *int64 `json:"phone_number_verified_at" bson:"phone_number_verified_at" cql:"phone_number_verified_at"`
Picture *string `gorm:"type:text" json:"picture" bson:"picture" cql:"picture"`
Picture *string `json:"picture" bson:"picture" cql:"picture"`
Roles string `json:"roles" bson:"roles" cql:"roles"`
RevokedTimestamp *int64 `json:"revoked_timestamp" bson:"revoked_timestamp" cql:"revoked_timestamp"`
IsMultiFactorAuthEnabled *bool `json:"is_multi_factor_auth_enabled" bson:"is_multi_factor_auth_enabled" cql:"is_multi_factor_auth_enabled"`
@@ -64,3 +65,10 @@ func (user *User) AsAPIUser() *model.User {
UpdatedAt: refs.NewInt64Ref(user.UpdatedAt),
}
}
func (user *User) ToMap() map[string]interface{} {
res := map[string]interface{}{}
data, _ := json.Marshal(user) // Convert to a json string
json.Unmarshal(data, &res) // Convert to a map
return res
}

View File

@@ -13,12 +13,12 @@ import (
type VerificationRequest struct {
Key string `json:"_key,omitempty" bson:"_key" cql:"_key,omitempty"` // for arangodb
ID string `gorm:"primaryKey;type:char(36)" json:"_id" bson:"_id" cql:"id"`
Token string `gorm:"type:text" json:"token" bson:"token" cql:"jwt_token"` // token is reserved keyword in cassandra
Token string `json:"token" bson:"token" cql:"jwt_token"` // token is reserved keyword in cassandra
Identifier string `gorm:"uniqueIndex:idx_email_identifier;type:varchar(64)" json:"identifier" bson:"identifier" cql:"identifier"`
ExpiresAt int64 `json:"expires_at" bson:"expires_at" cql:"expires_at"`
Email string `gorm:"uniqueIndex:idx_email_identifier;type:varchar(256)" json:"email" bson:"email" cql:"email"`
Nonce string `gorm:"type:text" json:"nonce" bson:"nonce" cql:"nonce"`
RedirectURI string `gorm:"type:text" json:"redirect_uri" bson:"redirect_uri" cql:"redirect_uri"`
Nonce string `json:"nonce" bson:"nonce" cql:"nonce"`
RedirectURI string `json:"redirect_uri" bson:"redirect_uri" cql:"redirect_uri"`
CreatedAt int64 `json:"created_at" bson:"created_at" cql:"created_at"`
UpdatedAt int64 `json:"updated_at" bson:"updated_at" cql:"updated_at"`
}

View File

@@ -15,8 +15,8 @@ type Webhook struct {
Key string `json:"_key,omitempty" bson:"_key,omitempty" cql:"_key,omitempty"` // for arangodb
ID string `gorm:"primaryKey;type:char(36)" json:"_id" bson:"_id" cql:"id"`
EventName string `gorm:"unique" json:"event_name" bson:"event_name" cql:"event_name"`
EndPoint string `gorm:"type:text" json:"endpoint" bson:"endpoint" cql:"endpoint"`
Headers string `gorm:"type:text" json:"headers" bson:"headers" cql:"headers"`
EndPoint string `json:"endpoint" bson:"endpoint" cql:"endpoint"`
Headers string `json:"headers" bson:"headers" cql:"headers"`
Enabled bool `json:"enabled" bson:"enabled" cql:"enabled"`
CreatedAt int64 `json:"created_at" bson:"created_at" cql:"created_at"`
UpdatedAt int64 `json:"updated_at" bson:"updated_at" cql:"updated_at"`

View File

@@ -14,8 +14,8 @@ type WebhookLog struct {
Key string `json:"_key,omitempty" bson:"_key,omitempty" cql:"_key,omitempty"` // for arangodb
ID string `gorm:"primaryKey;type:char(36)" json:"_id" bson:"_id" cql:"id"`
HttpStatus int64 `json:"http_status" bson:"http_status" cql:"http_status"`
Response string `gorm:"type:text" json:"response" bson:"response" cql:"response"`
Request string `gorm:"type:text" json:"request" bson:"request" cql:"request"`
Response string `json:"response" bson:"response" cql:"response"`
Request string `json:"request" bson:"request" cql:"request"`
WebhookID string `gorm:"type:char(36)" json:"webhook_id" bson:"webhook_id" cql:"webhook_id"`
CreatedAt int64 `json:"created_at" bson:"created_at" cql:"created_at"`
UpdatedAt int64 `json:"updated_at" bson:"updated_at" cql:"updated_at"`

View File

@@ -29,7 +29,7 @@ func (p *provider) AddEmailTemplate(ctx context.Context, emailTemplate models.Em
return nil, fmt.Errorf("Email template with %s event_name already exists", emailTemplate.EventName)
}
insertQuery := fmt.Sprintf("INSERT INTO %s (id, event_name, template, created_at, updated_at) VALUES ('%s', '%s', '%s', %d, %d)", KeySpace+"."+models.Collections.EmailTemplate, emailTemplate.ID, emailTemplate.EventName, emailTemplate.Template, emailTemplate.CreatedAt, emailTemplate.UpdatedAt)
insertQuery := fmt.Sprintf("INSERT INTO %s (id, event_name, subject, design, template, created_at, updated_at) VALUES ('%s', '%s', '%s','%s','%s', %d, %d)", KeySpace+"."+models.Collections.EmailTemplate, emailTemplate.ID, emailTemplate.EventName, emailTemplate.Subject, emailTemplate.Design, emailTemplate.Template, emailTemplate.CreatedAt, emailTemplate.UpdatedAt)
err := p.db.Query(insertQuery).Exec()
if err != nil {
return nil, err
@@ -103,14 +103,14 @@ func (p *provider) ListEmailTemplate(ctx context.Context, pagination model.Pagin
// there is no offset in cassandra
// so we fetch till limit + offset
// and return the results from offset to limit
query := fmt.Sprintf("SELECT id, event_name, template, created_at, updated_at FROM %s LIMIT %d", KeySpace+"."+models.Collections.EmailTemplate, pagination.Limit+pagination.Offset)
query := fmt.Sprintf("SELECT id, event_name, subject, design, template, created_at, updated_at FROM %s LIMIT %d", KeySpace+"."+models.Collections.EmailTemplate, pagination.Limit+pagination.Offset)
scanner := p.db.Query(query).Iter().Scanner()
counter := int64(0)
for scanner.Next() {
if counter >= pagination.Offset {
var emailTemplate models.EmailTemplate
err := scanner.Scan(&emailTemplate.ID, &emailTemplate.EventName, &emailTemplate.Template, &emailTemplate.CreatedAt, &emailTemplate.UpdatedAt)
err := scanner.Scan(&emailTemplate.ID, &emailTemplate.EventName, &emailTemplate.Subject, &emailTemplate.Design, &emailTemplate.Template, &emailTemplate.CreatedAt, &emailTemplate.UpdatedAt)
if err != nil {
return nil, err
}
@@ -128,8 +128,8 @@ func (p *provider) ListEmailTemplate(ctx context.Context, pagination model.Pagin
// GetEmailTemplateByID to get EmailTemplate by id
func (p *provider) GetEmailTemplateByID(ctx context.Context, emailTemplateID string) (*model.EmailTemplate, error) {
var emailTemplate models.EmailTemplate
query := fmt.Sprintf(`SELECT id, event_name, template, created_at, updated_at FROM %s WHERE id = '%s' LIMIT 1`, KeySpace+"."+models.Collections.EmailTemplate, emailTemplateID)
err := p.db.Query(query).Consistency(gocql.One).Scan(&emailTemplate.ID, &emailTemplate.EventName, &emailTemplate.Template, &emailTemplate.CreatedAt, &emailTemplate.UpdatedAt)
query := fmt.Sprintf(`SELECT id, event_name, subject, design, template, created_at, updated_at FROM %s WHERE id = '%s' LIMIT 1`, KeySpace+"."+models.Collections.EmailTemplate, emailTemplateID)
err := p.db.Query(query).Consistency(gocql.One).Scan(&emailTemplate.ID, &emailTemplate.EventName, &emailTemplate.Subject, &emailTemplate.Design, &emailTemplate.Template, &emailTemplate.CreatedAt, &emailTemplate.UpdatedAt)
if err != nil {
return nil, err
}
@@ -139,8 +139,8 @@ func (p *provider) GetEmailTemplateByID(ctx context.Context, emailTemplateID str
// GetEmailTemplateByEventName to get EmailTemplate by event_name
func (p *provider) GetEmailTemplateByEventName(ctx context.Context, eventName string) (*model.EmailTemplate, error) {
var emailTemplate models.EmailTemplate
query := fmt.Sprintf(`SELECT id, event_name, template, created_at, updated_at FROM %s WHERE event_name = '%s' LIMIT 1 ALLOW FILTERING`, KeySpace+"."+models.Collections.EmailTemplate, eventName)
err := p.db.Query(query).Consistency(gocql.One).Scan(&emailTemplate.ID, &emailTemplate.EventName, &emailTemplate.Template, &emailTemplate.CreatedAt, &emailTemplate.UpdatedAt)
query := fmt.Sprintf(`SELECT id, event_name, subject, design, template, created_at, updated_at FROM %s WHERE event_name = '%s' LIMIT 1 ALLOW FILTERING`, KeySpace+"."+models.Collections.EmailTemplate, eventName)
err := p.db.Query(query).Consistency(gocql.One).Scan(&emailTemplate.ID, &emailTemplate.EventName, &emailTemplate.Subject, &emailTemplate.Design, &emailTemplate.Template, &emailTemplate.CreatedAt, &emailTemplate.UpdatedAt)
if err != nil {
return nil, err
}

View File

@@ -223,6 +223,13 @@ func NewProvider() (*provider, error) {
if err != nil {
return nil, err
}
// add subject on email_templates table
emailTemplateAlterQuery := fmt.Sprintf(`ALTER TABLE %s.%s ADD (subject text, design text);`, KeySpace, models.Collections.EmailTemplate)
err = session.Query(emailTemplateAlterQuery).Exec()
if err != nil {
log.Debug("Failed to alter table as column exists: ", err)
// continue
}
otpCollection := fmt.Sprintf("CREATE TABLE IF NOT EXISTS %s.%s (id text, email text, otp text, expires_at bigint, updated_at bigint, created_at bigint, PRIMARY KEY (id))", KeySpace, models.Collections.OTP)
err = session.Query(otpCollection).Exec()

View File

@@ -2,8 +2,8 @@ package email
import (
"bytes"
"context"
"crypto/tls"
"encoding/json"
"strconv"
"text/template"
@@ -11,27 +11,75 @@ import (
gomail "gopkg.in/mail.v2"
"github.com/authorizerdev/authorizer/server/constants"
"github.com/authorizerdev/authorizer/server/db"
"github.com/authorizerdev/authorizer/server/graph/model"
"github.com/authorizerdev/authorizer/server/memorystore"
)
// addEmailTemplate is used to add html template in email body
func addEmailTemplate(a string, b map[string]interface{}, templateName string) string {
tmpl, err := template.New(templateName).Parse(a)
if err != nil {
output, _ := json.Marshal(b)
return string(output)
func getDefaultTemplate(event string) *model.EmailTemplate {
switch event {
case constants.VerificationTypeBasicAuthSignup, constants.VerificationTypeMagicLinkLogin, constants.VerificationTypeUpdateEmail:
return &model.EmailTemplate{
Subject: emailVerificationSubject,
Template: emailVerificationTemplate,
}
case constants.VerificationTypeForgotPassword:
return &model.EmailTemplate{
Subject: forgotPasswordSubject,
Template: forgotPasswordTemplate,
}
case constants.VerificationTypeInviteMember:
return &model.EmailTemplate{
Subject: inviteEmailSubject,
Template: inviteEmailTemplate,
}
case constants.VerificationTypeOTP:
return &model.EmailTemplate{
Subject: otpEmailSubject,
Template: otpEmailTemplate,
}
default:
return nil
}
buf := &bytes.Buffer{}
err = tmpl.Execute(buf, b)
if err != nil {
panic(err)
}
s := buf.String()
return s
}
// SendMail function to send mail
func SendMail(to []string, Subject, bodyMessage string) error {
func getEmailTemplate(event string, data map[string]interface{}) (*model.EmailTemplate, error) {
ctx := context.Background()
tmp, err := db.Provider.GetEmailTemplateByEventName(ctx, event)
if err != nil || tmp == nil {
tmp = getDefaultTemplate(event)
}
templ, err := template.New(event + "_template.tmpl").Parse(tmp.Template)
if err != nil {
return nil, err
}
buf := &bytes.Buffer{}
err = templ.Execute(buf, data)
if err != nil {
return nil, err
}
templateString := buf.String()
subject, err := template.New(event + "_subject.tmpl").Parse(tmp.Subject)
if err != nil {
return nil, err
}
buf = &bytes.Buffer{}
err = subject.Execute(buf, data)
if err != nil {
return nil, err
}
subjectString := buf.String()
return &model.EmailTemplate{
Template: templateString,
Subject: subjectString,
}, nil
}
// SendEmail function to send mail
func SendEmail(to []string, event string, data map[string]interface{}) error {
// dont trigger email sending in case of test
envKey, err := memorystore.Provider.GetStringStoreEnvVariable(constants.EnvKeyEnv)
if err != nil {
@@ -40,6 +88,13 @@ func SendMail(to []string, Subject, bodyMessage string) error {
if envKey == constants.TestEnv {
return nil
}
tmp, err := getEmailTemplate(event, data)
if err != nil {
log.Errorf("Failed to get event template: ", err)
return err
}
m := gomail.NewMessage()
senderEmail, err := memorystore.Provider.GetStringStoreEnvVariable(constants.EnvKeySenderEmail)
if err != nil {
@@ -79,8 +134,8 @@ func SendMail(to []string, Subject, bodyMessage string) error {
m.SetHeader("From", senderEmail)
m.SetHeader("To", to...)
m.SetHeader("Subject", Subject)
m.SetBody("text/html", bodyMessage)
m.SetHeader("Subject", tmp.Subject)
m.SetBody("text/html", tmp.Template)
port, _ := strconv.Atoi(smtpPort)
d := gomail.NewDialer(smtpHost, port, smtpUsername, smtpPassword)
if !isProd {

View File

@@ -1,19 +1,8 @@
package email
import (
log "github.com/sirupsen/logrus"
"github.com/authorizerdev/authorizer/server/constants"
"github.com/authorizerdev/authorizer/server/memorystore"
)
// SendVerificationMail to send verification email
func SendVerificationMail(toEmail, token, hostname string) error {
// The receiver needs to be in slice as the receive supports multiple receiver
Receiver := []string{toEmail}
Subject := "Please verify your email"
message := `
const (
emailVerificationSubject = "Please verify your email"
emailVerificationTemplate = `
<!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>
@@ -98,23 +87,4 @@ func SendVerificationMail(toEmail, token, hostname string) error {
</body>
</html>
`
data := make(map[string]interface{}, 3)
var err error
data["org_logo"], err = memorystore.Provider.GetStringStoreEnvVariable(constants.EnvKeyOrganizationLogo)
if err != nil {
return err
}
data["org_name"], err = memorystore.Provider.GetStringStoreEnvVariable(constants.EnvKeyOrganizationName)
if err != nil {
return err
}
data["verification_url"] = hostname + "/verify_email?token=" + token
message = addEmailTemplate(message, data, "verify_email.tmpl")
// bodyMessage := sender.WriteHTMLEmail(Receiver, Subject, message)
err = SendMail(Receiver, Subject, message)
if err != nil {
log.Warn("error sending email: ", err)
}
return err
}
)

View File

@@ -1,28 +1,8 @@
package email
import (
"github.com/authorizerdev/authorizer/server/constants"
"github.com/authorizerdev/authorizer/server/memorystore"
)
// SendForgotPasswordMail to send forgot password email
func SendForgotPasswordMail(toEmail, token, hostname string) error {
resetPasswordUrl, err := memorystore.Provider.GetStringStoreEnvVariable(constants.EnvKeyResetPasswordURL)
if err != nil {
return err
}
if resetPasswordUrl == "" {
if err := memorystore.Provider.UpdateEnvVariable(constants.EnvKeyResetPasswordURL, hostname+"/app/reset-password"); err != nil {
return err
}
}
// The receiver needs to be in slice as the receive supports multiple receiver
Receiver := []string{toEmail}
Subject := "Reset Password"
message := `
const (
forgotPasswordSubject = "Reset Password"
forgotPasswordTemplate = `
<!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>
@@ -73,13 +53,13 @@ func SendForgotPasswordMail(toEmail, token, hostname string) error {
<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>
<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="{{.organization.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>Hey there 👋</p>
<p>We have received a request to reset password for email: <b>{{.org_name}}</b>. If this is correct, please reset the password clicking the button below.</p> <br/>
<p>We have received a request to reset password for email: <b>{{.organization.name}}</b>. If this is correct, please reset the password 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;">Reset Password</a>
</td>
</tr>
@@ -106,18 +86,4 @@ func SendForgotPasswordMail(toEmail, token, hostname string) error {
</body>
</html>
`
data := make(map[string]interface{}, 3)
data["org_logo"], err = memorystore.Provider.GetStringStoreEnvVariable(constants.EnvKeyOrganizationLogo)
if err != nil {
return err
}
data["org_name"], err = memorystore.Provider.GetStringStoreEnvVariable(constants.EnvKeyOrganizationName)
if err != nil {
return err
}
data["verification_url"] = resetPasswordUrl + "?token=" + token
message = addEmailTemplate(message, data, "reset_password_email.tmpl")
return SendMail(Receiver, Subject, message)
}
)

View File

@@ -1,19 +1,8 @@
package email
import (
log "github.com/sirupsen/logrus"
"github.com/authorizerdev/authorizer/server/constants"
"github.com/authorizerdev/authorizer/server/memorystore"
)
// 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 := `
const (
inviteEmailSubject = "Please accept the invitation"
inviteEmailTemplate = `
<!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>
@@ -64,13 +53,13 @@ func InviteEmail(toEmail, token, verificationURL, redirectURI string) error {
<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>
<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="{{.organization.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 button below.</p> <br/>
<p>Join us! You are invited to sign-up for <b>{{.organization.name}}</b>. Please accept the invitation by 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>
@@ -98,23 +87,4 @@ func InviteEmail(toEmail, token, verificationURL, redirectURI string) error {
</body>
</html>
`
data := make(map[string]interface{}, 3)
var err error
data["org_logo"], err = memorystore.Provider.GetStringStoreEnvVariable(constants.EnvKeyOrganizationLogo)
if err != nil {
return err
}
data["org_name"], err = memorystore.Provider.GetStringStoreEnvVariable(constants.EnvKeyOrganizationName)
if err != nil {
return err
}
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.Warn("error sending email: ", err)
}
return err
}
)

View File

@@ -1,19 +1,8 @@
package email
import (
log "github.com/sirupsen/logrus"
"github.com/authorizerdev/authorizer/server/constants"
"github.com/authorizerdev/authorizer/server/memorystore"
)
// SendOtpMail to send otp email
func SendOtpMail(toEmail, otp string) error {
// The receiver needs to be in slice as the receive supports multiple receiver
Receiver := []string{toEmail}
Subject := "OTP for your multi factor authentication"
message := `
const (
otpEmailSubject = "OTP for your multi factor authentication"
otpEmailTemplate = `
<!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>
@@ -64,13 +53,13 @@ func SendOtpMail(toEmail, otp string) error {
<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>
<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="{{.organization.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>Hey there 👋</p>
<b>{{.otp}}</b> is your one time password (OTP) for accessing {{.org_name}}. Please keep your OTP confidential and it will expire in 1 minute.
<b>{{.otp}}</b> is your one time password (OTP) for accessing {{.organization.name}}. Please keep your OTP confidential and it will expire in 1 minute.
</td>
</tr>
</tbody>
@@ -96,23 +85,4 @@ func SendOtpMail(toEmail, otp string) error {
</body>
</html>
`
data := make(map[string]interface{}, 3)
var err error
data["org_logo"], err = memorystore.Provider.GetStringStoreEnvVariable(constants.EnvKeyOrganizationLogo)
if err != nil {
return err
}
data["org_name"], err = memorystore.Provider.GetStringStoreEnvVariable(constants.EnvKeyOrganizationName)
if err != nil {
return err
}
data["otp"] = otp
message = addEmailTemplate(message, data, "otp.tmpl")
// bodyMessage := sender.WriteHTMLEmail(Receiver, Subject, message)
err = SendMail(Receiver, Subject, message)
if err != nil {
log.Warn("error sending email: ", err)
}
return err
}
)

60
server/env/env.go vendored
View File

@@ -72,11 +72,15 @@ func InitAllEnv() error {
osLinkedInClientSecret := os.Getenv(constants.EnvKeyLinkedInClientSecret)
osAppleClientID := os.Getenv(constants.EnvKeyAppleClientID)
osAppleClientSecret := os.Getenv(constants.EnvKeyAppleClientSecret)
osTwitterClientID := os.Getenv(constants.EnvKeyTwitterClientID)
osTwitterClientSecret := os.Getenv(constants.EnvKeyTwitterClientSecret)
osResetPasswordURL := os.Getenv(constants.EnvKeyResetPasswordURL)
osOrganizationName := os.Getenv(constants.EnvKeyOrganizationName)
osOrganizationLogo := os.Getenv(constants.EnvKeyOrganizationLogo)
// os bool vars
osAppCookieSecure := os.Getenv(constants.EnvKeyAppCookieSecure)
osAdminCookieSecure := os.Getenv(constants.EnvKeyAdminCookieSecure)
osDisableBasicAuthentication := os.Getenv(constants.EnvKeyDisableBasicAuthentication)
osDisableEmailVerification := os.Getenv(constants.EnvKeyDisableEmailVerification)
osDisableMagicLinkLogin := os.Getenv(constants.EnvKeyDisableMagicLinkLogin)
@@ -355,31 +359,45 @@ func InitAllEnv() error {
if val, ok := envData[constants.EnvKeyLinkedInClientID]; !ok || val == "" {
envData[constants.EnvKeyLinkedInClientID] = osLinkedInClientID
}
if osFacebookClientID != "" && envData[constants.EnvKeyLinkedInClientID] != osFacebookClientID {
if osLinkedInClientID != "" && envData[constants.EnvKeyLinkedInClientID] != osLinkedInClientID {
envData[constants.EnvKeyLinkedInClientID] = osLinkedInClientID
}
if val, ok := envData[constants.EnvKeyLinkedInClientSecret]; !ok || val == "" {
envData[constants.EnvKeyLinkedInClientSecret] = osLinkedInClientSecret
}
if osFacebookClientSecret != "" && envData[constants.EnvKeyLinkedInClientSecret] != osFacebookClientSecret {
if osLinkedInClientSecret != "" && envData[constants.EnvKeyLinkedInClientSecret] != osLinkedInClientSecret {
envData[constants.EnvKeyLinkedInClientSecret] = osLinkedInClientSecret
}
if val, ok := envData[constants.EnvKeyAppleClientID]; !ok || val == "" {
envData[constants.EnvKeyAppleClientID] = osAppleClientID
}
if osFacebookClientID != "" && envData[constants.EnvKeyAppleClientID] != osFacebookClientID {
if osAppleClientID != "" && envData[constants.EnvKeyAppleClientID] != osAppleClientID {
envData[constants.EnvKeyAppleClientID] = osAppleClientID
}
if val, ok := envData[constants.EnvKeyAppleClientSecret]; !ok || val == "" {
envData[constants.EnvKeyAppleClientSecret] = osAppleClientSecret
}
if osFacebookClientSecret != "" && envData[constants.EnvKeyAppleClientSecret] != osFacebookClientSecret {
if osAppleClientSecret != "" && envData[constants.EnvKeyAppleClientSecret] != osAppleClientSecret {
envData[constants.EnvKeyAppleClientSecret] = osAppleClientSecret
}
if val, ok := envData[constants.EnvKeyTwitterClientID]; !ok || val == "" {
envData[constants.EnvKeyTwitterClientID] = osTwitterClientID
}
if osTwitterClientID != "" && envData[constants.EnvKeyTwitterClientID] != osTwitterClientID {
envData[constants.EnvKeyTwitterClientID] = osTwitterClientID
}
if val, ok := envData[constants.EnvKeyTwitterClientSecret]; !ok || val == "" {
envData[constants.EnvKeyTwitterClientSecret] = osTwitterClientSecret
}
if osTwitterClientSecret != "" && envData[constants.EnvKeyTwitterClientSecret] != osTwitterClientSecret {
envData[constants.EnvKeyTwitterClientSecret] = osTwitterClientSecret
}
if val, ok := envData[constants.EnvKeyResetPasswordURL]; !ok || val == "" {
envData[constants.EnvKeyResetPasswordURL] = strings.TrimPrefix(osResetPasswordURL, "/")
}
@@ -401,6 +419,40 @@ func InitAllEnv() error {
envData[constants.EnvKeyOrganizationLogo] = osOrganizationLogo
}
if _, ok := envData[constants.EnvKeyAppCookieSecure]; !ok {
if osAppCookieSecure == "" {
envData[constants.EnvKeyAppCookieSecure] = true
} else {
envData[constants.EnvKeyAppCookieSecure] = osAppCookieSecure == "true"
}
}
if osAppCookieSecure != "" {
boolValue, err := strconv.ParseBool(osAppCookieSecure)
if err != nil {
return err
}
if boolValue != envData[constants.EnvKeyAppCookieSecure].(bool) {
envData[constants.EnvKeyAppCookieSecure] = boolValue
}
}
if _, ok := envData[constants.EnvKeyAdminCookieSecure]; !ok {
if osAdminCookieSecure == "" {
envData[constants.EnvKeyAdminCookieSecure] = true
} else {
envData[constants.EnvKeyAdminCookieSecure] = osAdminCookieSecure == "true"
}
}
if osAdminCookieSecure != "" {
boolValue, err := strconv.ParseBool(osAdminCookieSecure)
if err != nil {
return err
}
if boolValue != envData[constants.EnvKeyAdminCookieSecure].(bool) {
envData[constants.EnvKeyAdminCookieSecure] = boolValue
}
}
if _, ok := envData[constants.EnvKeyDisableBasicAuthentication]; !ok {
envData[constants.EnvKeyDisableBasicAuthentication] = osDisableBasicAuthentication == "true"
}

View File

@@ -201,7 +201,7 @@ func PersistEnv() error {
envValue := strings.TrimSpace(os.Getenv(key))
if envValue != "" {
switch key {
case constants.EnvKeyIsProd, constants.EnvKeyDisableBasicAuthentication, constants.EnvKeyDisableEmailVerification, constants.EnvKeyDisableLoginPage, constants.EnvKeyDisableMagicLinkLogin, constants.EnvKeyDisableSignUp, constants.EnvKeyDisableRedisForEnv, constants.EnvKeyDisableStrongPassword, constants.EnvKeyIsEmailServiceEnabled, constants.EnvKeyEnforceMultiFactorAuthentication, constants.EnvKeyDisableMultiFactorAuthentication:
case constants.EnvKeyIsProd, constants.EnvKeyDisableBasicAuthentication, constants.EnvKeyDisableEmailVerification, constants.EnvKeyDisableLoginPage, constants.EnvKeyDisableMagicLinkLogin, constants.EnvKeyDisableSignUp, constants.EnvKeyDisableRedisForEnv, constants.EnvKeyDisableStrongPassword, constants.EnvKeyIsEmailServiceEnabled, constants.EnvKeyEnforceMultiFactorAuthentication, constants.EnvKeyDisableMultiFactorAuthentication, constants.EnvKeyAdminCookieSecure, constants.EnvKeyAppCookieSecure:
if envValueBool, err := strconv.ParseBool(envValue); err == nil {
if value.(bool) != envValueBool {
storeData[key] = envValueBool

View File

@@ -3,37 +3,34 @@ module github.com/authorizerdev/authorizer/server
go 1.16
require (
github.com/99designs/gqlgen v0.14.0
github.com/99designs/gqlgen v0.17.20
github.com/arangodb/go-driver v1.2.1
github.com/coreos/go-oidc/v3 v3.1.0
github.com/gin-gonic/gin v1.7.2
github.com/go-playground/validator/v10 v10.8.0 // indirect
github.com/gin-gonic/gin v1.8.1
github.com/go-playground/validator/v10 v10.11.1 // indirect
github.com/go-redis/redis/v8 v8.11.0
github.com/goccy/go-json v0.9.11 // indirect
github.com/gocql/gocql v1.2.0
github.com/golang-jwt/jwt v3.2.2+incompatible
github.com/golang/protobuf v1.5.2 // indirect
github.com/google/uuid v1.3.0
github.com/joho/godotenv v1.3.0
github.com/json-iterator/go v1.1.11 // indirect
github.com/mattn/go-isatty v0.0.13 // indirect
github.com/mitchellh/mapstructure v1.1.2 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.1 // indirect
github.com/pelletier/go-toml/v2 v2.0.5 // indirect
github.com/robertkrimen/otto v0.0.0-20211024170158-b87d35c0b86f
github.com/sirupsen/logrus v1.8.1
github.com/stretchr/testify v1.7.0
github.com/ugorji/go v1.2.6 // indirect
github.com/vektah/gqlparser/v2 v2.2.0
github.com/stretchr/testify v1.8.0
github.com/vektah/gqlparser/v2 v2.5.1
go.mongodb.org/mongo-driver v1.8.1
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519
golang.org/x/net v0.0.0-20210614182718-04defd469f4e // indirect
golang.org/x/crypto v0.0.0-20220926161630-eccd6366d1be
golang.org/x/net v0.0.0-20220930213112-107f3e3c3b0b // indirect
golang.org/x/oauth2 v0.0.0-20210628180205-a41e5a781914
golang.org/x/sys v0.0.0-20220928140112-f11e5e49a4ec // indirect
google.golang.org/appengine v1.6.7 // indirect
google.golang.org/protobuf v1.27.1 // indirect
google.golang.org/protobuf v1.28.1 // indirect
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect
gopkg.in/mail.v2 v2.3.1
gopkg.in/square/go-jose.v2 v2.6.0
gopkg.in/yaml.v2 v2.4.0 // indirect
gorm.io/driver/mysql v1.2.1
gorm.io/driver/postgres v1.2.3
gorm.io/driver/sqlite v1.2.6

View File

@@ -31,15 +31,16 @@ cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohl
cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs=
cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0=
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
github.com/99designs/gqlgen v0.14.0 h1:Wg8aNYQUjMR/4v+W3xD+7SizOy6lSvVeQ06AobNQAXI=
github.com/99designs/gqlgen v0.14.0/go.mod h1:S7z4boV+Nx4VvzMUpVrY/YuHjFX4n7rDyuTqvAkuoRE=
github.com/99designs/gqlgen v0.17.20 h1:O7WzccIhKB1dm+7g6dhQcULINftfiLSBg2l/mwbpJMw=
github.com/99designs/gqlgen v0.17.20/go.mod h1:Mja2HI23kWT1VRH09hvWshFgOzKswpO20o4ScpJIES4=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/BurntSushi/toml v1.1.0/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
github.com/Masterminds/semver/v3 v3.1.1 h1:hLg3sBzpNErnxhQtUy/mmLR2I9foDujNK030IGemrRc=
github.com/Masterminds/semver/v3 v3.1.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs=
github.com/agnivade/levenshtein v1.0.1/go.mod h1:CURSv5d9Uaml+FovSIICkLbAUZ9S4RqaHDIsdSBg7lM=
github.com/agnivade/levenshtein v1.1.0 h1:n6qGwyHG61v3ABce1rPVZklEYRT8NFpCMrpZdBUbYGM=
github.com/agnivade/levenshtein v1.1.0/go.mod h1:veldBMzWxcCG2ZvUTKD2kJNRdCk5hVbJomOvKkmgYbo=
github.com/agnivade/levenshtein v1.1.1 h1:QY8M92nrzkmr798gCo3kmMyqXFzdQVpxLlGPRBij0P8=
github.com/agnivade/levenshtein v1.1.1/go.mod h1:veldBMzWxcCG2ZvUTKD2kJNRdCk5hVbJomOvKkmgYbo=
github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883 h1:bvNMNQO63//z+xNgfBlViaCIJKLlCJ6/fmUseuG0wVQ=
github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883/go.mod h1:rCTlJbsFo29Kk6CurOXKm700vrz8f0KW0JNfpkRJY/8=
github.com/arangodb/go-driver v1.2.1 h1:HREDHhDmzdIWxHmfkfTESbYUnRjESjPh4WUuXq7FZa8=
@@ -67,8 +68,10 @@ github.com/coreos/go-oidc/v3 v3.1.0 h1:6avEvcdvTa1qYsOZ6I5PRkSYHzpTNWgKYmaJfaYbr
github.com/coreos/go-oidc/v3 v3.1.0/go.mod h1:rEJ/idjfUyfkBit1eI1fvyr+64/g9dcKpAm8MJMesvo=
github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
github.com/cpuguy83/go-md2man/v2 v2.0.1 h1:r/myEWzV9lfsM1tFLgDyu0atFtJ1fXn261LKYj/3DxU=
github.com/cpuguy83/go-md2man/v2 v2.0.1/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
@@ -88,8 +91,8 @@ github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWo
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
github.com/gin-gonic/gin v1.7.2 h1:Tg03T9yM2xa8j6I3Z3oqLaQRSmKvxPd6g/2HJ6zICFA=
github.com/gin-gonic/gin v1.7.2/go.mod h1:jD2toBW3GZUr5UMcdrwQA10I7RuaFOl/SGeDjXkfUtY=
github.com/gin-gonic/gin v1.8.1 h1:4+fr/el88TOO3ewCmQr8cx/CtZ/umlIRIs5M4NTNjf8=
github.com/gin-gonic/gin v1.8.1/go.mod h1:ji8BvRH1azfM+SYow9zQ6SZMvR8qOMZHmsCuWR9tTTk=
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
@@ -97,24 +100,26 @@ github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vb
github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A=
github.com/go-playground/assert/v2 v2.0.1 h1:MsBgLAaY856+nPRTKrp3/OZK38U/wa0CcBYNjji3q3A=
github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
github.com/go-playground/locales v0.13.0 h1:HyWk6mgj5qFqCT5fjGBuRArbVDfE4hi8+e8ceBS/t7Q=
github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8=
github.com/go-playground/universal-translator v0.17.0 h1:icxd5fm+REJzpZx7ZfpaD876Lmtgy7VtROAbHHXk8no=
github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA=
github.com/go-playground/validator/v10 v10.4.1/go.mod h1:nlOn6nFhuKACm19sB/8EGNn9GlaMV7XkbRSipzJ0Ii4=
github.com/go-playground/validator/v10 v10.8.0 h1:1kAa0fCrnpv+QYdkdcRzrRM7AyYs5o8+jZdJCz9xj6k=
github.com/go-playground/validator/v10 v10.8.0/go.mod h1:9JhgTzTaE31GZDpH/HSvHiRJrJ3iKAgqqH0Bl/Ocjdk=
github.com/go-playground/locales v0.14.0 h1:u50s323jtVGugKlcYeyzC0etD1HifMjqmJqb8WugfUU=
github.com/go-playground/locales v0.14.0/go.mod h1:sawfccIbzZTqEDETgFXqTho0QybSa7l++s0DH+LDiLs=
github.com/go-playground/universal-translator v0.18.0 h1:82dyy6p4OuJq4/CByFNOn/jYrnRPArHwAcmLoJZxyho=
github.com/go-playground/universal-translator v0.18.0/go.mod h1:UvRDBj+xPUEGrFYl+lu/H90nyDXpg0fqeB/AQUGNTVA=
github.com/go-playground/validator/v10 v10.10.0/go.mod h1:74x4gJWsvQexRdW8Pn3dXSGrTK4nAUsbPlLADvpJkos=
github.com/go-playground/validator/v10 v10.11.1 h1:prmOlTVv+YjZjmRmNSF3VmspqJIxJWXmqUsHwfTRRkQ=
github.com/go-playground/validator/v10 v10.11.1/go.mod h1:i+3WkQ1FvaUjjxh1kSvIA4dMGDBiPU55YFDl0WbKdWU=
github.com/go-redis/redis/v8 v8.11.0 h1:O1Td0mQ8UFChQ3N9zFQqo6kTU2cJ+/it88gDB+zg0wo=
github.com/go-redis/redis/v8 v8.11.0/go.mod h1:DLomh7y2e3ggQXQLd1YgmvIfecPJoFl7WU5SOQ/r06M=
github.com/go-sql-driver/mysql v1.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfCHuOE=
github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
github.com/go-stack/stack v1.8.0 h1:5SgMzNM5HxrEjV0ww2lTmX6E2Izsfxas4+YHWRs3Lsk=
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
github.com/goccy/go-json v0.9.7/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
github.com/goccy/go-json v0.9.11 h1:/pAaQDLHEoCq/5FFmSKBswWmK6H0e8g4159Kc/X/nqk=
github.com/goccy/go-json v0.9.11/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
github.com/gocql/gocql v1.2.0 h1:TZhsCd7fRuye4VyHr3WCvWwIQaZUmjsqnSIXK9FcVCE=
github.com/gocql/gocql v1.2.0/go.mod h1:3gM2c4D3AnkISwBxGnMMsS8Oy4y2lhbPRsH4xnJrHG8=
github.com/gofrs/uuid v4.0.0+incompatible h1:1SD/1F5pU8p29ybwgQSwpQk+mwdRrXCYuPhW6m+TnJw=
github.com/gofrs/uuid v4.0.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY=
github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I=
github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe h1:lXe2qZdvpiX5WZkZR4hgp4KJVfY3nMkvmwbVkpv1rVY=
@@ -178,15 +183,14 @@ github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
github.com/gorilla/context v0.0.0-20160226214623-1ea25387ff6f/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg=
github.com/gorilla/mux v1.6.1/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc=
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc=
github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/hailocab/go-hostpool v0.0.0-20160125115350-e80d13ce29ed h1:5upAirOpQc1Q53c0bnx2ufif5kANL7bfZWcc6VJWJd8=
github.com/hailocab/go-hostpool v0.0.0-20160125115350-e80d13ce29ed/go.mod h1:tMWxXQ9wFIaZeTI9F+hmhFiGpFmhOHzyShyFUhRm0H4=
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/golang-lru v0.5.1 h1:0hERBMJE1eitiLkihrMvRVBYAkpHzc/J3QdDN+dAcgU=
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/golang-lru v0.5.4 h1:YDjusn29QI/Das2iO9M0BHnIbxPeyuCHsjMW+lJfyTc=
github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4=
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
github.com/jackc/chunkreader v1.0.0 h1:4s39bBR8ByfqH+DKm8rQA3E1LHZWB9XWcrz8fqaZbe0=
@@ -245,24 +249,25 @@ github.com/jinzhu/now v1.1.3 h1:PlHq1bSCSZL9K0wUhbm2pGLoTWs2GwVhsP6emvGV/ZI=
github.com/jinzhu/now v1.1.3/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
github.com/joho/godotenv v1.3.0 h1:Zjp+RcGpHhGlrMbJzXTrZZPrWj+1vfm90La1wgB6Bhc=
github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg=
github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
github.com/json-iterator/go v1.1.11 h1:uVUAXhF2To8cbw/3xN3pxj6kk7TYKs98NIrTqPlMWAQ=
github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
github.com/kevinmbeaulieu/eq-go v1.0.0/go.mod h1:G3S8ajA56gKBZm4UB9AOyoOS37JO3roToPzKNM8dtdM=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/klauspost/compress v1.13.6 h1:P76CopJELS0TiO2mebmnzgWaajssP/EszplttgQxcgc=
github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw=
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/leodido/go-urn v1.2.1 h1:BqpAaACuzVSgi/VLzGZIobT2z4v53pjosyNd9Yv6n/w=
github.com/leodido/go-urn v1.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ictyFfxY=
github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
@@ -270,28 +275,26 @@ github.com/lib/pq v1.1.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
github.com/lib/pq v1.10.2 h1:AqzbZs4ZoCBp+GtejcpCpcxM3zlSMx29dXbUSeVtJb8=
github.com/lib/pq v1.10.2/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
github.com/logrusorgru/aurora v0.0.0-20200102142835-e9ef32dff381/go.mod h1:7rIyQOR62GCctdiQpZ/zOJlFyk6y+94wXzv6RNZgaR4=
github.com/matryer/moq v0.0.0-20200106131100-75d0ddfc0007/go.mod h1:9ELz6aaclSIGnZBoaSLZ3NAl1VTufbOrXBPvtcy6WiQ=
github.com/logrusorgru/aurora/v3 v3.0.0/go.mod h1:vsR12bk5grlLvLXAYrBsb5Oc/N+LxAlxggSjiwMnCUc=
github.com/matryer/moq v0.2.7/go.mod h1:kITsx543GOENm48TUAQyJ9+SAvFSr7iGQXPoth/VUBk=
github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ=
github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
github.com/mattn/go-isatty v0.0.13 h1:qdl+GuBjcsKKDco5BsxPJlId98mSWNKqYA+Co0SC1yA=
github.com/mattn/go-isatty v0.0.13/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
github.com/mattn/go-isatty v0.0.16 h1:bq3VjFmv/sOjHtdEhmkEV4x1AJtvUvOJ2PFAZ5+peKQ=
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
github.com/mattn/go-sqlite3 v1.14.9 h1:10HX2Td0ocZpYEjhilsuo6WWtUqttj2Kb0KtD86/KYA=
github.com/mattn/go-sqlite3 v1.14.9/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
github.com/mitchellh/mapstructure v0.0.0-20180203102830-a4e142e9c047/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE=
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
github.com/mitchellh/mapstructure v1.3.1 h1:cCBH2gTD2K0OtLlv/Y5H01VQCqmlDxz30kS5Y5bqfLA=
github.com/mitchellh/mapstructure v1.3.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9AWI=
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe/go.mod h1:wL8QJuTMNUDYhXwkmfOly8iTdp5TEcJFWZD2D7SIkUc=
github.com/nxadm/tail v1.4.4 h1:DQuhQpB1tVlglWS2hLQ5OV6B5r8aGxSrPc5Qo6uTN78=
github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
@@ -303,8 +306,10 @@ github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7J
github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
github.com/onsi/gomega v1.10.5 h1:7n6FEkpFmfCoo2t+YYqXH0evK+a9ICQz0xcAy9dYcaQ=
github.com/onsi/gomega v1.10.5/go.mod h1:gza4q3jKQJijlu05nKWRCW/GavJumGt8aNRxWg7mt48=
github.com/opentracing/basictracer-go v1.0.0/go.mod h1:QfBfYuafItcjQuMwinw9GhYKwFXS9KnPs5lxoYwgW74=
github.com/opentracing/opentracing-go v1.0.2/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o=
github.com/pelletier/go-toml/v2 v2.0.1/go.mod h1:r9LEWfGN8R5k0VXJ+0BkIe7MYkRdwZOjgMj2KwnJFUo=
github.com/pelletier/go-toml/v2 v2.0.5 h1:ipoSadvV8oGUjnUbMub59IDPPwfxF694nG/jwbMiyQg=
github.com/pelletier/go-toml/v2 v2.0.5/go.mod h1:OMHamSCAODeSsVrwwvcJOaoN0LIUIaFVNZzmWyNfXas=
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
@@ -314,21 +319,21 @@ github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:
github.com/robertkrimen/otto v0.0.0-20211024170158-b87d35c0b86f h1:a7clxaGmmqtdNTXyvrp/lVO/Gnkzlhc/+dLs5v965GM=
github.com/robertkrimen/otto v0.0.0-20211024170158-b87d35c0b86f/go.mod h1:/mK7FZ3mFYEn9zvNPhpngTyatyehSwte5bJZ4ehL5Xw=
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/rs/cors v1.6.0/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU=
github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
github.com/rogpeppe/go-internal v1.8.0 h1:FCbCCtXNOY3UtUuHUYaghJg4y7Fd14rXifAYUAtL9R8=
github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE=
github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ=
github.com/rs/zerolog v1.13.0/go.mod h1:YbFCdg8HfsridGWAh22vktObvhZbQsZXe4/zB0OKkWU=
github.com/rs/zerolog v1.15.0/go.mod h1:xYTKnLHcpfU2225ny5qZjxnj9NvkumZYjJHlAThCjNc=
github.com/rs/zerolog v1.19.0/go.mod h1:IzD0RJ65iWH0w97OQQebJEvTZYvsCUm9WVLWBQrJRjo=
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0=
github.com/sergi/go-diff v1.1.0 h1:we8PVUC3FE2uYfodKH/nBHMSetSfHDR6scGdBi+erh0=
github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM=
github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4=
github.com/shopspring/decimal v1.2.0 h1:abSATXmQEYyShuxI4/vyW3tV1MrKAJzCZ/0zLUXYbsQ=
github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o=
github.com/shurcooL/httpfs v0.0.0-20171119174359-809beceb2371/go.mod h1:ZY1cvUeJuFPAdZ/B6v7RHavJWZn2YPVFQ1OSXhCGOkg=
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
github.com/shurcooL/vfsgen v0.0.0-20180121065927-ffb13db8def0/go.mod h1:TrYk7fJVaAttu97ZZKrO9UbRa8izdowaMIZcxYMbVaw=
github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q=
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE=
@@ -336,38 +341,42 @@ github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE=
github.com/stretchr/testify v1.2.1/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/tidwall/pretty v1.0.0 h1:HsD+QiTn7sK6flMKIvNmpqz1qrpP3Ps6jOKIKMooyg4=
github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk=
github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw=
github.com/ugorji/go v1.2.6 h1:tGiWC9HENWE2tqYycIqFTNorMmFRVhNwCpDOpWqnk8E=
github.com/ugorji/go v1.2.6/go.mod h1:anCg0y61KIhDlPZmnH+so+RQbysYVyDko0IMgJv0Nn0=
github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY=
github.com/ugorji/go/codec v1.2.6 h1:7kbGefxLoDBuYXOms4yD7223OpNMMPNPZxXk5TvFcyQ=
github.com/ugorji/go/codec v1.2.6/go.mod h1:V6TCNZ4PHqoHGFZuSG1W8nrCzzdgA2DozYxWFFpvxTw=
github.com/urfave/cli/v2 v2.1.1/go.mod h1:SE9GqnLQmjVa0iPEY0f1w3ygNIYcIJ0OKPMoW2caLfQ=
github.com/vektah/dataloaden v0.2.1-0.20190515034641-a19b9a6e7c9e/go.mod h1:/HUdMve7rvxZma+2ZELQeNh88+003LL7Pf/CZ089j8U=
github.com/vektah/gqlparser/v2 v2.2.0 h1:bAc3slekAAJW6sZTi07aGq0OrfaCjj4jxARAaC7g2EM=
github.com/vektah/gqlparser/v2 v2.2.0/go.mod h1:i3mQIGIrbK2PD1RrCeMTlVbkF2FJ6WkU1KJlJlC+3F4=
github.com/ugorji/go v1.2.7 h1:qYhyWUUd6WbiM+C6JZAUkIJt/1WrjzNHY9+KCIjVqTo=
github.com/ugorji/go v1.2.7/go.mod h1:nF9osbDWLy6bDVv/Rtoh6QgnvNDpmCalQV5urGCCS6M=
github.com/ugorji/go/codec v1.2.7 h1:YPXUKf7fYbp/y8xloBqZOw2qaVggbfwMlI8WM3wZUJ0=
github.com/ugorji/go/codec v1.2.7/go.mod h1:WGN1fab3R1fzQlVQTkfxVtIBhWDRqOviHU95kRgeqEY=
github.com/urfave/cli/v2 v2.8.1 h1:CGuYNZF9IKZY/rfBe3lJpccSoIY1ytfvmgQT90cNOl4=
github.com/urfave/cli/v2 v2.8.1/go.mod h1:Z41J9TPoffeoqP0Iza0YbAhGvymRdZAd2uPmZ5JxRdY=
github.com/vektah/gqlparser/v2 v2.5.1 h1:ZGu+bquAY23jsxDRcYpWjttRZrUz07LbiY77gUOHcr4=
github.com/vektah/gqlparser/v2 v2.5.1/go.mod h1:mPgqFBu/woKTVYWyNk8cO3kh4S/f4aRFZrvOnp3hmCs=
github.com/xdg-go/pbkdf2 v1.0.0 h1:Su7DPu48wXMwC3bs7MCNG+z4FhcyEuz5dlvchbq0B0c=
github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI=
github.com/xdg-go/scram v1.0.2 h1:akYIkZ28e6A96dkWNJQu3nmCzH3YfwMPQExUYDaRv7w=
github.com/xdg-go/scram v1.0.2/go.mod h1:1WAq6h33pAW+iRreB34OORO2Nf7qel3VV3fjBj+hCSs=
github.com/xdg-go/stringprep v1.0.2 h1:6iq84/ryjjeRmMJwxutI51F2GIPlP5BfTvXHeYjyhBc=
github.com/xdg-go/stringprep v1.0.2/go.mod h1:8F9zXuvzgwmyT5DUm4GUfZGDdT3W+LCvS6+da4O5kxM=
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 h1:bAn7/zixMGCfxrRTfdpNzjtPYqr8smhKouy9mxVdGPU=
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673/go.mod h1:N3UwUGtsrSj3ccvlPHLoLsHnpR27oXr4ZE984MbSER8=
github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d h1:splanxYIlg+5LfHAM6xpdFEAYOk8iySO56hMFq6uLyA=
github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d/go.mod h1:rHwXgn7JulP+udvsHwJoVG1YGAP6VLg4y9I5dyZdqmA=
github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
github.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q=
go.mongodb.org/mongo-driver v1.8.1 h1:OZE4Wni/SJlrcmSIBRYNzunX5TKxjrTS4jKSnA99oKU=
go.mongodb.org/mongo-driver v1.8.1/go.mod h1:0sQWfOeY63QTntERDJJ/0SuKK0T1uVSgKCuAROlKEPY=
@@ -399,8 +408,10 @@ golang.org/x/crypto v0.0.0-20201203163018-be400aefbc4c/go.mod h1:jdWPYTVW3xRLrWP
golang.org/x/crypto v0.0.0-20201216223049-8b5274cf687f/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519 h1:7I4JAnoQBe7ZtJcBaYHi5UtiO8tQHbUSXxL+pnGRANg=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/crypto v0.0.0-20220926161630-eccd6366d1be h1:fmw3UbQh+nxngCAHrDCCztao/kbYFnWjoqop8dHx05A=
golang.org/x/crypto v0.0.0-20220926161630-eccd6366d1be/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
@@ -431,6 +442,9 @@ golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzB
golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3/go.mod h1:3p9vT2HGsQu2K1YbXdKPJLVgG5VJdoTa1poYQBtP1AY=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 h1:6zppjxzCulZykYSLyVDYbneBfbaBIQPYMevg0bEwv2s=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
@@ -464,8 +478,11 @@ golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81R
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20201202161906-c7110b5ffcbb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210614182718-04defd469f4e h1:XpT3nA5TvE525Ne3hInMh6+GETgn27Zfm9dxsThnX2Q=
golang.org/x/net v0.0.0-20210614182718-04defd469f4e/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.0.0-20220930213112-107f3e3c3b0b h1:uKO3Js8lXGjpjdc4J3rqs0/Ex5yDKUGfk43tTYWVLas=
golang.org/x/net v0.0.0-20220930213112-107f3e3c3b0b/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
@@ -481,8 +498,10 @@ golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJ
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9 h1:SQFwaSi55rU7vdNs9Yr0Z324VNlrF+0wMqRXT4St8ck=
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4 h1:uVc8UZUe6tr40fFVnUP5Oj+veunVezqYl9z7DYw9xzw=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
@@ -524,10 +543,18 @@ golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c h1:F1jZWGFhYfh0Ci55sIpILtKKK8p3i2/krTr0H1rg74I=
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220928140112-f11e5e49a4ec h1:BkDtF2Ih9xZ7le9ndzTA7KJow28VbQW3odyk/8drmuI=
golang.org/x/sys v0.0.0-20220928140112-f11e5e49a4ec/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
@@ -543,7 +570,6 @@ golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxb
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190125232054-d66bd3c5d5a6/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
@@ -551,7 +577,6 @@ golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3
golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190425163242-31fd60d6bfdc/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190515012406-7d7faa4812bd/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190531172133-b3315ee88b7d/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
@@ -587,12 +612,13 @@ golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roY
golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.1.10/go.mod h1:Uh6Zz+xoGYZom868N8YTex3t7RhtHDBrE8Gzo9bV56E=
golang.org/x/tools v0.1.12 h1:VveCTK38A2rkS8ZqFY25HIDFscX5X9OoEhJd3quQmXU=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
@@ -677,14 +703,16 @@ google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGj
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
google.golang.org/protobuf v1.27.1 h1:SnqbnDw1V7RiZcXPx5MEeqPv2s79L9i7BJUlG/+RurQ=
google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
google.golang.org/protobuf v1.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w=
google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc h1:2gGKlE2+asNV9m7xrywl36YYNnBG5ZQ0r/BOOxqPpmk=
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc/go.mod h1:m7x9LTH6d71AHyAX77c9yqWCCa3UKHcVEj9y7hAtKDk=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
gopkg.in/inconshreveable/log15.v2 v2.0.0-20180818164646-67afb5ed74ec/go.mod h1:aPpfJ7XW+gOuirDoZ8gHhLh3kZ1B08FtV2bbmy7Jv3s=
@@ -706,8 +734,10 @@ gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gorm.io/driver/mysql v1.2.1 h1:h+3f1l9Ng2C072Y2tIiLgPpWN78r1KXL7bHJ0nTjlhU=
gorm.io/driver/mysql v1.2.1/go.mod h1:qsiz+XcAyMrS6QY+X3M9R6b/lKM1imKmcuK9kac5LTo=
gorm.io/driver/postgres v1.2.3 h1:f4t0TmNMy9gh3TU2PX+EppoA6YsgFnyq8Ojtddb42To=
@@ -730,5 +760,3 @@ honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
sourcegraph.com/sourcegraph/appdash v0.0.0-20190731080439-ebfcffb1b5c0/go.mod h1:hI742Nqp5OhwiqlzhgfbWU4mW4yO10fP+LoT9WOswdU=
sourcegraph.com/sourcegraph/appdash-data v0.0.0-20151005221446-73f23eafcf67/go.mod h1:L5q+DGLGOQFpo1snNEkLOJT2d1YTW66rWNzatr3He1k=

View File

@@ -23,19 +23,26 @@ resolver:
dir: graph
package: graph
# Optional: turn on use `gqlgen:"fieldName"` tags in your models
# Optional: turn on use ` + "`" + `gqlgen:"fieldName"` + "`" + ` tags in your models
# struct_tag: json
# Optional: turn on to use []Thing instead of []*Thing
# omit_slice_element_pointers: false
# Optional: turn off to make struct-type struct fields not use pointers
# e.g. type Thing struct { FieldA OtherThing } instead of { FieldA *OtherThing }
# struct_fields_always_pointers: true
# Optional: turn off to make resolvers return values instead of pointers for structs
# resolvers_always_return_pointers: true
# Optional: set to speed up generation time by not performing a final validation pass.
# skip_validation: true
# gqlgen will search for any type names in the schema in these go packages
# if they match it will use them, otherwise it will generate them.
autobind:
- 'github.com/authorizerdev/authorizer/server/graph/model'
# - "github.com/authorizerdev/authorizer/server/graph/model"
# This section declares type mapping between the GraphQL and go type systems
#
@@ -45,7 +52,6 @@ autobind:
models:
ID:
model:
# - github.com/99designs/gqlgen/graphql.IntID # An go integer
- github.com/99designs/gqlgen/graphql.ID
- github.com/99designs/gqlgen/graphql.Int
- github.com/99designs/gqlgen/graphql.Int64
@@ -55,11 +61,12 @@ models:
- github.com/99designs/gqlgen/graphql.Int
- github.com/99designs/gqlgen/graphql.Int64
- github.com/99designs/gqlgen/graphql.Int32
Float:
model:
- github.com/99designs/gqlgen/graphql.Float
- github.com/99designs/gqlgen/graphql.Float64
- github.com/99designs/gqlgen/graphql.Float32
Int64:
model:
- github.com/99designs/gqlgen/graphql.Int64
Map:
model:
- github.com/99designs/gqlgen/graphql.Map
Any:
model:
- github.com/99designs/gqlgen/graphql.Any

File diff suppressed because it is too large Load Diff

View File

@@ -4,7 +4,9 @@ package model
type AddEmailTemplateRequest struct {
EventName string `json:"event_name"`
Subject string `json:"subject"`
Template string `json:"template"`
Design string `json:"design"`
}
type AddWebhookRequest struct {
@@ -44,13 +46,15 @@ type EmailTemplate struct {
ID string `json:"id"`
EventName string `json:"event_name"`
Template string `json:"template"`
Design string `json:"design"`
Subject string `json:"subject"`
CreatedAt *int64 `json:"created_at"`
UpdatedAt *int64 `json:"updated_at"`
}
type EmailTemplates struct {
Pagination *Pagination `json:"pagination"`
EmailTemplates []*EmailTemplate `json:"EmailTemplates"`
EmailTemplates []*EmailTemplate `json:"email_templates"`
}
type Env struct {
@@ -102,8 +106,12 @@ type Env struct {
LinkedinClientSecret *string `json:"LINKEDIN_CLIENT_SECRET"`
AppleClientID *string `json:"APPLE_CLIENT_ID"`
AppleClientSecret *string `json:"APPLE_CLIENT_SECRET"`
TwitterClientID *string `json:"TWITTER_CLIENT_ID"`
TwitterClientSecret *string `json:"TWITTER_CLIENT_SECRET"`
OrganizationName *string `json:"ORGANIZATION_NAME"`
OrganizationLogo *string `json:"ORGANIZATION_LOGO"`
AppCookieSecure bool `json:"APP_COOKIE_SECURE"`
AdminCookieSecure bool `json:"ADMIN_COOKIE_SECURE"`
}
type Error struct {
@@ -160,6 +168,7 @@ type Meta struct {
IsGithubLoginEnabled bool `json:"is_github_login_enabled"`
IsLinkedinLoginEnabled bool `json:"is_linkedin_login_enabled"`
IsAppleLoginEnabled bool `json:"is_apple_login_enabled"`
IsTwitterLoginEnabled bool `json:"is_twitter_login_enabled"`
IsEmailVerificationEnabled bool `json:"is_email_verification_enabled"`
IsBasicAuthenticationEnabled bool `json:"is_basic_authentication_enabled"`
IsMagicLinkLoginEnabled bool `json:"is_magic_link_login_enabled"`
@@ -249,6 +258,8 @@ type UpdateEmailTemplateRequest struct {
ID string `json:"id"`
EventName *string `json:"event_name"`
Template *string `json:"template"`
Subject *string `json:"subject"`
Design *string `json:"design"`
}
type UpdateEnvInput struct {
@@ -268,6 +279,8 @@ type UpdateEnvInput struct {
AllowedOrigins []string `json:"ALLOWED_ORIGINS"`
AppURL *string `json:"APP_URL"`
ResetPasswordURL *string `json:"RESET_PASSWORD_URL"`
AppCookieSecure *bool `json:"APP_COOKIE_SECURE"`
AdminCookieSecure *bool `json:"ADMIN_COOKIE_SECURE"`
DisableEmailVerification *bool `json:"DISABLE_EMAIL_VERIFICATION"`
DisableBasicAuthentication *bool `json:"DISABLE_BASIC_AUTHENTICATION"`
DisableMagicLinkLogin *bool `json:"DISABLE_MAGIC_LINK_LOGIN"`
@@ -291,6 +304,8 @@ type UpdateEnvInput struct {
LinkedinClientSecret *string `json:"LINKEDIN_CLIENT_SECRET"`
AppleClientID *string `json:"APPLE_CLIENT_ID"`
AppleClientSecret *string `json:"APPLE_CLIENT_SECRET"`
TwitterClientID *string `json:"TWITTER_CLIENT_ID"`
TwitterClientSecret *string `json:"TWITTER_CLIENT_SECRET"`
OrganizationName *string `json:"ORGANIZATION_NAME"`
OrganizationLogo *string `json:"ORGANIZATION_LOGO"`
}

View File

@@ -6,481 +6,496 @@ scalar Map
scalar Any
type Pagination {
limit: Int64!
page: Int64!
offset: Int64!
total: Int64!
limit: Int64!
page: Int64!
offset: Int64!
total: Int64!
}
type Meta {
version: String!
client_id: String!
is_google_login_enabled: Boolean!
is_facebook_login_enabled: Boolean!
is_github_login_enabled: Boolean!
is_linkedin_login_enabled: Boolean!
is_apple_login_enabled: Boolean!
is_email_verification_enabled: Boolean!
is_basic_authentication_enabled: Boolean!
is_magic_link_login_enabled: Boolean!
is_sign_up_enabled: Boolean!
is_strong_password_enabled: Boolean!
is_multi_factor_auth_enabled: Boolean!
version: String!
client_id: String!
is_google_login_enabled: Boolean!
is_facebook_login_enabled: Boolean!
is_github_login_enabled: Boolean!
is_linkedin_login_enabled: Boolean!
is_apple_login_enabled: Boolean!
is_twitter_login_enabled: Boolean!
is_email_verification_enabled: Boolean!
is_basic_authentication_enabled: Boolean!
is_magic_link_login_enabled: Boolean!
is_sign_up_enabled: Boolean!
is_strong_password_enabled: Boolean!
is_multi_factor_auth_enabled: Boolean!
}
type User {
id: ID!
email: String!
email_verified: Boolean!
signup_methods: String!
given_name: String
family_name: String
middle_name: String
nickname: String
# defaults to email
preferred_username: String
gender: String
birthdate: String
phone_number: String
phone_number_verified: Boolean
picture: String
roles: [String!]!
created_at: Int64
updated_at: Int64
revoked_timestamp: Int64
is_multi_factor_auth_enabled: Boolean
id: ID!
email: String!
email_verified: Boolean!
signup_methods: String!
given_name: String
family_name: String
middle_name: String
nickname: String
# defaults to email
preferred_username: String
gender: String
birthdate: String
phone_number: String
phone_number_verified: Boolean
picture: String
roles: [String!]!
created_at: Int64
updated_at: Int64
revoked_timestamp: Int64
is_multi_factor_auth_enabled: Boolean
}
type Users {
pagination: Pagination!
users: [User!]!
pagination: Pagination!
users: [User!]!
}
type VerificationRequest {
id: ID!
identifier: String
token: String
email: String
expires: Int64
created_at: Int64
updated_at: Int64
nonce: String
redirect_uri: String
id: ID!
identifier: String
token: String
email: String
expires: Int64
created_at: Int64
updated_at: Int64
nonce: String
redirect_uri: String
}
type VerificationRequests {
pagination: Pagination!
verification_requests: [VerificationRequest!]!
pagination: Pagination!
verification_requests: [VerificationRequest!]!
}
type Error {
message: String!
reason: String!
message: String!
reason: String!
}
type AuthResponse {
message: String!
should_show_otp_screen: Boolean
access_token: String
id_token: String
refresh_token: String
expires_in: Int64
user: User
message: String!
should_show_otp_screen: Boolean
access_token: String
id_token: String
refresh_token: String
expires_in: Int64
user: User
}
type Response {
message: String!
message: String!
}
type Env {
ACCESS_TOKEN_EXPIRY_TIME: String
ADMIN_SECRET: String
DATABASE_NAME: String
DATABASE_URL: String
DATABASE_TYPE: String
DATABASE_USERNAME: String
DATABASE_PASSWORD: String
DATABASE_HOST: String
DATABASE_PORT: String
CLIENT_ID: String!
CLIENT_SECRET: String!
CUSTOM_ACCESS_TOKEN_SCRIPT: String
SMTP_HOST: String
SMTP_PORT: String
SMTP_USERNAME: String
SMTP_PASSWORD: String
SENDER_EMAIL: String
JWT_TYPE: String
JWT_SECRET: String
JWT_PRIVATE_KEY: String
JWT_PUBLIC_KEY: String
ALLOWED_ORIGINS: [String!]
APP_URL: String
REDIS_URL: String
RESET_PASSWORD_URL: String
DISABLE_EMAIL_VERIFICATION: Boolean!
DISABLE_BASIC_AUTHENTICATION: Boolean!
DISABLE_MAGIC_LINK_LOGIN: Boolean!
DISABLE_LOGIN_PAGE: Boolean!
DISABLE_SIGN_UP: Boolean!
DISABLE_REDIS_FOR_ENV: Boolean!
DISABLE_STRONG_PASSWORD: Boolean!
DISABLE_MULTI_FACTOR_AUTHENTICATION: Boolean!
ENFORCE_MULTI_FACTOR_AUTHENTICATION: Boolean!
ROLES: [String!]
PROTECTED_ROLES: [String!]
DEFAULT_ROLES: [String!]
JWT_ROLE_CLAIM: String
GOOGLE_CLIENT_ID: String
GOOGLE_CLIENT_SECRET: String
GITHUB_CLIENT_ID: String
GITHUB_CLIENT_SECRET: String
FACEBOOK_CLIENT_ID: String
FACEBOOK_CLIENT_SECRET: String
LINKEDIN_CLIENT_ID: String
LINKEDIN_CLIENT_SECRET: String
APPLE_CLIENT_ID: String
APPLE_CLIENT_SECRET: String
ORGANIZATION_NAME: String
ORGANIZATION_LOGO: String
ACCESS_TOKEN_EXPIRY_TIME: String
ADMIN_SECRET: String
DATABASE_NAME: String
DATABASE_URL: String
DATABASE_TYPE: String
DATABASE_USERNAME: String
DATABASE_PASSWORD: String
DATABASE_HOST: String
DATABASE_PORT: String
CLIENT_ID: String!
CLIENT_SECRET: String!
CUSTOM_ACCESS_TOKEN_SCRIPT: String
SMTP_HOST: String
SMTP_PORT: String
SMTP_USERNAME: String
SMTP_PASSWORD: String
SENDER_EMAIL: String
JWT_TYPE: String
JWT_SECRET: String
JWT_PRIVATE_KEY: String
JWT_PUBLIC_KEY: String
ALLOWED_ORIGINS: [String!]
APP_URL: String
REDIS_URL: String
RESET_PASSWORD_URL: String
DISABLE_EMAIL_VERIFICATION: Boolean!
DISABLE_BASIC_AUTHENTICATION: Boolean!
DISABLE_MAGIC_LINK_LOGIN: Boolean!
DISABLE_LOGIN_PAGE: Boolean!
DISABLE_SIGN_UP: Boolean!
DISABLE_REDIS_FOR_ENV: Boolean!
DISABLE_STRONG_PASSWORD: Boolean!
DISABLE_MULTI_FACTOR_AUTHENTICATION: Boolean!
ENFORCE_MULTI_FACTOR_AUTHENTICATION: Boolean!
ROLES: [String!]
PROTECTED_ROLES: [String!]
DEFAULT_ROLES: [String!]
JWT_ROLE_CLAIM: String
GOOGLE_CLIENT_ID: String
GOOGLE_CLIENT_SECRET: String
GITHUB_CLIENT_ID: String
GITHUB_CLIENT_SECRET: String
FACEBOOK_CLIENT_ID: String
FACEBOOK_CLIENT_SECRET: String
LINKEDIN_CLIENT_ID: String
LINKEDIN_CLIENT_SECRET: String
APPLE_CLIENT_ID: String
APPLE_CLIENT_SECRET: String
TWITTER_CLIENT_ID: String
TWITTER_CLIENT_SECRET: String
ORGANIZATION_NAME: String
ORGANIZATION_LOGO: String
APP_COOKIE_SECURE: Boolean!
ADMIN_COOKIE_SECURE: Boolean!
}
type ValidateJWTTokenResponse {
is_valid: Boolean!
is_valid: Boolean!
}
type GenerateJWTKeysResponse {
secret: String
public_key: String
private_key: String
secret: String
public_key: String
private_key: String
}
type Webhook {
id: ID!
event_name: String
endpoint: String
enabled: Boolean
headers: Map
created_at: Int64
updated_at: Int64
id: ID!
event_name: String
endpoint: String
enabled: Boolean
headers: Map
created_at: Int64
updated_at: Int64
}
type Webhooks {
pagination: Pagination!
webhooks: [Webhook!]!
pagination: Pagination!
webhooks: [Webhook!]!
}
type WebhookLog {
id: ID!
http_status: Int64
response: String
request: String
webhook_id: ID
created_at: Int64
updated_at: Int64
id: ID!
http_status: Int64
response: String
request: String
webhook_id: ID
created_at: Int64
updated_at: Int64
}
type TestEndpointResponse {
http_status: Int64
response: String
http_status: Int64
response: String
}
type WebhookLogs {
pagination: Pagination!
webhook_logs: [WebhookLog!]!
pagination: Pagination!
webhook_logs: [WebhookLog!]!
}
type EmailTemplate {
id: ID!
event_name: String!
template: String!
created_at: Int64
updated_at: Int64
id: ID!
event_name: String!
template: String!
design: String!
subject: String!
created_at: Int64
updated_at: Int64
}
type EmailTemplates {
pagination: Pagination!
EmailTemplates: [EmailTemplate!]!
pagination: Pagination!
email_templates: [EmailTemplate!]!
}
input UpdateEnvInput {
ACCESS_TOKEN_EXPIRY_TIME: String
ADMIN_SECRET: String
CUSTOM_ACCESS_TOKEN_SCRIPT: String
OLD_ADMIN_SECRET: String
SMTP_HOST: String
SMTP_PORT: String
SMTP_USERNAME: String
SMTP_PASSWORD: String
SENDER_EMAIL: String
JWT_TYPE: String
JWT_SECRET: String
JWT_PRIVATE_KEY: String
JWT_PUBLIC_KEY: String
ALLOWED_ORIGINS: [String!]
APP_URL: String
RESET_PASSWORD_URL: String
DISABLE_EMAIL_VERIFICATION: Boolean
DISABLE_BASIC_AUTHENTICATION: Boolean
DISABLE_MAGIC_LINK_LOGIN: Boolean
DISABLE_LOGIN_PAGE: Boolean
DISABLE_SIGN_UP: Boolean
DISABLE_REDIS_FOR_ENV: Boolean
DISABLE_STRONG_PASSWORD: Boolean
DISABLE_MULTI_FACTOR_AUTHENTICATION: Boolean
ENFORCE_MULTI_FACTOR_AUTHENTICATION: Boolean
ROLES: [String!]
PROTECTED_ROLES: [String!]
DEFAULT_ROLES: [String!]
JWT_ROLE_CLAIM: String
GOOGLE_CLIENT_ID: String
GOOGLE_CLIENT_SECRET: String
GITHUB_CLIENT_ID: String
GITHUB_CLIENT_SECRET: String
FACEBOOK_CLIENT_ID: String
FACEBOOK_CLIENT_SECRET: String
LINKEDIN_CLIENT_ID: String
LINKEDIN_CLIENT_SECRET: String
APPLE_CLIENT_ID: String
APPLE_CLIENT_SECRET: String
ORGANIZATION_NAME: String
ORGANIZATION_LOGO: String
ACCESS_TOKEN_EXPIRY_TIME: String
ADMIN_SECRET: String
CUSTOM_ACCESS_TOKEN_SCRIPT: String
OLD_ADMIN_SECRET: String
SMTP_HOST: String
SMTP_PORT: String
SMTP_USERNAME: String
SMTP_PASSWORD: String
SENDER_EMAIL: String
JWT_TYPE: String
JWT_SECRET: String
JWT_PRIVATE_KEY: String
JWT_PUBLIC_KEY: String
ALLOWED_ORIGINS: [String!]
APP_URL: String
RESET_PASSWORD_URL: String
APP_COOKIE_SECURE: Boolean
ADMIN_COOKIE_SECURE: Boolean
DISABLE_EMAIL_VERIFICATION: Boolean
DISABLE_BASIC_AUTHENTICATION: Boolean
DISABLE_MAGIC_LINK_LOGIN: Boolean
DISABLE_LOGIN_PAGE: Boolean
DISABLE_SIGN_UP: Boolean
DISABLE_REDIS_FOR_ENV: Boolean
DISABLE_STRONG_PASSWORD: Boolean
DISABLE_MULTI_FACTOR_AUTHENTICATION: Boolean
ENFORCE_MULTI_FACTOR_AUTHENTICATION: Boolean
ROLES: [String!]
PROTECTED_ROLES: [String!]
DEFAULT_ROLES: [String!]
JWT_ROLE_CLAIM: String
GOOGLE_CLIENT_ID: String
GOOGLE_CLIENT_SECRET: String
GITHUB_CLIENT_ID: String
GITHUB_CLIENT_SECRET: String
FACEBOOK_CLIENT_ID: String
FACEBOOK_CLIENT_SECRET: String
LINKEDIN_CLIENT_ID: String
LINKEDIN_CLIENT_SECRET: String
APPLE_CLIENT_ID: String
APPLE_CLIENT_SECRET: String
TWITTER_CLIENT_ID: String
TWITTER_CLIENT_SECRET: String
ORGANIZATION_NAME: String
ORGANIZATION_LOGO: String
}
input AdminLoginInput {
admin_secret: String!
admin_secret: String!
}
input AdminSignupInput {
admin_secret: String!
admin_secret: String!
}
input SignUpInput {
email: String!
given_name: String
family_name: String
middle_name: String
nickname: String
gender: String
birthdate: String
phone_number: String
picture: String
password: String!
confirm_password: String!
roles: [String!]
scope: [String!]
redirect_uri: String
is_multi_factor_auth_enabled: Boolean
email: String!
given_name: String
family_name: String
middle_name: String
nickname: String
gender: String
birthdate: String
phone_number: String
picture: String
password: String!
confirm_password: String!
roles: [String!]
scope: [String!]
redirect_uri: String
is_multi_factor_auth_enabled: Boolean
}
input LoginInput {
email: String!
password: String!
roles: [String!]
scope: [String!]
email: String!
password: String!
roles: [String!]
scope: [String!]
}
input VerifyEmailInput {
token: String!
token: String!
}
input ResendVerifyEmailInput {
email: String!
identifier: String!
email: String!
identifier: String!
}
input UpdateProfileInput {
old_password: String
new_password: String
confirm_new_password: String
email: String
given_name: String
family_name: String
middle_name: String
nickname: String
gender: String
birthdate: String
phone_number: String
picture: String
is_multi_factor_auth_enabled: Boolean
old_password: String
new_password: String
confirm_new_password: String
email: String
given_name: String
family_name: String
middle_name: String
nickname: String
gender: String
birthdate: String
phone_number: String
picture: String
is_multi_factor_auth_enabled: Boolean
}
input UpdateUserInput {
id: ID!
email: String
email_verified: Boolean
given_name: String
family_name: String
middle_name: String
nickname: String
gender: String
birthdate: String
phone_number: String
picture: String
roles: [String]
is_multi_factor_auth_enabled: Boolean
id: ID!
email: String
email_verified: Boolean
given_name: String
family_name: String
middle_name: String
nickname: String
gender: String
birthdate: String
phone_number: String
picture: String
roles: [String]
is_multi_factor_auth_enabled: Boolean
}
input ForgotPasswordInput {
email: String!
state: String
redirect_uri: String
email: String!
state: String
redirect_uri: String
}
input ResetPasswordInput {
token: String!
password: String!
confirm_password: String!
token: String!
password: String!
confirm_password: String!
}
input DeleteUserInput {
email: String!
email: String!
}
input MagicLinkLoginInput {
email: String!
roles: [String!]
scope: [String!]
state: String
redirect_uri: String
email: String!
roles: [String!]
scope: [String!]
state: String
redirect_uri: String
}
input SessionQueryInput {
roles: [String!]
scope: [String!]
roles: [String!]
scope: [String!]
}
input PaginationInput {
limit: Int64
page: Int64
limit: Int64
page: Int64
}
input PaginatedInput {
pagination: PaginationInput
pagination: PaginationInput
}
input OAuthRevokeInput {
refresh_token: String!
refresh_token: String!
}
input InviteMemberInput {
emails: [String!]!
redirect_uri: String
emails: [String!]!
redirect_uri: String
}
input UpdateAccessInput {
user_id: String!
user_id: String!
}
input ValidateJWTTokenInput {
token_type: String!
token: String!
roles: [String!]
token_type: String!
token: String!
roles: [String!]
}
input GenerateJWTKeysInput {
type: String!
type: String!
}
input ListWebhookLogRequest {
pagination: PaginationInput
webhook_id: String
pagination: PaginationInput
webhook_id: String
}
input AddWebhookRequest {
event_name: String!
endpoint: String!
enabled: Boolean!
headers: Map
event_name: String!
endpoint: String!
enabled: Boolean!
headers: Map
}
input UpdateWebhookRequest {
id: ID!
event_name: String
endpoint: String
enabled: Boolean
headers: Map
id: ID!
event_name: String
endpoint: String
enabled: Boolean
headers: Map
}
input WebhookRequest {
id: ID!
id: ID!
}
input TestEndpointRequest {
endpoint: String!
event_name: String!
headers: Map
endpoint: String!
event_name: String!
headers: Map
}
input AddEmailTemplateRequest {
event_name: String!
template: String!
event_name: String!
subject: String!
template: String!
design: String!
}
input UpdateEmailTemplateRequest {
id: ID!
event_name: String
template: String
id: ID!
event_name: String
template: String
subject: String
design: String
}
input DeleteEmailTemplateRequest {
id: ID!
id: ID!
}
input VerifyOTPRequest {
email: String!
otp: String!
email: String!
otp: String!
}
input ResendOTPRequest {
email: String!
email: String!
}
type Mutation {
signup(params: SignUpInput!): AuthResponse!
login(params: LoginInput!): AuthResponse!
magic_link_login(params: MagicLinkLoginInput!): Response!
logout: Response!
update_profile(params: UpdateProfileInput!): Response!
verify_email(params: VerifyEmailInput!): AuthResponse!
resend_verify_email(params: ResendVerifyEmailInput!): Response!
forgot_password(params: ForgotPasswordInput!): Response!
reset_password(params: ResetPasswordInput!): Response!
revoke(params: OAuthRevokeInput!): Response!
verify_otp(params: VerifyOTPRequest!): AuthResponse!
resend_otp(params: ResendOTPRequest!): Response!
# admin only apis
_delete_user(params: DeleteUserInput!): Response!
_update_user(params: UpdateUserInput!): User!
_admin_signup(params: AdminSignupInput!): Response!
_admin_login(params: AdminLoginInput!): Response!
_admin_logout: 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!
_add_webhook(params: AddWebhookRequest!): Response!
_update_webhook(params: UpdateWebhookRequest!): Response!
_delete_webhook(params: WebhookRequest!): Response!
_test_endpoint(params: TestEndpointRequest!): TestEndpointResponse!
_add_email_template(params: AddEmailTemplateRequest!): Response!
_update_email_template(params: UpdateEmailTemplateRequest!): Response!
_delete_email_template(params: DeleteEmailTemplateRequest!): Response!
signup(params: SignUpInput!): AuthResponse!
login(params: LoginInput!): AuthResponse!
magic_link_login(params: MagicLinkLoginInput!): Response!
logout: Response!
update_profile(params: UpdateProfileInput!): Response!
verify_email(params: VerifyEmailInput!): AuthResponse!
resend_verify_email(params: ResendVerifyEmailInput!): Response!
forgot_password(params: ForgotPasswordInput!): Response!
reset_password(params: ResetPasswordInput!): Response!
revoke(params: OAuthRevokeInput!): Response!
verify_otp(params: VerifyOTPRequest!): AuthResponse!
resend_otp(params: ResendOTPRequest!): Response!
# admin only apis
_delete_user(params: DeleteUserInput!): Response!
_update_user(params: UpdateUserInput!): User!
_admin_signup(params: AdminSignupInput!): Response!
_admin_login(params: AdminLoginInput!): Response!
_admin_logout: 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!
_add_webhook(params: AddWebhookRequest!): Response!
_update_webhook(params: UpdateWebhookRequest!): Response!
_delete_webhook(params: WebhookRequest!): Response!
_test_endpoint(params: TestEndpointRequest!): TestEndpointResponse!
_add_email_template(params: AddEmailTemplateRequest!): Response!
_update_email_template(params: UpdateEmailTemplateRequest!): Response!
_delete_email_template(params: DeleteEmailTemplateRequest!): Response!
}
type Query {
meta: Meta!
session(params: SessionQueryInput): AuthResponse!
profile: User!
validate_jwt_token(params: ValidateJWTTokenInput!): ValidateJWTTokenResponse!
# admin only apis
_users(params: PaginatedInput): Users!
_verification_requests(params: PaginatedInput): VerificationRequests!
_admin_session: Response!
_env: Env!
_webhook(params: WebhookRequest!): Webhook!
_webhooks(params: PaginatedInput): Webhooks!
_webhook_logs(params: ListWebhookLogRequest): WebhookLogs!
_email_templates(params: PaginatedInput): EmailTemplates!
meta: Meta!
session(params: SessionQueryInput): AuthResponse!
profile: User!
validate_jwt_token(params: ValidateJWTTokenInput!): ValidateJWTTokenResponse!
# admin only apis
_users(params: PaginatedInput): Users!
_verification_requests(params: PaginatedInput): VerificationRequests!
_admin_session: Response!
_env: Env!
_webhook(params: WebhookRequest!): Webhook!
_webhooks(params: PaginatedInput): Webhooks!
_webhook_logs(params: ListWebhookLogRequest): WebhookLogs!
_email_templates(params: PaginatedInput): EmailTemplates!
}

View File

@@ -11,166 +11,207 @@ import (
"github.com/authorizerdev/authorizer/server/resolvers"
)
// Signup is the resolver for the signup field.
func (r *mutationResolver) Signup(ctx context.Context, params model.SignUpInput) (*model.AuthResponse, error) {
return resolvers.SignupResolver(ctx, params)
}
// Login is the resolver for the login field.
func (r *mutationResolver) Login(ctx context.Context, params model.LoginInput) (*model.AuthResponse, error) {
return resolvers.LoginResolver(ctx, params)
}
// MagicLinkLogin is the resolver for the magic_link_login field.
func (r *mutationResolver) MagicLinkLogin(ctx context.Context, params model.MagicLinkLoginInput) (*model.Response, error) {
return resolvers.MagicLinkLoginResolver(ctx, params)
}
// Logout is the resolver for the logout field.
func (r *mutationResolver) Logout(ctx context.Context) (*model.Response, error) {
return resolvers.LogoutResolver(ctx)
}
// UpdateProfile is the resolver for the update_profile field.
func (r *mutationResolver) UpdateProfile(ctx context.Context, params model.UpdateProfileInput) (*model.Response, error) {
return resolvers.UpdateProfileResolver(ctx, params)
}
// VerifyEmail is the resolver for the verify_email field.
func (r *mutationResolver) VerifyEmail(ctx context.Context, params model.VerifyEmailInput) (*model.AuthResponse, error) {
return resolvers.VerifyEmailResolver(ctx, params)
}
// ResendVerifyEmail is the resolver for the resend_verify_email field.
func (r *mutationResolver) ResendVerifyEmail(ctx context.Context, params model.ResendVerifyEmailInput) (*model.Response, error) {
return resolvers.ResendVerifyEmailResolver(ctx, params)
}
// ForgotPassword is the resolver for the forgot_password field.
func (r *mutationResolver) ForgotPassword(ctx context.Context, params model.ForgotPasswordInput) (*model.Response, error) {
return resolvers.ForgotPasswordResolver(ctx, params)
}
// ResetPassword is the resolver for the reset_password field.
func (r *mutationResolver) ResetPassword(ctx context.Context, params model.ResetPasswordInput) (*model.Response, error) {
return resolvers.ResetPasswordResolver(ctx, params)
}
// Revoke is the resolver for the revoke field.
func (r *mutationResolver) Revoke(ctx context.Context, params model.OAuthRevokeInput) (*model.Response, error) {
return resolvers.RevokeResolver(ctx, params)
}
// VerifyOtp is the resolver for the verify_otp field.
func (r *mutationResolver) VerifyOtp(ctx context.Context, params model.VerifyOTPRequest) (*model.AuthResponse, error) {
return resolvers.VerifyOtpResolver(ctx, params)
}
// ResendOtp is the resolver for the resend_otp field.
func (r *mutationResolver) ResendOtp(ctx context.Context, params model.ResendOTPRequest) (*model.Response, error) {
return resolvers.ResendOTPResolver(ctx, params)
}
// DeleteUser is the resolver for the _delete_user field.
func (r *mutationResolver) DeleteUser(ctx context.Context, params model.DeleteUserInput) (*model.Response, error) {
return resolvers.DeleteUserResolver(ctx, params)
}
// UpdateUser is the resolver for the _update_user field.
func (r *mutationResolver) UpdateUser(ctx context.Context, params model.UpdateUserInput) (*model.User, error) {
return resolvers.UpdateUserResolver(ctx, params)
}
// AdminSignup is the resolver for the _admin_signup field.
func (r *mutationResolver) AdminSignup(ctx context.Context, params model.AdminSignupInput) (*model.Response, error) {
return resolvers.AdminSignupResolver(ctx, params)
}
// AdminLogin is the resolver for the _admin_login field.
func (r *mutationResolver) AdminLogin(ctx context.Context, params model.AdminLoginInput) (*model.Response, error) {
return resolvers.AdminLoginResolver(ctx, params)
}
// AdminLogout is the resolver for the _admin_logout field.
func (r *mutationResolver) AdminLogout(ctx context.Context) (*model.Response, error) {
return resolvers.AdminLogoutResolver(ctx)
}
// UpdateEnv is the resolver for the _update_env field.
func (r *mutationResolver) UpdateEnv(ctx context.Context, params model.UpdateEnvInput) (*model.Response, error) {
return resolvers.UpdateEnvResolver(ctx, params)
}
// InviteMembers is the resolver for the _invite_members field.
func (r *mutationResolver) InviteMembers(ctx context.Context, params model.InviteMemberInput) (*model.Response, error) {
return resolvers.InviteMembersResolver(ctx, params)
}
// RevokeAccess is the resolver for the _revoke_access field.
func (r *mutationResolver) RevokeAccess(ctx context.Context, param model.UpdateAccessInput) (*model.Response, error) {
return resolvers.RevokeAccessResolver(ctx, param)
}
// EnableAccess is the resolver for the _enable_access field.
func (r *mutationResolver) EnableAccess(ctx context.Context, param model.UpdateAccessInput) (*model.Response, error) {
return resolvers.EnableAccessResolver(ctx, param)
}
// GenerateJwtKeys is the resolver for the _generate_jwt_keys field.
func (r *mutationResolver) GenerateJwtKeys(ctx context.Context, params model.GenerateJWTKeysInput) (*model.GenerateJWTKeysResponse, error) {
return resolvers.GenerateJWTKeysResolver(ctx, params)
}
// AddWebhook is the resolver for the _add_webhook field.
func (r *mutationResolver) AddWebhook(ctx context.Context, params model.AddWebhookRequest) (*model.Response, error) {
return resolvers.AddWebhookResolver(ctx, params)
}
// UpdateWebhook is the resolver for the _update_webhook field.
func (r *mutationResolver) UpdateWebhook(ctx context.Context, params model.UpdateWebhookRequest) (*model.Response, error) {
return resolvers.UpdateWebhookResolver(ctx, params)
}
// DeleteWebhook is the resolver for the _delete_webhook field.
func (r *mutationResolver) DeleteWebhook(ctx context.Context, params model.WebhookRequest) (*model.Response, error) {
return resolvers.DeleteWebhookResolver(ctx, params)
}
// TestEndpoint is the resolver for the _test_endpoint field.
func (r *mutationResolver) TestEndpoint(ctx context.Context, params model.TestEndpointRequest) (*model.TestEndpointResponse, error) {
return resolvers.TestEndpointResolver(ctx, params)
}
// AddEmailTemplate is the resolver for the _add_email_template field.
func (r *mutationResolver) AddEmailTemplate(ctx context.Context, params model.AddEmailTemplateRequest) (*model.Response, error) {
return resolvers.AddEmailTemplateResolver(ctx, params)
}
// UpdateEmailTemplate is the resolver for the _update_email_template field.
func (r *mutationResolver) UpdateEmailTemplate(ctx context.Context, params model.UpdateEmailTemplateRequest) (*model.Response, error) {
return resolvers.UpdateEmailTemplateResolver(ctx, params)
}
// DeleteEmailTemplate is the resolver for the _delete_email_template field.
func (r *mutationResolver) DeleteEmailTemplate(ctx context.Context, params model.DeleteEmailTemplateRequest) (*model.Response, error) {
return resolvers.DeleteEmailTemplateResolver(ctx, params)
}
// Meta is the resolver for the meta field.
func (r *queryResolver) Meta(ctx context.Context) (*model.Meta, error) {
return resolvers.MetaResolver(ctx)
}
// Session is the resolver for the session field.
func (r *queryResolver) Session(ctx context.Context, params *model.SessionQueryInput) (*model.AuthResponse, error) {
return resolvers.SessionResolver(ctx, params)
}
// Profile is the resolver for the profile field.
func (r *queryResolver) Profile(ctx context.Context) (*model.User, error) {
return resolvers.ProfileResolver(ctx)
}
// ValidateJwtToken is the resolver for the validate_jwt_token field.
func (r *queryResolver) ValidateJwtToken(ctx context.Context, params model.ValidateJWTTokenInput) (*model.ValidateJWTTokenResponse, error) {
return resolvers.ValidateJwtTokenResolver(ctx, params)
}
// Users is the resolver for the _users field.
func (r *queryResolver) Users(ctx context.Context, params *model.PaginatedInput) (*model.Users, error) {
return resolvers.UsersResolver(ctx, params)
}
// VerificationRequests is the resolver for the _verification_requests field.
func (r *queryResolver) VerificationRequests(ctx context.Context, params *model.PaginatedInput) (*model.VerificationRequests, error) {
return resolvers.VerificationRequestsResolver(ctx, params)
}
// AdminSession is the resolver for the _admin_session field.
func (r *queryResolver) AdminSession(ctx context.Context) (*model.Response, error) {
return resolvers.AdminSessionResolver(ctx)
}
// Env is the resolver for the _env field.
func (r *queryResolver) Env(ctx context.Context) (*model.Env, error) {
return resolvers.EnvResolver(ctx)
}
// Webhook is the resolver for the _webhook field.
func (r *queryResolver) Webhook(ctx context.Context, params model.WebhookRequest) (*model.Webhook, error) {
return resolvers.WebhookResolver(ctx, params)
}
// Webhooks is the resolver for the _webhooks field.
func (r *queryResolver) Webhooks(ctx context.Context, params *model.PaginatedInput) (*model.Webhooks, error) {
return resolvers.WebhooksResolver(ctx, params)
}
// WebhookLogs is the resolver for the _webhook_logs field.
func (r *queryResolver) WebhookLogs(ctx context.Context, params *model.ListWebhookLogRequest) (*model.WebhookLogs, error) {
return resolvers.WebhookLogsResolver(ctx, params)
}
// EmailTemplates is the resolver for the _email_templates field.
func (r *queryResolver) EmailTemplates(ctx context.Context, params *model.PaginatedInput) (*model.EmailTemplates, error) {
return resolvers.EmailTemplatesResolver(ctx, params)
}

View File

@@ -1,6 +1,7 @@
package handlers
import (
"fmt"
"net/http"
"strconv"
"strings"
@@ -14,7 +15,9 @@ import (
"github.com/authorizerdev/authorizer/server/cookie"
"github.com/authorizerdev/authorizer/server/db"
"github.com/authorizerdev/authorizer/server/memorystore"
"github.com/authorizerdev/authorizer/server/parsers"
"github.com/authorizerdev/authorizer/server/token"
"github.com/authorizerdev/authorizer/server/utils"
)
// AuthorizeHandler is the handler for the /authorize route
@@ -26,6 +29,12 @@ import (
// 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.
const (
authorizeWebMessageTemplate = "authorize_web_message.tmpl"
authorizeFormPostTemplate = "authorize_form_post.tmpl"
)
func AuthorizeHandler() gin.HandlerFunc {
return func(gc *gin.Context) {
redirectURI := strings.TrimSpace(gc.Query("redirect_uri"))
@@ -34,8 +43,8 @@ func AuthorizeHandler() gin.HandlerFunc {
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"))
nonce := strings.TrimSpace(gc.Query("nonce"))
var scope []string
if scopeString == "" {
@@ -45,176 +54,103 @@ func AuthorizeHandler() gin.HandlerFunc {
}
if responseMode == "" {
responseMode = "query"
}
if responseMode != "query" && responseMode != "web_message" {
log.Debug("Invalid response_mode: ", responseMode)
gc.JSON(400, gin.H{"error": "invalid response mode"})
responseMode = constants.ResponseModeQuery
}
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 {
log.Debug("Failed to get client_id: ", clientID)
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 client, err := memorystore.Provider.GetStringStoreEnvVariable(constants.EnvKeyClientID); client != clientID || err != nil {
if isQuery {
gc.Redirect(http.StatusFound, loginURL)
} else {
log.Debug("Invalid client_id: ", clientID)
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 {
log.Debug("Failed to get state: ", state)
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 {
log.Debug("Invalid response_type: ", responseType)
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",
},
},
})
}
if err := validateAuthorizeRequest(responseType, responseMode, clientID, state, codeChallenge); err != nil {
log.Debug("invalid authorization request: ", err)
gc.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
if isResponseTypeCode {
if codeChallenge == "" {
if isQuery {
gc.Redirect(http.StatusFound, loginURL)
} else {
log.Debug("Failed to get code_challenge: ", codeChallenge)
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
}
code := uuid.New().String()
if nonce == "" {
nonce = uuid.New().String()
}
log := log.WithFields(log.Fields{
"response_mode": responseMode,
"response_type": responseType,
"state": state,
"code_challenge": codeChallenge,
"scope": scope,
"redirect_uri": redirectURI,
"nonce": nonce,
"code": code,
})
memorystore.Provider.SetState(codeChallenge, code)
// used for response mode query or fragment
loginState := "state=" + state + "&scope=" + strings.Join(scope, " ") + "&redirect_uri=" + redirectURI + "&code=" + code + "&nonce=" + nonce
loginURL := "/app?" + loginState
if responseMode == constants.ResponseModeFragment {
loginURL = "/app#" + loginState
}
if state == "" {
handleResponse(gc, responseMode, loginURL, redirectURI, map[string]interface{}{
"type": "authorization_response",
"response": map[string]interface{}{
"error": "state_required",
"error_description": "state is required",
},
}, http.StatusOK)
return
}
if responseType == constants.ResponseTypeCode && codeChallenge == "" {
handleResponse(gc, responseMode, loginURL, redirectURI, map[string]interface{}{
"type": "authorization_response",
"response": map[string]interface{}{
"error": "code_challenge_required",
"error_description": "code challenge is required",
},
}, http.StatusOK)
}
loginError := map[string]interface{}{
"type": "authorization_response",
"response": map[string]interface{}{
"error": "login_required",
"error_description": "Login is required",
},
}
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",
},
},
})
}
log.Debug("GetSession failed: ", err)
handleResponse(gc, responseMode, loginURL, redirectURI, loginError, http.StatusOK)
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",
},
},
})
}
log.Debug("ValidateBrowserSession failed: ", err)
handleResponse(gc, responseMode, loginURL, redirectURI, loginError, http.StatusOK)
return
}
userID := claims.Subject
user, err := db.Provider.GetUserByID(gc, 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",
},
},
})
}
log.Debug("GetUserByID failed: ", err)
handleResponse(gc, responseMode, loginURL, redirectURI, map[string]interface{}{
"type": "authorization_response",
"response": map[string]interface{}{
"error": "signup_required",
"error_description": "Sign up required",
},
}, http.StatusOK)
return
}
@@ -223,129 +159,210 @@ func AuthorizeHandler() gin.HandlerFunc {
sessionKey = claims.LoginMethod + ":" + user.ID
}
// if user is logged in
// based on the response type code, generate the response
if isResponseTypeCode {
// rollover the session for security
go memorystore.Provider.DeleteUserSession(sessionKey, claims.Nonce)
nonce := uuid.New().String()
// rollover the session for security
go memorystore.Provider.DeleteUserSession(sessionKey, claims.Nonce)
if responseType == constants.ResponseTypeCode {
newSessionTokenData, newSessionToken, err := token.CreateSessionToken(user, nonce, claims.Roles, scope, claims.LoginMethod)
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",
},
},
})
}
log.Debug("CreateSessionToken failed: ", err)
handleResponse(gc, responseMode, loginURL, redirectURI, loginError, http.StatusOK)
return
}
if err := memorystore.Provider.SetState(codeChallenge, code+"@"+newSessionToken); err != nil {
log.Debug("SetState failed: ", err)
handleResponse(gc, responseMode, loginURL, redirectURI, loginError, http.StatusOK)
return
}
if err := memorystore.Provider.SetUserSession(sessionKey, constants.TokenTypeSessionToken+"_"+newSessionTokenData.Nonce, newSessionToken); err != nil {
log.Debug("SetUserSession failed: ", err)
handleResponse(gc, responseMode, loginURL, redirectURI, loginError, http.StatusOK)
return
}
memorystore.Provider.SetUserSession(user.ID, constants.TokenTypeSessionToken+"_"+newSessionTokenData.Nonce, newSessionToken)
cookie.SetSession(gc, newSessionToken)
code := uuid.New().String()
memorystore.Provider.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,
},
// in case, response type is code and user is already logged in send the code and state
// and cookie session will already be rolled over and set
// gc.HTML(http.StatusOK, authorizeWebMessageTemplate, gin.H{
// "target_origin": redirectURI,
// "authorization_response": map[string]interface{}{
// "type": "authorization_response",
// "response": map[string]string{
// "code": code,
// "state": state,
// },
// },
// })
params := "code=" + code + "&state=" + state + "&nonce=" + nonce
if responseMode == constants.ResponseModeQuery {
if strings.Contains(redirectURI, "?") {
redirectURI = redirectURI + "&" + params
} else {
redirectURI = redirectURI + "?" + params
}
} else if responseMode == constants.ResponseModeFragment {
if strings.Contains(redirectURI, "#") {
redirectURI = redirectURI + "&" + params
} else {
redirectURI = redirectURI + "#" + params
}
}
handleResponse(gc, responseMode, loginURL, redirectURI, map[string]interface{}{
"type": "authorization_response",
"response": map[string]interface{}{
"code": code,
"state": state,
},
})
}, http.StatusOK)
return
}
if isResponseTypeToken {
// rollover the session for security
authToken, err := token.CreateAuthToken(gc, user, claims.Roles, scope, claims.LoginMethod)
if responseType == constants.ResponseTypeToken || responseType == constants.ResponseTypeIDToken {
hostname := parsers.GetHost(gc)
nonce := uuid.New().String()
_, fingerPrintHash, err := token.CreateSessionToken(user, nonce, claims.Roles, scope, claims.LoginMethod)
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",
},
},
})
}
log.Debug("CreateSessionToken failed: ", err)
handleResponse(gc, responseMode, loginURL, redirectURI, loginError, http.StatusOK)
return
}
accessToken, accessTokenExpiresAt, err := token.CreateAccessToken(user, claims.Roles, scope, hostname, nonce, claims.LoginMethod)
if err != nil {
log.Debug("CreateAccessToken failed: ", err)
handleResponse(gc, responseMode, loginURL, redirectURI, loginError, http.StatusOK)
return
}
go memorystore.Provider.DeleteUserSession(sessionKey, claims.Nonce)
memorystore.Provider.SetUserSession(sessionKey, constants.TokenTypeSessionToken+"_"+authToken.FingerPrint, authToken.FingerPrintHash)
memorystore.Provider.SetUserSession(sessionKey, constants.TokenTypeAccessToken+"_"+authToken.FingerPrint, authToken.AccessToken.Token)
cookie.SetSession(gc, authToken.FingerPrintHash)
idToken, _, err := token.CreateIDToken(user, claims.Roles, hostname, nonce, claims.LoginMethod)
if err != nil {
log.Debug("CreateIDToken failed: ", err)
handleResponse(gc, responseMode, loginURL, redirectURI, loginError, http.StatusOK)
return
}
// rollover the session for security
// authToken, err := token.CreateAuthToken(gc, user, claims.Roles, scope, claims.LoginMethod)
// if err != nil {
// log.Debug("CreateAuthToken failed: ", err)
// handleResponse(gc, responseMode, loginURL, redirectURI, loginError, http.StatusOK)
// return
// }
expiresIn := authToken.AccessToken.ExpiresAt - time.Now().Unix()
if err := memorystore.Provider.SetUserSession(sessionKey, constants.TokenTypeSessionToken+"_"+nonce, fingerPrintHash); err != nil {
log.Debug("SetUserSession failed: ", err)
handleResponse(gc, responseMode, loginURL, redirectURI, loginError, http.StatusOK)
return
}
if err := memorystore.Provider.SetUserSession(sessionKey, constants.TokenTypeAccessToken+"_"+nonce, accessToken); err != nil {
log.Debug("SetUserSession failed: ", err)
handleResponse(gc, responseMode, loginURL, redirectURI, loginError, http.StatusOK)
return
}
cookie.SetSession(gc, fingerPrintHash)
expiresIn := accessTokenExpiresAt - 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
params := "access_token=" + accessToken + "&token_type=bearer&expires_in=" + strconv.FormatInt(expiresIn, 10) + "&state=" + state + "&id_token=" + idToken + "&code=" + code + "&nonce=" + nonce
res := map[string]interface{}{
"access_token": authToken.AccessToken.Token,
"id_token": authToken.IDToken.Token,
"access_token": accessToken,
"id_token": idToken,
"state": state,
"scope": scope,
"token_type": "Bearer",
"expires_in": expiresIn,
"code": code,
"nonce": nonce,
}
if authToken.RefreshToken != nil {
res["refresh_token"] = authToken.RefreshToken.Token
params += "&refresh_token=" + authToken.RefreshToken.Token
memorystore.Provider.SetUserSession(sessionKey, constants.TokenTypeRefreshToken+"_"+authToken.FingerPrint, authToken.RefreshToken.Token)
}
if isQuery {
if strings.Contains(redirectURI, "?") {
gc.Redirect(http.StatusFound, redirectURI+"&"+params)
} else {
gc.Redirect(http.StatusFound, redirectURI+"?"+params)
if utils.StringSliceContains(scope, "offline_access") {
refreshToken, _, err := token.CreateRefreshToken(user, claims.Roles, scope, hostname, nonce, claims.LoginMethod)
if err != nil {
log.Debug("SetUserSession failed: ", err)
handleResponse(gc, responseMode, loginURL, redirectURI, loginError, http.StatusOK)
return
}
} else {
gc.HTML(http.StatusOK, template, gin.H{
"target_origin": redirectURI,
"authorization_response": map[string]interface{}{
"type": "authorization_response",
"response": res,
},
})
res["refresh_token"] = refreshToken
params += "&refresh_token=" + refreshToken
memorystore.Provider.SetUserSession(sessionKey, constants.TokenTypeRefreshToken+"_"+nonce, refreshToken)
}
if responseMode == constants.ResponseModeQuery {
if strings.Contains(redirectURI, "?") {
redirectURI = redirectURI + "&" + params
} else {
redirectURI = redirectURI + "?" + params
}
} else if responseMode == constants.ResponseModeFragment {
if strings.Contains(redirectURI, "#") {
redirectURI = redirectURI + "&" + params
} else {
redirectURI = redirectURI + "#" + params
}
}
handleResponse(gc, responseMode, loginURL, redirectURI, map[string]interface{}{
"type": "authorization_response",
"response": res,
}, http.StatusOK)
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",
},
},
})
}
handleResponse(gc, responseMode, loginURL, redirectURI, loginError, http.StatusOK)
}
}
func validateAuthorizeRequest(responseType, responseMode, clientID, state, codeChallenge string) error {
if responseType != constants.ResponseTypeCode && responseType != constants.ResponseTypeToken && responseType != constants.ResponseTypeIDToken {
return fmt.Errorf("invalid response type %s. 'code' & 'token' are valid response_type", responseMode)
}
if responseMode != constants.ResponseModeQuery && responseMode != constants.ResponseModeWebMessage && responseMode != constants.ResponseModeFragment && responseMode != constants.ResponseModeFormPost {
return fmt.Errorf("invalid response mode %s. 'query', 'fragment', 'form_post' and 'web_message' are valid response_mode", responseMode)
}
if client, err := memorystore.Provider.GetStringStoreEnvVariable(constants.EnvKeyClientID); client != clientID || err != nil {
return fmt.Errorf("invalid client_id %s", clientID)
}
return nil
}
func handleResponse(gc *gin.Context, responseMode, loginURI, redirectURI string, data map[string]interface{}, httpStatusCode int) {
isAuthenticationRequired := false
if _, ok := data["response"].(map[string]interface{})["error"]; ok {
isAuthenticationRequired = true
}
switch responseMode {
case constants.ResponseModeQuery, constants.ResponseModeFragment:
if isAuthenticationRequired {
gc.Redirect(http.StatusFound, loginURI)
} else {
gc.Redirect(http.StatusFound, redirectURI)
}
return
case constants.ResponseModeWebMessage:
gc.HTML(httpStatusCode, authorizeWebMessageTemplate, gin.H{
"target_origin": redirectURI,
"authorization_response": data,
})
return
case constants.ResponseModeFormPost:
gc.HTML(httpStatusCode, authorizeFormPostTemplate, gin.H{
"target_origin": redirectURI,
"authorization_response": data["response"],
})
return
}
}

View File

@@ -2,7 +2,7 @@ package handlers
import (
"github.com/99designs/gqlgen/graphql/handler"
"github.com/authorizerdev/authorizer/server/graph"
graph "github.com/authorizerdev/authorizer/server/graph"
"github.com/authorizerdev/authorizer/server/graph/generated"
"github.com/gin-gonic/gin"
)

View File

@@ -67,6 +67,8 @@ func OAuthCallbackHandler() gin.HandlerFunc {
user, err = processLinkedInUserInfo(code)
case constants.AuthRecipeMethodApple:
user, err = processAppleUserInfo(code)
case constants.AuthRecipeMethodTwitter:
user, err = processTwitterUserInfo(code, sessionState)
default:
log.Info("Invalid oauth provider")
err = fmt.Errorf(`invalid oauth provider`)
@@ -285,9 +287,9 @@ func processGithubUserInfo(code string) (models.User, error) {
log.Debug("Failed to create github user info request: ", err)
return user, fmt.Errorf("error creating github user info request: %s", err.Error())
}
req.Header = http.Header{
"Authorization": []string{fmt.Sprintf("token %s", oauth2Token.AccessToken)},
}
req.Header.Set(
"Authorization", fmt.Sprintf("token %s", oauth2Token.AccessToken),
)
response, err := client.Do(req)
if err != nil {
@@ -329,14 +331,14 @@ func processGithubUserInfo(code string) (models.User, error) {
}
// fetch using /users/email endpoint
req, err := http.NewRequest("GET", constants.GithubUserEmails, nil)
req, err := http.NewRequest(http.MethodGet, constants.GithubUserEmails, nil)
if err != nil {
log.Debug("Failed to create github emails request: ", err)
return user, fmt.Errorf("error creating github user info request: %s", err.Error())
}
req.Header = http.Header{
"Authorization": []string{fmt.Sprintf("token %s", oauth2Token.AccessToken)},
}
req.Header.Set(
"Authorization", fmt.Sprintf("token %s", oauth2Token.AccessToken),
)
response, err := client.Do(req)
if err != nil {
@@ -564,3 +566,70 @@ func processAppleUserInfo(code string) (models.User, error) {
return user, err
}
func processTwitterUserInfo(code, verifier string) (models.User, error) {
user := models.User{}
oauth2Token, err := oauth.OAuthProviders.TwitterConfig.Exchange(oauth2.NoContext, code, oauth2.SetAuthURLParam("code_verifier", verifier))
if err != nil {
log.Debug("Failed to exchange code for token: ", err)
return user, fmt.Errorf("invalid twitter exchange code: %s", err.Error())
}
client := http.Client{}
req, err := http.NewRequest("GET", constants.TwitterUserInfoURL, nil)
if err != nil {
log.Debug("Failed to create Twitter user info request: ", err)
return user, fmt.Errorf("error creating Twitter user info request: %s", err.Error())
}
req.Header = http.Header{
"Authorization": []string{fmt.Sprintf("Bearer %s", oauth2Token.AccessToken)},
}
response, err := client.Do(req)
if err != nil {
log.Debug("Failed to request Twitter user info: ", err)
return user, err
}
defer response.Body.Close()
body, err := ioutil.ReadAll(response.Body)
if err != nil {
log.Debug("Failed to read Twitter user info response body: ", err)
return user, fmt.Errorf("failed to read Twitter response body: %s", err.Error())
}
if response.StatusCode >= 400 {
log.Debug("Failed to request Twitter user info: ", string(body))
return user, fmt.Errorf("failed to request Twitter user info: %s", string(body))
}
responseRawData := make(map[string]interface{})
json.Unmarshal(body, &responseRawData)
userRawData := responseRawData["data"].(map[string]interface{})
// log.Info(userRawData)
// Twitter API does not return E-Mail adresses by default. For that case special privileges have
// to be granted on a per-App basis. See https://developer.twitter.com/en/docs/twitter-api/v1/accounts-and-users/manage-account-settings/api-reference/get-account-verify_credentials
// Currently Twitter API only provides the full name of a user. To fill givenName and familyName
// the full name will be split at the first whitespace. This approach will not be valid for all name combinations
nameArr := strings.SplitAfterN(userRawData["name"].(string), " ", 2)
firstName := nameArr[0]
lastName := ""
if len(nameArr) == 2 {
lastName = nameArr[1]
}
nickname := userRawData["username"].(string)
profilePicture := userRawData["profile_image_url"].(string)
user = models.User{
GivenName: &firstName,
FamilyName: &lastName,
Picture: &profilePicture,
Nickname: &nickname,
}
return user, nil
}

View File

@@ -12,6 +12,7 @@ import (
"github.com/authorizerdev/authorizer/server/memorystore"
"github.com/authorizerdev/authorizer/server/oauth"
"github.com/authorizerdev/authorizer/server/parsers"
"github.com/authorizerdev/authorizer/server/utils"
"github.com/authorizerdev/authorizer/server/validators"
)
@@ -95,7 +96,7 @@ func OAuthLoginHandler() gin.HandlerFunc {
}
oauthStateString := state + "___" + redirectURI + "___" + roles + "___" + strings.Join(scope, ",")
oauthStateString := state + "___" + redirectURI + "___" + roles + "___" + strings.Join(scope, " ")
provider := c.Param("oauth_provider")
isProviderConfigured := true
@@ -169,6 +170,26 @@ func OAuthLoginHandler() gin.HandlerFunc {
oauth.OAuthProviders.LinkedInConfig.RedirectURL = hostname + "/oauth_callback/" + constants.AuthRecipeMethodLinkedIn
url := oauth.OAuthProviders.LinkedInConfig.AuthCodeURL(oauthStateString)
c.Redirect(http.StatusTemporaryRedirect, url)
case constants.AuthRecipeMethodTwitter:
if oauth.OAuthProviders.TwitterConfig == nil {
log.Debug("Twitter OAuth provider is not configured")
isProviderConfigured = false
break
}
verifier, challenge := utils.GenerateCodeChallenge()
err := memorystore.Provider.SetState(oauthStateString, verifier)
if err != nil {
log.Debug("Error setting state: ", err)
c.JSON(500, gin.H{
"error": "internal server error",
})
return
}
oauth.OAuthProviders.TwitterConfig.RedirectURL = hostname + "/oauth_callback/" + constants.AuthRecipeMethodTwitter
url := oauth.OAuthProviders.TwitterConfig.AuthCodeURL(oauthStateString, oauth2.SetAuthURLParam("code_challenge", challenge), oauth2.SetAuthURLParam("code_challenge_method", "S256"))
c.Redirect(http.StatusTemporaryRedirect, url)
case constants.AuthRecipeMethodApple:
if oauth.OAuthProviders.AppleConfig == nil {
log.Debug("Apple OAuth provider is not configured")

View File

@@ -17,14 +17,14 @@ func OpenIDConfigurationHandler() gin.HandlerFunc {
c.JSON(200, gin.H{
"issuer": issuer,
"authorization_endpoint": issuer + "/authorize",
"token_endpoint": issuer + "/token",
"token_endpoint": issuer + "/oauth/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"},
"response_types_supported": []string{"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"},
"response_modes_supported": []string{"query", "fragment", "form_post", "web_message"},
"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"},
"claims_supported": []string{"aud", "exp", "iss", "iat", "sub", "given_name", "family_name", "middle_name", "nickname", "preferred_username", "picture", "email", "email_verified", "roles", "role", "gender", "birthdate", "phone_number", "phone_number_verified", "nonce", "updated_at", "created_at", "revoked_timestamp", "login_method", "signup_methods", "token_type"},
})
}
}

View File

@@ -76,7 +76,6 @@ func TokenHandler() gin.HandlerFunc {
sessionKey := ""
if isAuthorizationCodeGrant {
if codeVerifier == "" {
log.Debug("Code verifier is empty")
gc.JSON(http.StatusBadRequest, gin.H{
@@ -134,15 +133,18 @@ func TokenHandler() gin.HandlerFunc {
})
return
}
userID = claims.Subject
roles = claims.Roles
scope = claims.Scope
loginMethod = claims.LoginMethod
// rollover the session for security
sessionKey = userID
if loginMethod != "" {
sessionKey = loginMethod + ":" + userID
}
go memorystore.Provider.DeleteUserSession(sessionKey, claims.Nonce)
} else {
// validate refresh token

View File

@@ -58,7 +58,10 @@ func main() {
default:
logLevel = logrus.InfoLevel
}
// set log level globally
logrus.SetLevel(logLevel)
// set log level for go-gin middleware
log.SetLevel(logLevel)
// show file path in log for debug or other log levels.
@@ -109,6 +112,7 @@ func main() {
router := routes.InitRouter(log)
log.Info("Starting Authorizer: ", VERSION)
port, err := memorystore.Provider.GetStringStoreEnvVariable(constants.EnvKeyPort)
log.Info("Authorizer running at PORT: ", port)
if err != nil {
log.Info("Error while getting port from env using default port 8080: ", err)
port = "8080"

View File

@@ -34,6 +34,8 @@ func InitMemStore() error {
constants.EnvKeyIsEmailServiceEnabled: false,
constants.EnvKeyEnforceMultiFactorAuthentication: false,
constants.EnvKeyDisableMultiFactorAuthentication: false,
constants.EnvKeyAppCookieSecure: true,
constants.EnvKeyAdminCookieSecure: true,
}
requiredEnvs := RequiredEnvStoreObj.GetRequiredEnv()
@@ -55,7 +57,7 @@ func InitMemStore() error {
}
redisURL := requiredEnvs.RedisURL
if redisURL != "" && !requiredEnvs.disableRedisForEnv {
if redisURL != "" && !requiredEnvs.DisableRedisForEnv {
log.Info("Initializing Redis memory store")
Provider, err = redis.NewRedisProvider(redisURL)
if err != nil {

View File

@@ -7,7 +7,7 @@ import (
"github.com/authorizerdev/authorizer/server/constants"
)
// SetUserSession sets the user session
// SetUserSession sets the user session for given user identifier in form recipe:user_id
func (c *provider) SetUserSession(userId, key, token string) error {
c.sessionStore.Set(userId, key, token)
return nil
@@ -34,6 +34,7 @@ func (c *provider) DeleteAllUserSessions(userId string) error {
constants.AuthRecipeMethodGithub,
constants.AuthRecipeMethodGoogle,
constants.AuthRecipeMethodLinkedIn,
constants.AuthRecipeMethodTwitter,
}
for _, namespace := range namespaces {

View File

@@ -2,7 +2,7 @@ package providers
// Provider defines current memory store provider
type Provider interface {
// SetUserSession sets the user session
// SetUserSession sets the user session for given user identifier in form recipe:user_id
SetUserSession(userId, key, token string) error
// GetAllUserSessions returns all the user sessions from the session store
GetAllUserSessions(userId string) (map[string]string, error)

View File

@@ -14,7 +14,7 @@ var (
envStorePrefix = "authorizer_env"
)
// SetUserSession sets the user session in redis store.
// SetUserSession sets the user session for given user identifier in form recipe:user_id
func (c *provider) SetUserSession(userId, key, token string) error {
err := c.store.HSet(c.ctx, userId, key, token).Err()
if err != nil {
@@ -71,6 +71,7 @@ func (c *provider) DeleteAllUserSessions(userID string) error {
constants.AuthRecipeMethodGithub,
constants.AuthRecipeMethodGoogle,
constants.AuthRecipeMethodLinkedIn,
constants.AuthRecipeMethodTwitter,
}
for _, namespace := range namespaces {
err := c.store.Del(c.ctx, namespace+":"+userID).Err()
@@ -160,7 +161,7 @@ func (c *provider) GetEnvStore() (map[string]interface{}, error) {
return nil, err
}
for key, value := range data {
if key == constants.EnvKeyDisableBasicAuthentication || key == constants.EnvKeyDisableEmailVerification || key == constants.EnvKeyDisableLoginPage || key == constants.EnvKeyDisableMagicLinkLogin || key == constants.EnvKeyDisableRedisForEnv || key == constants.EnvKeyDisableSignUp || key == constants.EnvKeyDisableStrongPassword || key == constants.EnvKeyIsEmailServiceEnabled || key == constants.EnvKeyEnforceMultiFactorAuthentication || key == constants.EnvKeyDisableMultiFactorAuthentication {
if key == constants.EnvKeyDisableBasicAuthentication || key == constants.EnvKeyDisableEmailVerification || key == constants.EnvKeyDisableLoginPage || key == constants.EnvKeyDisableMagicLinkLogin || key == constants.EnvKeyDisableRedisForEnv || key == constants.EnvKeyDisableSignUp || key == constants.EnvKeyDisableStrongPassword || key == constants.EnvKeyIsEmailServiceEnabled || key == constants.EnvKeyEnforceMultiFactorAuthentication || key == constants.EnvKeyDisableMultiFactorAuthentication || key == constants.EnvKeyAppCookieSecure || key == constants.EnvKeyAdminCookieSecure {
boolValue, err := strconv.ParseBool(value)
if err != nil {
return res, err

View File

@@ -27,7 +27,7 @@ type RequiredEnv struct {
DatabaseCertKey string `json:"DATABASE_CERT_KEY"`
DatabaseCACert string `json:"DATABASE_CA_CERT"`
RedisURL string `json:"REDIS_URL"`
disableRedisForEnv bool `json:"DISABLE_REDIS_FOR_ENV"`
DisableRedisForEnv bool `json:"DISABLE_REDIS_FOR_ENV"`
}
// RequiredEnvObj is a simple in-memory store for sessions.
@@ -138,7 +138,7 @@ func InitRequiredEnv() error {
DatabaseCertKey: dbCertKey,
DatabaseCACert: dbCACert,
RedisURL: redisURL,
disableRedisForEnv: disableRedisForEnv,
DisableRedisForEnv: disableRedisForEnv,
}
RequiredEnvStoreObj = &RequiredEnvStore{

View File

@@ -20,6 +20,7 @@ type OAuthProvider struct {
FacebookConfig *oauth2.Config
LinkedInConfig *oauth2.Config
AppleConfig *oauth2.Config
TwitterConfig *oauth2.Config
}
// OIDCProviders is a struct that contains reference all the OpenID providers
@@ -74,6 +75,7 @@ func InitOAuth() error {
ClientSecret: githubClientSecret,
RedirectURL: "/oauth_callback/github",
Endpoint: githubOAuth2.Endpoint,
Scopes: []string{"read:user", "user:email"},
}
}
@@ -133,5 +135,28 @@ func InitOAuth() error {
}
}
twitterClientID, err := memorystore.Provider.GetStringStoreEnvVariable(constants.EnvKeyTwitterClientID)
if err != nil {
twitterClientID = ""
}
twitterClientSecret, err := memorystore.Provider.GetStringStoreEnvVariable(constants.EnvKeyTwitterClientSecret)
if err != nil {
twitterClientSecret = ""
}
if twitterClientID != "" && twitterClientSecret != "" {
OAuthProviders.TwitterConfig = &oauth2.Config{
ClientID: twitterClientID,
ClientSecret: twitterClientSecret,
RedirectURL: "/oauth_callback/twitter",
Endpoint: oauth2.Endpoint{
// Endpoint is currently not yet part of oauth2-package. See https://go-review.googlesource.com/c/oauth2/+/350889 for status
AuthURL: "https://twitter.com/i/oauth2/authorize",
TokenURL: "https://api.twitter.com/2/oauth2/token",
AuthStyle: oauth2.AuthStyleInHeader,
},
Scopes: []string{"tweet.read", "users.read"},
}
}
return nil
}

View File

@@ -11,8 +11,8 @@ import (
)
// GetHost returns hostname from request context
// if X-Authorizer-URL header is set it is given highest priority
// if EnvKeyAuthorizerURL is set it is given second highest priority.
// if EnvKeyAuthorizerURL is set it is given highest priority.
// if X-Authorizer-URL header is set it is given second highest priority
// if above 2 are not set the requesting host name is used
func GetHost(c *gin.Context) string {
authorizerURL, err := memorystore.Provider.GetStringStoreEnvVariable(constants.EnvKeyAuthorizerURL)
@@ -91,7 +91,7 @@ func GetDomainName(uri string) string {
return host
}
// GetAppURL to get /app/ url if not configured by user
// GetAppURL to get /app url if not configured by user
func GetAppURL(gc *gin.Context) string {
envAppURL, err := memorystore.Provider.GetStringStoreEnvVariable(constants.EnvKeyAppURL)
if envAppURL == "" || err != nil {

View File

@@ -14,8 +14,6 @@ import (
log "github.com/sirupsen/logrus"
)
// TODO add template validator
// AddEmailTemplateResolver resolver for add email template mutation
func AddEmailTemplateResolver(ctx context.Context, params model.AddEmailTemplateRequest) (*model.Response, error) {
gc, err := utils.GinContextFromContext(ctx)
@@ -34,13 +32,23 @@ func AddEmailTemplateResolver(ctx context.Context, params model.AddEmailTemplate
return nil, fmt.Errorf("invalid event name %s", params.EventName)
}
if strings.TrimSpace(params.Subject) == "" {
return nil, fmt.Errorf("empty subject not allowed")
}
if strings.TrimSpace(params.Template) == "" {
return nil, fmt.Errorf("empty template not allowed")
}
if strings.TrimSpace(params.Design) == "" {
return nil, fmt.Errorf("empty design not allowed")
}
_, err = db.Provider.AddEmailTemplate(ctx, models.EmailTemplate{
EventName: params.EventName,
Template: params.Template,
Subject: params.Subject,
Design: params.Design,
})
if err != nil {
log.Debug("Failed to add email template: ", err)

View File

@@ -50,6 +50,34 @@ func DeleteUserResolver(ctx context.Context, params model.DeleteUserInput) (*mod
}
go func() {
// delete otp for given email
otp, err := db.Provider.GetOTPByEmail(ctx, user.Email)
if err != nil {
log.Infof("No OTP found for email (%s): %v", user.Email, err)
// continue
} else {
err := db.Provider.DeleteOTP(ctx, otp)
if err != nil {
log.Debugf("Failed to delete otp for given email (%s): %v", user.Email, err)
// continue
}
}
// delete verification requests for given email
for _, vt := range constants.VerificationTypes {
verificationRequest, err := db.Provider.GetVerificationRequestByEmail(ctx, user.Email, vt)
if err != nil {
log.Infof("No verification verification request found for email: %s, verification_request_type: %s. %v", user.Email, vt, err)
// continue
} else {
err := db.Provider.DeleteVerificationRequest(ctx, verificationRequest)
if err != nil {
log.Debugf("Failed to DeleteVerificationRequest for email: %s, verification_request_type: %s. %v", user.Email, vt, err)
// continue
}
}
}
memorystore.Provider.DeleteAllUserSessions(user.ID)
utils.RegisterEvent(ctx, constants.UserDeletedWebhookEvent, "", user)
}()

View File

@@ -143,6 +143,13 @@ func EnvResolver(ctx context.Context) (*model.Env, error) {
if val, ok := store[constants.EnvKeyAppleClientSecret]; ok {
res.AppleClientSecret = refs.NewStringRef(val.(string))
}
if val, ok := store[constants.EnvKeyTwitterClientID]; ok {
res.TwitterClientID = refs.NewStringRef(val.(string))
}
if val, ok := store[constants.EnvKeyTwitterClientSecret]; ok {
res.TwitterClientSecret = refs.NewStringRef(val.(string))
}
if val, ok := store[constants.EnvKeyOrganizationName]; ok {
res.OrganizationName = refs.NewStringRef(val.(string))
}
@@ -172,6 +179,8 @@ func EnvResolver(ctx context.Context) (*model.Env, error) {
res.DisableStrongPassword = store[constants.EnvKeyDisableStrongPassword].(bool)
res.EnforceMultiFactorAuthentication = store[constants.EnvKeyEnforceMultiFactorAuthentication].(bool)
res.DisableMultiFactorAuthentication = store[constants.EnvKeyDisableMultiFactorAuthentication].(bool)
res.AdminCookieSecure = store[constants.EnvKeyAdminCookieSecure].(bool)
res.AppCookieSecure = store[constants.EnvKeyAppCookieSecure].(bool)
return res, nil
}

View File

@@ -15,6 +15,7 @@ import (
"github.com/authorizerdev/authorizer/server/graph/model"
"github.com/authorizerdev/authorizer/server/memorystore"
"github.com/authorizerdev/authorizer/server/parsers"
"github.com/authorizerdev/authorizer/server/refs"
"github.com/authorizerdev/authorizer/server/token"
"github.com/authorizerdev/authorizer/server/utils"
"github.com/authorizerdev/authorizer/server/validators"
@@ -49,7 +50,7 @@ func ForgotPasswordResolver(ctx context.Context, params model.ForgotPasswordInpu
log := log.WithFields(log.Fields{
"email": params.Email,
})
_, err = db.Provider.GetUserByEmail(ctx, params.Email)
user, err := db.Provider.GetUserByEmail(ctx, params.Email)
if err != nil {
log.Debug("User not found: ", err)
return res, fmt.Errorf(`user with this email not found`)
@@ -61,9 +62,9 @@ func ForgotPasswordResolver(ctx context.Context, params model.ForgotPasswordInpu
log.Debug("Failed to generate nonce: ", err)
return res, err
}
redirectURL := parsers.GetAppURL(gc) + "/reset-password"
if params.RedirectURI != nil {
redirectURL = *params.RedirectURI
redirectURL := parsers.GetAppURL(gc)
if strings.TrimSpace(refs.StringValue(params.RedirectURI)) != "" {
redirectURL = refs.StringValue(params.RedirectURI)
}
verificationToken, err := token.CreateVerificationToken(params.Email, constants.VerificationTypeForgotPassword, hostname, nonceHash, redirectURL)
@@ -84,8 +85,12 @@ func ForgotPasswordResolver(ctx context.Context, params model.ForgotPasswordInpu
return res, err
}
// exec it as go routin so that we can reduce the api latency
go email.SendForgotPasswordMail(params.Email, verificationToken, hostname)
// execute it as go routine so that we can reduce the api latency
go email.SendEmail([]string{params.Email}, constants.VerificationTypeForgotPassword, map[string]interface{}{
"user": user.ToMap(),
"organization": utils.GetOrganization(),
"verification_url": utils.GetForgotPasswordURL(verificationToken, hostname),
})
res = &model.Response{
Message: `Please check your inbox! We have sent a password reset link.`,

View File

@@ -115,7 +115,7 @@ func InviteMembersResolver(ctx context.Context, params model.InviteMemberInput)
return nil, err
}
verificationToken, err := token.CreateVerificationToken(email, constants.VerificationTypeForgotPassword, hostname, nonceHash, redirectURL)
verificationToken, err := token.CreateVerificationToken(email, constants.VerificationTypeInviteMember, hostname, nonceHash, redirectURL)
if err != nil {
log.Debug("Failed to create verification token: ", err)
}
@@ -135,7 +135,7 @@ func InviteMembersResolver(ctx context.Context, params model.InviteMemberInput)
} else {
// use basic authentication if that option is on
user.SignupMethods = constants.AuthRecipeMethodBasicAuth
verificationRequest.Identifier = constants.VerificationTypeForgotPassword
verificationRequest.Identifier = constants.VerificationTypeInviteMember
isMFAEnforced, err := memorystore.Provider.GetBoolStoreEnvVariable(constants.EnvKeyEnforceMultiFactorAuthentication)
if err != nil {
@@ -162,7 +162,12 @@ func InviteMembersResolver(ctx context.Context, params model.InviteMemberInput)
return nil, err
}
go emailservice.InviteEmail(email, verificationToken, verifyEmailURL, redirectURL)
// exec it as go routine so that we can reduce the api latency
go emailservice.SendEmail([]string{user.Email}, constants.VerificationTypeInviteMember, map[string]interface{}{
"user": user.ToMap(),
"organization": utils.GetOrganization(),
"verification_url": utils.GetInviteVerificationURL(verifyEmailURL, verificationToken, redirectURL),
})
}
return &model.Response{

View File

@@ -50,7 +50,7 @@ func LoginResolver(ctx context.Context, params model.LoginInput) (*model.AuthRes
user, err := db.Provider.GetUserByEmail(ctx, params.Email)
if err != nil {
log.Debug("Failed to get user by email: ", err)
return res, fmt.Errorf(`user with this email not found`)
return res, fmt.Errorf(`bad user credentials`)
}
if user.RevokedTimestamp != nil {
@@ -72,7 +72,7 @@ func LoginResolver(ctx context.Context, params model.LoginInput) (*model.AuthRes
if err != nil {
log.Debug("Failed to compare password: ", err)
return res, fmt.Errorf(`invalid password`)
return res, fmt.Errorf(`bad user credentials`)
}
defaultRolesString, err := memorystore.Provider.GetStringStoreEnvVariable(constants.EnvKeyDefaultRoles)
@@ -123,7 +123,12 @@ func LoginResolver(ctx context.Context, params model.LoginInput) (*model.AuthRes
}
go func() {
err := email.SendOtpMail(user.Email, otpData.Otp)
// exec it as go routine so that we can reduce the api latency
go email.SendEmail([]string{params.Email}, constants.VerificationTypeOTP, map[string]interface{}{
"user": user.ToMap(),
"organization": utils.GetOrganization(),
"otp": otpData.Otp,
})
if err != nil {
log.Debug("Failed to send otp email: ", err)
}

View File

@@ -219,8 +219,12 @@ func MagicLinkLoginResolver(ctx context.Context, params model.MagicLinkLoginInpu
return res, err
}
// exec it as go routing so that we can reduce the api latency
go email.SendVerificationMail(params.Email, verificationToken, hostname)
// exec it as go routine so that we can reduce the api latency
go email.SendEmail([]string{params.Email}, constants.VerificationTypeMagicLinkLogin, map[string]interface{}{
"user": user.ToMap(),
"organization": utils.GetOrganization(),
"verification_url": utils.GetEmailVerificationURL(verificationToken, hostname),
})
}
res = &model.Response{

View File

@@ -77,6 +77,18 @@ func MetaResolver(ctx context.Context) (*model.Meta, error) {
githubClientSecret = ""
}
twitterClientID, err := memorystore.Provider.GetStringStoreEnvVariable(constants.EnvKeyTwitterClientID)
if err != nil {
log.Debug("Failed to get Twitter Client ID from environment variable", err)
twitterClientID = ""
}
twitterClientSecret, err := memorystore.Provider.GetStringStoreEnvVariable(constants.EnvKeyTwitterClientSecret)
if err != nil {
log.Debug("Failed to get Twitter Client Secret from environment variable", err)
twitterClientSecret = ""
}
isBasicAuthDisabled, err := memorystore.Provider.GetBoolStoreEnvVariable(constants.EnvKeyDisableBasicAuthentication)
if err != nil {
log.Debug("Failed to get Disable Basic Authentication from environment variable", err)
@@ -121,6 +133,7 @@ func MetaResolver(ctx context.Context) (*model.Meta, error) {
IsFacebookLoginEnabled: facebookClientID != "" && facebookClientSecret != "",
IsLinkedinLoginEnabled: linkedClientID != "" && linkedInClientSecret != "",
IsAppleLoginEnabled: appleClientID != "" && appleClientSecret != "",
IsTwitterLoginEnabled: twitterClientID != "" && twitterClientSecret != "",
IsBasicAuthenticationEnabled: !isBasicAuthDisabled,
IsEmailVerificationEnabled: !isEmailVerificationDisabled,
IsMagicLinkLoginEnabled: !isMagicLinkLoginDisabled,

View File

@@ -84,7 +84,12 @@ func ResendOTPResolver(ctx context.Context, params model.ResendOTPRequest) (*mod
}
go func() {
err := email.SendOtpMail(params.Email, otp)
// exec it as go routine so that we can reduce the api latency
go email.SendEmail([]string{params.Email}, constants.VerificationTypeOTP, map[string]interface{}{
"user": user.ToMap(),
"organization": utils.GetOrganization(),
"otp": otp,
})
if err != nil {
log.Debug("Error sending otp email: ", otp)
}

View File

@@ -39,6 +39,11 @@ func ResendVerifyEmailResolver(ctx context.Context, params model.ResendVerifyEma
return res, fmt.Errorf("invalid identifier")
}
user, err := db.Provider.GetUserByEmail(ctx, params.Email)
if err != nil {
return res, fmt.Errorf("invalid user")
}
verificationRequest, err := db.Provider.GetVerificationRequestByEmail(ctx, params.Email, params.Identifier)
if err != nil {
log.Debug("Failed to get verification request: ", err)
@@ -74,8 +79,12 @@ func ResendVerifyEmailResolver(ctx context.Context, params model.ResendVerifyEma
log.Debug("Failed to add verification request: ", err)
}
// exec it as go routin so that we can reduce the api latency
go email.SendVerificationMail(params.Email, verificationToken, hostname)
// exec it as go routine so that we can reduce the api latency
go email.SendEmail([]string{params.Email}, params.Identifier, map[string]interface{}{
"user": user.ToMap(),
"organization": utils.GetOrganization(),
"verification_url": utils.GetEmailVerificationURL(verificationToken, hostname),
})
res = &model.Response{
Message: `Verification email has been sent. Please check your inbox`,

View File

@@ -221,9 +221,14 @@ func SignupResolver(ctx context.Context, params model.SignUpInput) (*model.AuthR
return res, err
}
// exec it as go routin so that we can reduce the api latency
// exec it as go routine so that we can reduce the api latency
go func() {
email.SendVerificationMail(params.Email, verificationToken, hostname)
// exec it as go routine so that we can reduce the api latency
email.SendEmail([]string{params.Email}, constants.VerificationTypeBasicAuthSignup, map[string]interface{}{
"user": user.ToMap(),
"organization": utils.GetOrganization(),
"verification_url": utils.GetEmailVerificationURL(verificationToken, hostname),
})
utils.RegisterEvent(ctx, constants.UserCreatedWebhookEvent, constants.AuthRecipeMethodBasicAuth, user)
}()

View File

@@ -15,8 +15,6 @@ import (
log "github.com/sirupsen/logrus"
)
// TODO add template validator
// UpdateEmailTemplateResolver resolver for update email template mutation
func UpdateEmailTemplateResolver(ctx context.Context, params model.UpdateEmailTemplateRequest) (*model.Response, error) {
gc, err := utils.GinContextFromContext(ctx)
@@ -51,6 +49,14 @@ func UpdateEmailTemplateResolver(ctx context.Context, params model.UpdateEmailTe
emailTemplateDetails.EventName = refs.StringValue(params.EventName)
}
if params.Subject != nil && emailTemplateDetails.Subject != refs.StringValue(params.Subject) {
if strings.TrimSpace(refs.StringValue(params.Subject)) == "" {
log.Debug("empty subject not allowed")
return nil, fmt.Errorf("empty subject not allowed")
}
emailTemplateDetails.Subject = refs.StringValue(params.Subject)
}
if params.Template != nil && emailTemplateDetails.Template != refs.StringValue(params.Template) {
if strings.TrimSpace(refs.StringValue(params.Template)) == "" {
log.Debug("empty template not allowed")
@@ -59,6 +65,14 @@ func UpdateEmailTemplateResolver(ctx context.Context, params model.UpdateEmailTe
emailTemplateDetails.Template = refs.StringValue(params.Template)
}
if params.Design != nil && emailTemplateDetails.Design != refs.StringValue(params.Design) {
if strings.TrimSpace(refs.StringValue(params.Design)) == "" {
log.Debug("empty design not allowed")
return nil, fmt.Errorf("empty design not allowed")
}
emailTemplateDetails.Design = refs.StringValue(params.Design)
}
_, err = db.Provider.UpdateEmailTemplate(ctx, emailTemplateDetails)
if err != nil {
return nil, err

View File

@@ -31,6 +31,7 @@ func clearSessionIfRequired(currentData, updatedData map[string]interface{}) {
isCurrentGoogleLoginEnabled := currentData[constants.EnvKeyGoogleClientID] != nil && currentData[constants.EnvKeyGoogleClientSecret] != nil && currentData[constants.EnvKeyGoogleClientID].(string) != "" && currentData[constants.EnvKeyGoogleClientSecret].(string) != ""
isCurrentGithubLoginEnabled := currentData[constants.EnvKeyGithubClientID] != nil && currentData[constants.EnvKeyGithubClientSecret] != nil && currentData[constants.EnvKeyGithubClientID].(string) != "" && currentData[constants.EnvKeyGithubClientSecret].(string) != ""
isCurrentLinkedInLoginEnabled := currentData[constants.EnvKeyLinkedInClientID] != nil && currentData[constants.EnvKeyLinkedInClientSecret] != nil && currentData[constants.EnvKeyLinkedInClientID].(string) != "" && currentData[constants.EnvKeyLinkedInClientSecret].(string) != ""
isCurrentTwitterLoginEnabled := currentData[constants.EnvKeyTwitterClientID] != nil && currentData[constants.EnvKeyTwitterClientSecret] != nil && currentData[constants.EnvKeyTwitterClientID].(string) != "" && currentData[constants.EnvKeyTwitterClientSecret].(string) != ""
isUpdatedBasicAuthEnabled := !updatedData[constants.EnvKeyDisableBasicAuthentication].(bool)
isUpdatedMagicLinkLoginEnabled := !updatedData[constants.EnvKeyDisableMagicLinkLogin].(bool)
@@ -39,6 +40,7 @@ func clearSessionIfRequired(currentData, updatedData map[string]interface{}) {
isUpdatedGoogleLoginEnabled := updatedData[constants.EnvKeyGoogleClientID] != nil && updatedData[constants.EnvKeyGoogleClientSecret] != nil && updatedData[constants.EnvKeyGoogleClientID].(string) != "" && updatedData[constants.EnvKeyGoogleClientSecret].(string) != ""
isUpdatedGithubLoginEnabled := updatedData[constants.EnvKeyGithubClientID] != nil && updatedData[constants.EnvKeyGithubClientSecret] != nil && updatedData[constants.EnvKeyGithubClientID].(string) != "" && updatedData[constants.EnvKeyGithubClientSecret].(string) != ""
isUpdatedLinkedInLoginEnabled := updatedData[constants.EnvKeyLinkedInClientID] != nil && updatedData[constants.EnvKeyLinkedInClientSecret] != nil && updatedData[constants.EnvKeyLinkedInClientID].(string) != "" && updatedData[constants.EnvKeyLinkedInClientSecret].(string) != ""
isUpdatedTwitterLoginEnabled := updatedData[constants.EnvKeyTwitterClientID] != nil && updatedData[constants.EnvKeyTwitterClientSecret] != nil && updatedData[constants.EnvKeyTwitterClientID].(string) != "" && updatedData[constants.EnvKeyTwitterClientSecret].(string) != ""
if isCurrentBasicAuthEnabled && !isUpdatedBasicAuthEnabled {
memorystore.Provider.DeleteSessionForNamespace(constants.AuthRecipeMethodBasicAuth)
@@ -67,6 +69,10 @@ func clearSessionIfRequired(currentData, updatedData map[string]interface{}) {
if isCurrentLinkedInLoginEnabled && !isUpdatedLinkedInLoginEnabled {
memorystore.Provider.DeleteSessionForNamespace(constants.AuthRecipeMethodLinkedIn)
}
if isCurrentTwitterLoginEnabled && !isUpdatedTwitterLoginEnabled {
memorystore.Provider.DeleteSessionForNamespace(constants.AuthRecipeMethodTwitter)
}
}
// UpdateEnvResolver is a resolver for update config mutation

View File

@@ -244,8 +244,12 @@ func UpdateProfileResolver(ctx context.Context, params model.UpdateProfileInput)
return res, err
}
// exec it as go routin so that we can reduce the api latency
go email.SendVerificationMail(newEmail, verificationToken, hostname)
// exec it as go routine so that we can reduce the api latency
go email.SendEmail([]string{user.Email}, verificationType, map[string]interface{}{
"user": user.ToMap(),
"organization": utils.GetOrganization(),
"verification_url": utils.GetEmailVerificationURL(verificationToken, hostname),
})
}
}

View File

@@ -156,8 +156,12 @@ func UpdateUserResolver(ctx context.Context, params model.UpdateUserInput) (*mod
return res, err
}
// exec it as go routin so that we can reduce the api latency
go email.SendVerificationMail(newEmail, verificationToken, hostname)
// exec it as go routine so that we can reduce the api latency
go email.SendEmail([]string{user.Email}, constants.VerificationTypeBasicAuthSignup, map[string]interface{}{
"user": user.ToMap(),
"organization": utils.GetOrganization(),
"verification_url": utils.GetEmailVerificationURL(verificationToken, hostname),
})
}

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