Compare commits
31 Commits
fix/dashbo
...
1.1.2
Author | SHA1 | Date | |
---|---|---|---|
![]() |
6455ff956a | ||
![]() |
3898e43fff | ||
![]() |
2c305e5bde | ||
![]() |
b8fd08e576 | ||
![]() |
6dafa45051 | ||
![]() |
ead3514113 | ||
![]() |
75a413e5f2 | ||
![]() |
91bf0e2478 | ||
![]() |
7a1305cf96 | ||
![]() |
ff5a6ec301 | ||
![]() |
b7b97b4f8d | ||
![]() |
d9bc989c74 | ||
![]() |
d1f80d4088 | ||
![]() |
4b299f0da2 | ||
![]() |
ed8006db4c | ||
![]() |
97f6c7d50a | ||
![]() |
5e3f68a180 | ||
![]() |
f73d1fc588 | ||
![]() |
aa232de426 | ||
![]() |
34ce754ef6 | ||
![]() |
5f385b2016 | ||
![]() |
da7c17271e | ||
![]() |
69fbd631ff | ||
![]() |
deb209e358 | ||
![]() |
ea6b4cbc8d | ||
![]() |
2f21a09b2e | ||
![]() |
4ab775f2c1 | ||
![]() |
b6e8023104 | ||
![]() |
4f1597e5d2 | ||
![]() |
4f81d1969e | ||
![]() |
ad3e615ac7 |
14
Dockerfile
14
Dockerfile
@@ -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" ]
|
||||
|
14
README.md
14
README.md
@@ -7,7 +7,7 @@
|
||||
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
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
|
||||
# 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,13 +29,15 @@
|
||||
- ✅ 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
|
||||
- React Native SDK
|
||||
@@ -64,8 +66,8 @@
|
||||
Deploy production ready Authorizer instance using one click deployment options available below
|
||||
|
||||
| **Infra provider** | **One-click link** | **Additional information** |
|
||||
| :----------------: | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :----------------------------------------------------: |
|
||||
| Railway.app | <a href="https://railway.app/new/template?template=https://github.com/authorizerdev/authorizer-railway&plugins=postgresql,redis"><img src="https://railway.app/button.svg" style="height: 44px" alt="Deploy on Railway"></a> | [docs](https://docs.authorizer.dev/deployment/railway) |
|
||||
| :----------------: | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :----------------------------------------------------: |
|
||||
| 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 | [](https://render.com/deploy?repo=https://github.com/authorizerdev/authorizer-render) | [docs](https://docs.authorizer.dev/deployment/render) |
|
||||
|
||||
|
50
app/package-lock.json
generated
50
app/package-lock.json
generated
@@ -9,7 +9,7 @@
|
||||
"version": "1.0.0",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"@authorizerdev/authorizer-react": "^0.26.0-beta.0",
|
||||
"@authorizerdev/authorizer-react": "^1.1.0",
|
||||
"@types/react": "^17.0.15",
|
||||
"@types/react-dom": "^17.0.9",
|
||||
"esbuild": "^0.12.17",
|
||||
@@ -26,22 +26,22 @@
|
||||
}
|
||||
},
|
||||
"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.0",
|
||||
"resolved": "https://registry.npmjs.org/@authorizerdev/authorizer-react/-/authorizer-react-1.1.0.tgz",
|
||||
"integrity": "sha512-8ooyBREFI6ohHApVOPQitFr7T0w0SlpEVZruvU9oqa8OQ77UBxLQh+PCRKKPw7FeQRdCdh/VQyl17W7Xphp1NA==",
|
||||
"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 +404,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",
|
||||
@@ -852,19 +860,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.0",
|
||||
"resolved": "https://registry.npmjs.org/@authorizerdev/authorizer-react/-/authorizer-react-1.1.0.tgz",
|
||||
"integrity": "sha512-8ooyBREFI6ohHApVOPQitFr7T0w0SlpEVZruvU9oqa8OQ77UBxLQh+PCRKKPw7FeQRdCdh/VQyl17W7Xphp1NA==",
|
||||
"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 +1169,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",
|
||||
|
@@ -11,7 +11,7 @@
|
||||
"author": "Lakhan Samani",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"@authorizerdev/authorizer-react": "^0.26.0-beta.0",
|
||||
"@authorizerdev/authorizer-react": "^1.1.0",
|
||||
"@types/react": "^17.0.15",
|
||||
"@types/react-dom": "^17.0.9",
|
||||
"esbuild": "^0.12.17",
|
||||
|
21
dashboard/package-lock.json
generated
21
dashboard/package-lock.json
generated
@@ -2605,7 +2605,8 @@
|
||||
"@chakra-ui/css-reset": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/@chakra-ui/css-reset/-/css-reset-1.1.1.tgz",
|
||||
"integrity": "sha512-+KNNHL4OWqeKia5SL858K3Qbd8WxMij9mWIilBzLD4j2KFrl/+aWFw8syMKth3NmgIibrjsljo+PU3fy2o50dg=="
|
||||
"integrity": "sha512-+KNNHL4OWqeKia5SL858K3Qbd8WxMij9mWIilBzLD4j2KFrl/+aWFw8syMKth3NmgIibrjsljo+PU3fy2o50dg==",
|
||||
"requires": {}
|
||||
},
|
||||
"@chakra-ui/descendant": {
|
||||
"version": "2.1.1",
|
||||
@@ -3209,7 +3210,8 @@
|
||||
"@graphql-typed-document-node/core": {
|
||||
"version": "3.1.1",
|
||||
"resolved": "https://registry.npmjs.org/@graphql-typed-document-node/core/-/core-3.1.1.tgz",
|
||||
"integrity": "sha512-NQ17ii0rK1b34VZonlmT2QMJFI70m0TRwbknO/ihlbatXyaktDhN/98vBiUU6kNBPljqGqyIrl2T4nY2RpFANg=="
|
||||
"integrity": "sha512-NQ17ii0rK1b34VZonlmT2QMJFI70m0TRwbknO/ihlbatXyaktDhN/98vBiUU6kNBPljqGqyIrl2T4nY2RpFANg==",
|
||||
"requires": {}
|
||||
},
|
||||
"@popperjs/core": {
|
||||
"version": "2.11.0",
|
||||
@@ -3481,7 +3483,8 @@
|
||||
"draftjs-utils": {
|
||||
"version": "0.10.2",
|
||||
"resolved": "https://registry.npmjs.org/draftjs-utils/-/draftjs-utils-0.10.2.tgz",
|
||||
"integrity": "sha512-EstHqr3R3JVcilJrBaO/A+01GvwwKmC7e4TCjC7S94ZeMh4IVmf60OuQXtHHpwItK8C2JCi3iljgN5KHkJboUg=="
|
||||
"integrity": "sha512-EstHqr3R3JVcilJrBaO/A+01GvwwKmC7e4TCjC7S94ZeMh4IVmf60OuQXtHHpwItK8C2JCi3iljgN5KHkJboUg==",
|
||||
"requires": {}
|
||||
},
|
||||
"error-ex": {
|
||||
"version": "1.3.2",
|
||||
@@ -3755,7 +3758,8 @@
|
||||
"html-to-draftjs": {
|
||||
"version": "1.5.0",
|
||||
"resolved": "https://registry.npmjs.org/html-to-draftjs/-/html-to-draftjs-1.5.0.tgz",
|
||||
"integrity": "sha512-kggLXBNciKDwKf+KYsuE+V5gw4dZ7nHyGMX9m0wy7urzWjKGWyNFetmArRLvRV0VrxKN70WylFsJvMTJx02OBQ=="
|
||||
"integrity": "sha512-kggLXBNciKDwKf+KYsuE+V5gw4dZ7nHyGMX9m0wy7urzWjKGWyNFetmArRLvRV0VrxKN70WylFsJvMTJx02OBQ==",
|
||||
"requires": {}
|
||||
},
|
||||
"import-fresh": {
|
||||
"version": "3.3.0",
|
||||
@@ -3945,7 +3949,8 @@
|
||||
"react-email-editor": {
|
||||
"version": "1.6.1",
|
||||
"resolved": "https://registry.npmjs.org/react-email-editor/-/react-email-editor-1.6.1.tgz",
|
||||
"integrity": "sha512-pEWpRmTY0ok03cwTGqEOoEldnzThhuRGTrcMnv8W3/jc5MTfcr9USU/IQ9HrVvFStLKoxYBIQnSKY+iCYWOtSQ=="
|
||||
"integrity": "sha512-pEWpRmTY0ok03cwTGqEOoEldnzThhuRGTrcMnv8W3/jc5MTfcr9USU/IQ9HrVvFStLKoxYBIQnSKY+iCYWOtSQ==",
|
||||
"requires": {}
|
||||
},
|
||||
"react-fast-compare": {
|
||||
"version": "3.2.0",
|
||||
@@ -3968,7 +3973,8 @@
|
||||
"react-icons": {
|
||||
"version": "4.3.1",
|
||||
"resolved": "https://registry.npmjs.org/react-icons/-/react-icons-4.3.1.tgz",
|
||||
"integrity": "sha512-cB10MXLTs3gVuXimblAdI71jrJx8njrJZmNMEMC+sQu5B/BIOmlsAjskdqpn81y8UBVEGuHODd7/ci5DvoSzTQ=="
|
||||
"integrity": "sha512-cB10MXLTs3gVuXimblAdI71jrJx8njrJZmNMEMC+sQu5B/BIOmlsAjskdqpn81y8UBVEGuHODd7/ci5DvoSzTQ==",
|
||||
"requires": {}
|
||||
},
|
||||
"react-is": {
|
||||
"version": "16.13.1",
|
||||
@@ -4159,7 +4165,8 @@
|
||||
"use-callback-ref": {
|
||||
"version": "1.2.5",
|
||||
"resolved": "https://registry.npmjs.org/use-callback-ref/-/use-callback-ref-1.2.5.tgz",
|
||||
"integrity": "sha512-gN3vgMISAgacF7sqsLPByqoePooY3n2emTH59Ur5d/M8eg4WTWu1xp8i8DHjohftIyEx0S08RiYxbffr4j8Peg=="
|
||||
"integrity": "sha512-gN3vgMISAgacF7sqsLPByqoePooY3n2emTH59Ur5d/M8eg4WTWu1xp8i8DHjohftIyEx0S08RiYxbffr4j8Peg==",
|
||||
"requires": {}
|
||||
},
|
||||
"use-sidecar": {
|
||||
"version": "1.0.5",
|
||||
|
@@ -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,9 +83,15 @@ const Features = ({ variables, setVariables }: any) => {
|
||||
/>
|
||||
</Flex>
|
||||
</Flex>
|
||||
<Flex>
|
||||
<Flex w="100%" justifyContent="start" alignItems="center">
|
||||
<Text fontSize="sm">Disable Multi Factor Authentication:</Text>
|
||||
<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
|
||||
@@ -96,13 +102,20 @@ const Features = ({ variables, setVariables }: any) => {
|
||||
</Flex>
|
||||
</Flex>
|
||||
</Stack>
|
||||
<Text fontSize="md" paddingTop="2%" fontWeight="bold" mb={5}>
|
||||
<Divider paddingY={5} />
|
||||
<Text fontSize="md" paddingTop={5} fontWeight="bold" mb={5}>
|
||||
Enable Features
|
||||
</Text>
|
||||
<Stack spacing={6} padding="2% 0%">
|
||||
<Flex>
|
||||
<Flex w="100%" justifyContent="start" alignItems="center">
|
||||
<Text fontSize="sm">Enforce Multi Factor Authentication:</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
|
||||
|
@@ -1,12 +1,21 @@
|
||||
import React from "react";
|
||||
import { Flex, Stack, Center, Text, useMediaQuery } from "@chakra-ui/react";
|
||||
import React from 'react';
|
||||
import {
|
||||
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";
|
||||
} from '../../constants';
|
||||
import GenerateKeysModal from '../GenerateKeysModal';
|
||||
import InputField from '../InputField';
|
||||
import { copyTextToClipboard } from '../../utils';
|
||||
|
||||
const JSTConfigurations = ({
|
||||
variables,
|
||||
@@ -19,11 +28,40 @@ const JSTConfigurations = ({
|
||||
RSAEncryptionType,
|
||||
ECDSAEncryptionType,
|
||||
}: any) => {
|
||||
const [isNotSmallerScreen] = useMediaQuery("(min-width:600px)");
|
||||
const [isNotSmallerScreen] = useMediaQuery('(min-width:600px)');
|
||||
const toast = useToast();
|
||||
|
||||
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%"
|
||||
@@ -32,24 +70,33 @@ const JSTConfigurations = ({
|
||||
paddingTop="2%"
|
||||
>
|
||||
<Text
|
||||
fontSize={isNotSmallerScreen ? "md" : "sm"}
|
||||
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 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"}
|
||||
w={isNotSmallerScreen ? '70%' : '100%'}
|
||||
mt={isNotSmallerScreen ? '0' : '2'}
|
||||
>
|
||||
<InputField
|
||||
borderRadius={5}
|
||||
@@ -66,13 +113,13 @@ const JSTConfigurations = ({
|
||||
</Flex>
|
||||
</Flex>
|
||||
{Object.values(HMACEncryptionType).includes(variables.JWT_TYPE) ? (
|
||||
<Flex direction={isNotSmallerScreen ? "row" : "column"}>
|
||||
<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"}
|
||||
w={isNotSmallerScreen ? '70%' : '100%'}
|
||||
mt={isNotSmallerScreen ? '0' : '2'}
|
||||
>
|
||||
<InputField
|
||||
borderRadius={5}
|
||||
@@ -86,13 +133,13 @@ const JSTConfigurations = ({
|
||||
</Flex>
|
||||
) : (
|
||||
<>
|
||||
<Flex direction={isNotSmallerScreen ? "row" : "column"}>
|
||||
<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"}
|
||||
w={isNotSmallerScreen ? '70%' : '100%'}
|
||||
mt={isNotSmallerScreen ? '0' : '2'}
|
||||
>
|
||||
<InputField
|
||||
borderRadius={5}
|
||||
@@ -104,13 +151,13 @@ const JSTConfigurations = ({
|
||||
/>
|
||||
</Center>
|
||||
</Flex>
|
||||
<Flex direction={isNotSmallerScreen ? "row" : "column"}>
|
||||
<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"}
|
||||
w={isNotSmallerScreen ? '70%' : '100%'}
|
||||
mt={isNotSmallerScreen ? '0' : '2'}
|
||||
>
|
||||
<InputField
|
||||
borderRadius={5}
|
||||
@@ -124,9 +171,9 @@ const JSTConfigurations = ({
|
||||
</Flex>
|
||||
</>
|
||||
)}
|
||||
<Flex direction={isNotSmallerScreen ? "row" : "column"}>
|
||||
<Flex direction={isNotSmallerScreen ? 'row' : 'column'}>
|
||||
<Flex
|
||||
w={isNotSmallerScreen ? "30%" : "40%"}
|
||||
w={isNotSmallerScreen ? '30%' : '40%'}
|
||||
justifyContent="start"
|
||||
alignItems="center"
|
||||
>
|
||||
@@ -135,8 +182,8 @@ const JSTConfigurations = ({
|
||||
</Text>
|
||||
</Flex>
|
||||
<Center
|
||||
w={isNotSmallerScreen ? "70%" : "100%"}
|
||||
mt={isNotSmallerScreen ? "0" : "2"}
|
||||
w={isNotSmallerScreen ? '70%' : '100%'}
|
||||
mt={isNotSmallerScreen ? '0' : '2'}
|
||||
>
|
||||
<InputField
|
||||
borderRadius={5}
|
||||
|
@@ -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>
|
||||
|
@@ -333,7 +333,7 @@ const UpdateEmailTemplate = ({
|
||||
{templateVariables.map((i) => (
|
||||
<Tr key={i.text}>
|
||||
<Td>
|
||||
<Code fontSize="sm">{`{{${i.text}}}`}</Code>
|
||||
<Code fontSize="sm">{`{{.${i.text}}}`}</Code>
|
||||
</Td>
|
||||
<Td>
|
||||
<Text
|
||||
|
@@ -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',
|
||||
@@ -110,6 +112,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] | [];
|
||||
|
@@ -18,50 +18,52 @@ 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,
|
||||
DISABLE_MULTI_FACTOR_AUTHENTICATION,
|
||||
ENFORCE_MULTI_FACTOR_AUTHENTICATION,
|
||||
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
|
||||
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
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
@@ -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: [],
|
||||
@@ -92,6 +94,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,
|
||||
|
@@ -29,6 +29,7 @@ import {
|
||||
MenuItem,
|
||||
useToast,
|
||||
Spinner,
|
||||
TableContainer
|
||||
} from '@chakra-ui/react';
|
||||
import {
|
||||
FaAngleLeft,
|
||||
@@ -262,8 +263,7 @@ export default function Users() {
|
||||
.toPromise();
|
||||
if (res.data?._update_user?.id) {
|
||||
toast({
|
||||
title: `Multi factor authentication ${
|
||||
user.is_multi_factor_auth_enabled ? 'disabled' : 'enabled'
|
||||
title: `Multi factor authentication ${user.is_multi_factor_auth_enabled ? 'disabled' : 'enabled'
|
||||
} for user`,
|
||||
isClosable: true,
|
||||
status: 'success',
|
||||
@@ -293,6 +293,7 @@ export default function Users() {
|
||||
</Flex>
|
||||
{!loading ? (
|
||||
userList.length > 0 ? (
|
||||
<TableContainer>
|
||||
<Table variant="simple">
|
||||
<Thead>
|
||||
<Tr>
|
||||
@@ -302,7 +303,11 @@ export default function Users() {
|
||||
<Th>Roles</Th>
|
||||
<Th>Verified</Th>
|
||||
<Th>Access</Th>
|
||||
<Th>MFA</Th>
|
||||
<Th>
|
||||
<Tooltip label="MultiFactor Authentication Enabled / Disabled">
|
||||
MFA
|
||||
</Tooltip>
|
||||
</Th>
|
||||
<Th>Actions</Th>
|
||||
</Tr>
|
||||
</Thead>
|
||||
@@ -404,13 +409,13 @@ export default function Users() {
|
||||
<MenuItem
|
||||
onClick={() => multiFactorAuthUpdateHandler(user)}
|
||||
>
|
||||
Disable MFA
|
||||
Disable MultiFactor Authentication
|
||||
</MenuItem>
|
||||
) : (
|
||||
<MenuItem
|
||||
onClick={() => multiFactorAuthUpdateHandler(user)}
|
||||
>
|
||||
Enable MFA
|
||||
Enable MultiFactor Authentication
|
||||
</MenuItem>
|
||||
)}
|
||||
</MenuList>
|
||||
@@ -543,6 +548,7 @@ export default function Users() {
|
||||
</TableCaption>
|
||||
)}
|
||||
</Table>
|
||||
</TableContainer>
|
||||
) : (
|
||||
<Flex
|
||||
flexDirection="column"
|
||||
|
@@ -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) => {
|
||||
|
@@ -15,4 +15,6 @@ const (
|
||||
AuthRecipeMethodLinkedIn = "linkedin"
|
||||
// AuthRecipeMethodApple is the apple auth method
|
||||
AuthRecipeMethodApple = "apple"
|
||||
// AuthRecipeMethodTwitter is the twitter auth method
|
||||
AuthRecipeMethodTwitter = "twitter"
|
||||
)
|
||||
|
@@ -85,6 +85,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
|
||||
|
@@ -14,4 +14,6 @@ const (
|
||||
// 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"
|
||||
)
|
||||
|
@@ -12,5 +12,5 @@ const (
|
||||
// VerificationTypeInviteMember is the invite_member verification type
|
||||
VerificationTypeInviteMember = "invite_member"
|
||||
// VerificationTypeOTP is the otp verification type
|
||||
VerificationTypeOTP = "otp"
|
||||
VerificationTypeOTP = "verify_otp"
|
||||
)
|
||||
|
@@ -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, subject, template, created_at, updated_at) VALUES ('%s', '%s', '%s','%s', %d, %d)", KeySpace+"."+models.Collections.EmailTemplate, emailTemplate.ID, emailTemplate.EventName, emailTemplate.Subject, 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, subject, 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.Subject, &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, subject, 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.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, subject, 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.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
|
||||
}
|
||||
|
@@ -224,10 +224,11 @@ func NewProvider() (*provider, error) {
|
||||
return nil, err
|
||||
}
|
||||
// add subject on email_templates table
|
||||
emailTemplateAlterQuery := fmt.Sprintf(`ALTER TABLE %s.%s ADD subject text;`, KeySpace, models.Collections.EmailTemplate)
|
||||
emailTemplateAlterQuery := fmt.Sprintf(`ALTER TABLE %s.%s ADD (subject text, design text);`, KeySpace, models.Collections.EmailTemplate)
|
||||
err = session.Query(emailTemplateAlterQuery).Exec()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
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)
|
||||
|
@@ -18,7 +18,7 @@ import (
|
||||
|
||||
func getDefaultTemplate(event string) *model.EmailTemplate {
|
||||
switch event {
|
||||
case constants.VerificationTypeBasicAuthSignup, constants.VerificationTypeMagicLinkLogin:
|
||||
case constants.VerificationTypeBasicAuthSignup, constants.VerificationTypeMagicLinkLogin, constants.VerificationTypeUpdateEmail:
|
||||
return &model.EmailTemplate{
|
||||
Subject: emailVerificationSubject,
|
||||
Template: emailVerificationTemplate,
|
||||
|
@@ -1 +0,0 @@
|
||||
package email
|
24
server/env/env.go
vendored
24
server/env/env.go
vendored
@@ -72,6 +72,8 @@ 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)
|
||||
@@ -355,31 +357,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, "/")
|
||||
}
|
||||
|
@@ -119,6 +119,8 @@ type ComplexityRoot struct {
|
||||
SMTPPort func(childComplexity int) int
|
||||
SMTPUsername func(childComplexity int) int
|
||||
SenderEmail func(childComplexity int) int
|
||||
TwitterClientID func(childComplexity int) int
|
||||
TwitterClientSecret func(childComplexity int) int
|
||||
}
|
||||
|
||||
Error struct {
|
||||
@@ -145,6 +147,7 @@ type ComplexityRoot struct {
|
||||
IsMultiFactorAuthEnabled func(childComplexity int) int
|
||||
IsSignUpEnabled func(childComplexity int) int
|
||||
IsStrongPasswordEnabled func(childComplexity int) int
|
||||
IsTwitterLoginEnabled func(childComplexity int) int
|
||||
Version func(childComplexity int) int
|
||||
}
|
||||
|
||||
@@ -813,6 +816,20 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in
|
||||
|
||||
return e.complexity.Env.SenderEmail(childComplexity), true
|
||||
|
||||
case "Env.TWITTER_CLIENT_ID":
|
||||
if e.complexity.Env.TwitterClientID == nil {
|
||||
break
|
||||
}
|
||||
|
||||
return e.complexity.Env.TwitterClientID(childComplexity), true
|
||||
|
||||
case "Env.TWITTER_CLIENT_SECRET":
|
||||
if e.complexity.Env.TwitterClientSecret == nil {
|
||||
break
|
||||
}
|
||||
|
||||
return e.complexity.Env.TwitterClientSecret(childComplexity), true
|
||||
|
||||
case "Error.message":
|
||||
if e.complexity.Error.Message == nil {
|
||||
break
|
||||
@@ -932,6 +949,13 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in
|
||||
|
||||
return e.complexity.Meta.IsStrongPasswordEnabled(childComplexity), true
|
||||
|
||||
case "Meta.is_twitter_login_enabled":
|
||||
if e.complexity.Meta.IsTwitterLoginEnabled == nil {
|
||||
break
|
||||
}
|
||||
|
||||
return e.complexity.Meta.IsTwitterLoginEnabled(childComplexity), true
|
||||
|
||||
case "Meta.version":
|
||||
if e.complexity.Meta.Version == nil {
|
||||
break
|
||||
@@ -1893,6 +1917,7 @@ type Meta {
|
||||
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!
|
||||
@@ -2014,6 +2039,8 @@ type Env {
|
||||
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
|
||||
}
|
||||
@@ -2118,6 +2145,8 @@ input UpdateEnvInput {
|
||||
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
|
||||
}
|
||||
@@ -5054,6 +5083,70 @@ func (ec *executionContext) _Env_APPLE_CLIENT_SECRET(ctx context.Context, field
|
||||
return ec.marshalOString2ᚖstring(ctx, field.Selections, res)
|
||||
}
|
||||
|
||||
func (ec *executionContext) _Env_TWITTER_CLIENT_ID(ctx context.Context, field graphql.CollectedField, obj *model.Env) (ret graphql.Marshaler) {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
ec.Error(ctx, ec.Recover(ctx, r))
|
||||
ret = graphql.Null
|
||||
}
|
||||
}()
|
||||
fc := &graphql.FieldContext{
|
||||
Object: "Env",
|
||||
Field: field,
|
||||
Args: nil,
|
||||
IsMethod: false,
|
||||
IsResolver: false,
|
||||
}
|
||||
|
||||
ctx = graphql.WithFieldContext(ctx, fc)
|
||||
resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) {
|
||||
ctx = rctx // use context from middleware stack in children
|
||||
return obj.TwitterClientID, nil
|
||||
})
|
||||
if err != nil {
|
||||
ec.Error(ctx, err)
|
||||
return graphql.Null
|
||||
}
|
||||
if resTmp == nil {
|
||||
return graphql.Null
|
||||
}
|
||||
res := resTmp.(*string)
|
||||
fc.Result = res
|
||||
return ec.marshalOString2ᚖstring(ctx, field.Selections, res)
|
||||
}
|
||||
|
||||
func (ec *executionContext) _Env_TWITTER_CLIENT_SECRET(ctx context.Context, field graphql.CollectedField, obj *model.Env) (ret graphql.Marshaler) {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
ec.Error(ctx, ec.Recover(ctx, r))
|
||||
ret = graphql.Null
|
||||
}
|
||||
}()
|
||||
fc := &graphql.FieldContext{
|
||||
Object: "Env",
|
||||
Field: field,
|
||||
Args: nil,
|
||||
IsMethod: false,
|
||||
IsResolver: false,
|
||||
}
|
||||
|
||||
ctx = graphql.WithFieldContext(ctx, fc)
|
||||
resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) {
|
||||
ctx = rctx // use context from middleware stack in children
|
||||
return obj.TwitterClientSecret, nil
|
||||
})
|
||||
if err != nil {
|
||||
ec.Error(ctx, err)
|
||||
return graphql.Null
|
||||
}
|
||||
if resTmp == nil {
|
||||
return graphql.Null
|
||||
}
|
||||
res := resTmp.(*string)
|
||||
fc.Result = res
|
||||
return ec.marshalOString2ᚖstring(ctx, field.Selections, res)
|
||||
}
|
||||
|
||||
func (ec *executionContext) _Env_ORGANIZATION_NAME(ctx context.Context, field graphql.CollectedField, obj *model.Env) (ret graphql.Marshaler) {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
@@ -5529,6 +5622,41 @@ func (ec *executionContext) _Meta_is_apple_login_enabled(ctx context.Context, fi
|
||||
return ec.marshalNBoolean2bool(ctx, field.Selections, res)
|
||||
}
|
||||
|
||||
func (ec *executionContext) _Meta_is_twitter_login_enabled(ctx context.Context, field graphql.CollectedField, obj *model.Meta) (ret graphql.Marshaler) {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
ec.Error(ctx, ec.Recover(ctx, r))
|
||||
ret = graphql.Null
|
||||
}
|
||||
}()
|
||||
fc := &graphql.FieldContext{
|
||||
Object: "Meta",
|
||||
Field: field,
|
||||
Args: nil,
|
||||
IsMethod: false,
|
||||
IsResolver: false,
|
||||
}
|
||||
|
||||
ctx = graphql.WithFieldContext(ctx, fc)
|
||||
resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) {
|
||||
ctx = rctx // use context from middleware stack in children
|
||||
return obj.IsTwitterLoginEnabled, nil
|
||||
})
|
||||
if err != nil {
|
||||
ec.Error(ctx, err)
|
||||
return graphql.Null
|
||||
}
|
||||
if resTmp == nil {
|
||||
if !graphql.HasFieldError(ctx, fc) {
|
||||
ec.Errorf(ctx, "must not be null")
|
||||
}
|
||||
return graphql.Null
|
||||
}
|
||||
res := resTmp.(bool)
|
||||
fc.Result = res
|
||||
return ec.marshalNBoolean2bool(ctx, field.Selections, res)
|
||||
}
|
||||
|
||||
func (ec *executionContext) _Meta_is_email_verification_enabled(ctx context.Context, field graphql.CollectedField, obj *model.Meta) (ret graphql.Marshaler) {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
@@ -11720,6 +11848,22 @@ func (ec *executionContext) unmarshalInputUpdateEnvInput(ctx context.Context, ob
|
||||
if err != nil {
|
||||
return it, err
|
||||
}
|
||||
case "TWITTER_CLIENT_ID":
|
||||
var err error
|
||||
|
||||
ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("TWITTER_CLIENT_ID"))
|
||||
it.TwitterClientID, err = ec.unmarshalOString2ᚖstring(ctx, v)
|
||||
if err != nil {
|
||||
return it, err
|
||||
}
|
||||
case "TWITTER_CLIENT_SECRET":
|
||||
var err error
|
||||
|
||||
ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("TWITTER_CLIENT_SECRET"))
|
||||
it.TwitterClientSecret, err = ec.unmarshalOString2ᚖstring(ctx, v)
|
||||
if err != nil {
|
||||
return it, err
|
||||
}
|
||||
case "ORGANIZATION_NAME":
|
||||
var err error
|
||||
|
||||
@@ -12421,6 +12565,10 @@ func (ec *executionContext) _Env(ctx context.Context, sel ast.SelectionSet, obj
|
||||
out.Values[i] = ec._Env_APPLE_CLIENT_ID(ctx, field, obj)
|
||||
case "APPLE_CLIENT_SECRET":
|
||||
out.Values[i] = ec._Env_APPLE_CLIENT_SECRET(ctx, field, obj)
|
||||
case "TWITTER_CLIENT_ID":
|
||||
out.Values[i] = ec._Env_TWITTER_CLIENT_ID(ctx, field, obj)
|
||||
case "TWITTER_CLIENT_SECRET":
|
||||
out.Values[i] = ec._Env_TWITTER_CLIENT_SECRET(ctx, field, obj)
|
||||
case "ORGANIZATION_NAME":
|
||||
out.Values[i] = ec._Env_ORGANIZATION_NAME(ctx, field, obj)
|
||||
case "ORGANIZATION_LOGO":
|
||||
@@ -12542,6 +12690,11 @@ func (ec *executionContext) _Meta(ctx context.Context, sel ast.SelectionSet, obj
|
||||
if out.Values[i] == graphql.Null {
|
||||
invalids++
|
||||
}
|
||||
case "is_twitter_login_enabled":
|
||||
out.Values[i] = ec._Meta_is_twitter_login_enabled(ctx, field, obj)
|
||||
if out.Values[i] == graphql.Null {
|
||||
invalids++
|
||||
}
|
||||
case "is_email_verification_enabled":
|
||||
out.Values[i] = ec._Meta_is_email_verification_enabled(ctx, field, obj)
|
||||
if out.Values[i] == graphql.Null {
|
||||
|
@@ -106,6 +106,8 @@ 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"`
|
||||
}
|
||||
@@ -164,6 +166,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"`
|
||||
@@ -297,6 +300,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"`
|
||||
}
|
||||
|
@@ -20,6 +20,7 @@ type Meta {
|
||||
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!
|
||||
@@ -141,6 +142,8 @@ type Env {
|
||||
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
|
||||
}
|
||||
@@ -245,6 +248,8 @@ input UpdateEnvInput {
|
||||
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
|
||||
}
|
||||
|
@@ -248,7 +248,7 @@ func AuthorizeHandler() gin.HandlerFunc {
|
||||
return
|
||||
}
|
||||
|
||||
memorystore.Provider.SetUserSession(user.ID, constants.TokenTypeSessionToken+"_"+newSessionTokenData.Nonce, newSessionToken)
|
||||
memorystore.Provider.SetUserSession(sessionKey, constants.TokenTypeSessionToken+"_"+newSessionTokenData.Nonce, newSessionToken)
|
||||
cookie.SetSession(gc, newSessionToken)
|
||||
code := uuid.New().String()
|
||||
memorystore.Provider.SetState(codeChallenge, code+"@"+newSessionToken)
|
||||
|
@@ -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`)
|
||||
@@ -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
|
||||
}
|
||||
|
@@ -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")
|
||||
|
@@ -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
|
||||
|
@@ -109,6 +109,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"
|
||||
|
@@ -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 {
|
||||
|
@@ -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)
|
||||
|
@@ -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()
|
||||
|
@@ -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
|
||||
@@ -133,5 +134,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
|
||||
}
|
||||
|
@@ -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))
|
||||
}
|
||||
|
@@ -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"
|
||||
@@ -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)
|
||||
|
@@ -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)
|
||||
|
@@ -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,
|
||||
|
@@ -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
|
||||
|
@@ -245,7 +245,7 @@ func UpdateProfileResolver(ctx context.Context, params model.UpdateProfileInput)
|
||||
}
|
||||
|
||||
// exec it as go routine so that we can reduce the api latency
|
||||
go email.SendEmail([]string{user.Email}, constants.VerificationTypeBasicAuthSignup, map[string]interface{}{
|
||||
go email.SendEmail([]string{user.Email}, verificationType, map[string]interface{}{
|
||||
"user": user.ToMap(),
|
||||
"organization": utils.GetOrganization(),
|
||||
"verification_url": utils.GetEmailVerificationURL(verificationToken, hostname),
|
||||
|
@@ -31,7 +31,7 @@ func addEmailTemplateTest(t *testing.T, s TestSetup) {
|
||||
assert.Nil(t, emailTemplate)
|
||||
})
|
||||
|
||||
t.Run("should not add email template for empty template", func(t *testing.T) {
|
||||
t.Run("should not add email template for empty subject", func(t *testing.T) {
|
||||
emailTemplate, err := resolvers.AddEmailTemplateResolver(ctx, model.AddEmailTemplateRequest{
|
||||
EventName: s.TestInfo.TestEmailTemplateEventTypes[0],
|
||||
Template: " test ",
|
||||
@@ -50,12 +50,25 @@ func addEmailTemplateTest(t *testing.T, s TestSetup) {
|
||||
assert.Error(t, err)
|
||||
assert.Nil(t, emailTemplate)
|
||||
})
|
||||
|
||||
t.Run("should not add email template with empty design", func(t *testing.T) {
|
||||
emailTemplate, err := resolvers.AddEmailTemplateResolver(ctx, model.AddEmailTemplateRequest{
|
||||
EventName: s.TestInfo.TestEmailTemplateEventTypes[0],
|
||||
Template: "test",
|
||||
Subject: "test",
|
||||
Design: " ",
|
||||
})
|
||||
assert.Error(t, err)
|
||||
assert.Nil(t, emailTemplate)
|
||||
})
|
||||
|
||||
for _, eventType := range s.TestInfo.TestEmailTemplateEventTypes {
|
||||
t.Run("should add email template for "+eventType, func(t *testing.T) {
|
||||
emailTemplate, err := resolvers.AddEmailTemplateResolver(ctx, model.AddEmailTemplateRequest{
|
||||
EventName: eventType,
|
||||
Template: "Test email",
|
||||
Subject: "Test email",
|
||||
Design: "Test design",
|
||||
})
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, emailTemplate)
|
||||
@@ -65,6 +78,7 @@ func addEmailTemplateTest(t *testing.T, s TestSetup) {
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, et.EventName, eventType)
|
||||
assert.Equal(t, "Test email", et.Subject)
|
||||
assert.Equal(t, "Test design", et.Design)
|
||||
})
|
||||
}
|
||||
})
|
||||
|
@@ -32,6 +32,7 @@ func updateEmailTemplateTest(t *testing.T, s TestSetup) {
|
||||
ID: emailTemplate.ID,
|
||||
Template: refs.NewStringRef("Updated test template"),
|
||||
Subject: refs.NewStringRef("Updated subject"),
|
||||
Design: refs.NewStringRef("Updated design"),
|
||||
})
|
||||
|
||||
assert.NoError(t, err)
|
||||
@@ -44,5 +45,6 @@ func updateEmailTemplateTest(t *testing.T, s TestSetup) {
|
||||
assert.Equal(t, emailTemplate.ID, updatedEmailTemplate.ID)
|
||||
assert.Equal(t, updatedEmailTemplate.Template, "Updated test template")
|
||||
assert.Equal(t, updatedEmailTemplate.Subject, "Updated subject")
|
||||
assert.Equal(t, updatedEmailTemplate.Design, "Updated design")
|
||||
})
|
||||
}
|
||||
|
@@ -298,7 +298,6 @@ func ValidateBrowserSession(gc *gin.Context, encryptedSession string) (*SessionD
|
||||
if res.LoginMethod != "" {
|
||||
sessionStoreKey = res.LoginMethod + ":" + res.Subject
|
||||
}
|
||||
|
||||
token, err := memorystore.Provider.GetUserSession(sessionStoreKey, constants.TokenTypeSessionToken+"_"+res.Nonce)
|
||||
if token == "" || err != nil {
|
||||
log.Debug("invalid browser session:", err)
|
||||
|
32
server/utils/pkce.go
Normal file
32
server/utils/pkce.go
Normal file
@@ -0,0 +1,32 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"crypto/sha256"
|
||||
b64 "encoding/base64"
|
||||
"math/rand"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
length = 32
|
||||
)
|
||||
|
||||
// GenerateCodeChallenge creates PKCE-Code-Challenge
|
||||
// and returns the verifier and challenge
|
||||
func GenerateCodeChallenge() (string, string) {
|
||||
// Generate Verifier
|
||||
randGenerator := rand.New(rand.NewSource(time.Now().UnixNano()))
|
||||
randomBytes := make([]byte, length)
|
||||
for i := 0; i < length; i++ {
|
||||
randomBytes[i] = byte(randGenerator.Intn(255))
|
||||
}
|
||||
verifier := strings.Trim(b64.URLEncoding.EncodeToString(randomBytes), "=")
|
||||
|
||||
// Generate Challenge
|
||||
rawChallenge := sha256.New()
|
||||
rawChallenge.Write([]byte(verifier))
|
||||
challenge := strings.Trim(b64.URLEncoding.EncodeToString(rawChallenge.Sum(nil)), "=")
|
||||
|
||||
return verifier, challenge
|
||||
}
|
@@ -30,8 +30,8 @@ func IsValidOrigin(url string) bool {
|
||||
replacedString := origin
|
||||
// if has regex whitelisted domains
|
||||
if strings.Contains(origin, "*") {
|
||||
replacedString = strings.Replace(origin, ".", "\\.", -1)
|
||||
replacedString = strings.Replace(replacedString, "*", ".*", -1)
|
||||
replacedString = strings.ReplaceAll(origin, ".", "\\.")
|
||||
replacedString = strings.ReplaceAll(replacedString, "*", ".*")
|
||||
|
||||
if strings.HasPrefix(replacedString, ".*") {
|
||||
replacedString += "\\b"
|
||||
|
Reference in New Issue
Block a user