Compare commits

..

68 Commits

Author SHA1 Message Date
Lakhan Samani
f2886e6da8 fix: disable other db test for quick test 2022-07-12 11:57:46 +05:30
Lakhan Samani
6b57bce6d9 fix: cassandra + mongo + arangodb issues with webhook 2022-07-12 11:48:42 +05:30
Lakhan Samani
bfbeb6add2 fix: couple session deletion with user deletion 2022-07-12 08:42:32 +05:30
Lakhan Samani
1fe0d65874 feat: add support for planetscale
Resolves #195
2022-07-11 22:37:07 +05:30
Lakhan Samani
bfaa0f9d89 fix: make list webhooks params optional 2022-07-11 22:05:44 +05:30
Lakhan Samani
4f5a6c77f8 Merge pull request #194 from authorizerdev/feat/webhook
feat: add webhook apis + integrate in events
2022-07-11 19:56:48 +05:30
Lakhan Samani
018a13ab3c feat: add tests for webhook resolvers 2022-07-11 19:40:54 +05:30
Lakhan Samani
334041d0e4 fix: delete user event flow 2022-07-11 11:13:32 +05:30
Lakhan Samani
6a8309a231 feat: register event for revoke/enable access + delete user 2022-07-11 11:12:30 +05:30
Lakhan Samani
6347b60753 fix: rename revoke refresh token handler for better reading 2022-07-11 11:10:30 +05:30
Lakhan Samani
bbb064b939 feat: add register event 2022-07-11 10:42:42 +05:30
Lakhan Samani
e91a819067 feat: implement resolvers 2022-07-10 21:49:33 +05:30
Lakhan Samani
09c3eafe6b feat: add mongodb database methods for webhook 2022-07-09 12:23:48 +05:30
Lakhan Samani
bb51775d34 feat: add cassandradb database methods for webhook 2022-07-09 12:16:54 +05:30
Lakhan Samani
6d586b16e4 feat: add arangodb database methods for webhook 2022-07-09 11:44:14 +05:30
Lakhan Samani
e8eb62769e feat: add sql database methods for webhook 2022-07-09 11:21:32 +05:30
Lakhan Samani
0ffb3f67f1 fix: index for arangodb 2022-07-08 19:10:37 +05:30
Lakhan Samani
ec62686fbc feat: add database methods for webhookLog 2022-07-08 19:09:23 +05:30
Lakhan Samani
a8064e79a1 feat: add template for webhook db methods 2022-07-06 10:38:21 +05:30
Lakhan Samani
265331801f feat: add database models 2022-07-04 22:37:13 +05:30
Lakhan Samani
6a74a50493 feat: add support for scylladb
Resolves #177
2022-07-04 21:57:14 +05:30
Lakhan Samani
8c27f20534 Merge pull request #193 from authorizerdev/fix/invalidate-session-token
fix: add provider to token creation
2022-07-01 22:14:50 +05:30
Lakhan Samani
29c6003ea3 fix: remove test log 2022-07-01 22:04:58 +05:30
Lakhan Samani
ae34fc7c2b fix: update_env resolver 2022-07-01 22:02:34 +05:30
Lakhan Samani
2a5d5d43b0 fix: add namespace to session token keys 2022-06-29 22:24:00 +05:30
Lakhan Samani
e6a4670ba9 fix: add provider to token creation 2022-06-29 09:54:12 +05:30
Lakhan Samani
64d64b4099 feat: add ability to disable strong password 2022-06-18 15:31:57 +05:30
Lakhan Samani
88f9a10f21 Merge pull request #192 from authorizerdev/feat/apple-login
feat: add apple login
2022-06-16 09:48:02 +05:30
Lakhan Samani
4e08d4f8fd fix: update app 2022-06-16 09:47:16 +05:30
Lakhan Samani
1c4dda9299 fix: remove debug logs 2022-06-16 07:34:10 +05:30
Lakhan Samani
ab18fa5832 fix: use raw base64 url decoding 2022-06-15 21:55:41 +05:30
Lakhan Samani
484d0c0882 chore: update app 2022-06-14 16:39:06 +05:30
Lakhan Samani
be59c3615f fix: add comment for scope 2022-06-14 15:47:08 +05:30
Lakhan Samani
db351f7771 fix: remove debug logs 2022-06-14 15:45:06 +05:30
Lakhan Samani
91c29c4092 fix: redirect 2022-06-14 15:43:23 +05:30
Lakhan Samani
415b97535e fix: update scope param 2022-06-14 15:05:56 +05:30
Lakhan Samani
7d1272d815 fix: update scope for apple login 2022-06-14 14:41:31 +05:30
Lakhan Samani
c9ba0b13f8 fix: update scope for apple login 2022-06-14 13:37:05 +05:30
Lakhan Samani
fadd9f6168 fix: update scope for apple login 2022-06-14 13:11:39 +05:30
Lakhan Samani
395e2e2a85 fix: update scope for apple login 2022-06-14 12:35:23 +05:30
Lakhan Samani
6335084835 fix: add post method support for oauth callback 2022-06-14 12:17:43 +05:30
Lakhan Samani
eab336cd3d fix: apple login params 2022-06-14 12:06:46 +05:30
Lakhan Samani
f4691fca1f fix: id token parsing 2022-06-14 11:38:04 +05:30
Lakhan Samani
341d4fbae5 fix: scope for apple login 2022-06-14 11:21:26 +05:30
Lakhan Samani
e467b45ab1 fix: apple client secret field 2022-06-14 11:11:09 +05:30
Lakhan Samani
7edfad3486 fix: apple client secret field 2022-06-14 10:56:47 +05:30
Lakhan Samani
80578b88ac feat: update app 2022-06-13 07:37:26 +05:30
Lakhan Samani
5646e7a0e7 feat: add test code to process apple user 2022-06-12 18:30:33 +05:30
Lakhan Samani
53a592ef63 feat: add base for apple login 2022-06-12 14:49:48 +05:30
Lakhan Samani
3337dbd0a4 Merge pull request #191 from authorizerdev/fix/session-invalidation
fix: session invalidation
2022-06-12 09:08:57 +05:30
Lakhan Samani
82a2a42f84 fix: user session access 2022-06-12 00:27:21 +05:30
Lakhan Samani
ac49b5bb70 fix: session tests 2022-06-11 19:24:53 +05:30
Lakhan Samani
926ab07c07 fix: session invalidation 2022-06-11 19:10:39 +05:30
Lakhan Samani
7a2dbea019 Merge branch 'main' of https://github.com/authorizerdev/authorizer 2022-06-09 23:43:28 +05:30
Lakhan Samani
dff50097e8 feat: add support for cockroachdb 2022-06-09 23:43:21 +05:30
Lakhan Samani
aff9d3af20 Merge pull request #187 from authorizerdev/fix-parallel-access
fix: parallel access of env vars
2022-06-09 23:13:34 +05:30
Lakhan Samani
02eb1d6677 fix: add const for test env 2022-06-09 23:13:22 +05:30
Lakhan Samani
78a673e4ad fix: fix parallel access of env vars 2022-06-08 09:50:30 +05:30
Lakhan Samani
e0d8644264 fix: role validation while signup 2022-06-07 08:00:30 +05:30
Lakhan Samani
d8c662eaad fix: dashboard roles 2022-06-07 07:30:01 +05:30
Lakhan Samani
6d1d259f71 Merge pull request #182 from authorizerdev/feat/add-linkedin-login
feat: add linkedin login
2022-06-06 22:09:08 +05:30
Lakhan Samani
2841853d37 feat: add linkedin login 2022-06-06 22:08:32 +05:30
Lakhan Samani
360dd3c3bd fix: redirect uri 2022-06-05 22:46:56 +05:30
Lakhan Samani
c6add0cca6 fix: give higher priority to authorizer url 2022-06-05 22:13:10 +05:30
Lakhan Samani
7ac6252aac fix: app login page signup url
add debug logs
2022-06-05 21:44:16 +05:30
Lakhan Samani
5d2d1c342b fix: allow setting host for cassandradb without prot 2022-06-05 12:13:55 +05:30
Lakhan Samani
6da0a85936 fix: remove unused code 2022-06-04 09:26:02 +05:30
Lakhan Samani
116972d725 feat: add support for ScyllaDB
Resolves #177
2022-06-04 08:59:26 +05:30
146 changed files with 6508 additions and 936 deletions

42
app/package-lock.json generated
View File

@@ -9,7 +9,7 @@
"version": "1.0.0",
"license": "ISC",
"dependencies": {
"@authorizerdev/authorizer-react": "^0.17.0",
"@authorizerdev/authorizer-react": "^0.25.0",
"@types/react": "^17.0.15",
"@types/react-dom": "^17.0.9",
"esbuild": "^0.12.17",
@@ -26,9 +26,9 @@
}
},
"node_modules/@authorizerdev/authorizer-js": {
"version": "0.10.0",
"resolved": "https://registry.npmjs.org/@authorizerdev/authorizer-js/-/authorizer-js-0.10.0.tgz",
"integrity": "sha512-REM8FLD/Ej9gzA2zDGDAke6QFss33ubePlTDmLDmIYUuQmpHFlO5mCCS6nVsKkN7F/Bcwkmp+eUNQjkdGCaKLg==",
"version": "0.14.0",
"resolved": "https://registry.npmjs.org/@authorizerdev/authorizer-js/-/authorizer-js-0.14.0.tgz",
"integrity": "sha512-cpeeFrmG623QPLn+nf+ACHayZYqW8xokIidGikeboBDJtuAAQB50a54/7HwLHriG2FB7WvPuHQ/9LFFX//N1lg==",
"dependencies": {
"node-fetch": "^2.6.1"
},
@@ -37,11 +37,11 @@
}
},
"node_modules/@authorizerdev/authorizer-react": {
"version": "0.17.0",
"resolved": "https://registry.npmjs.org/@authorizerdev/authorizer-react/-/authorizer-react-0.17.0.tgz",
"integrity": "sha512-7WcNCU7hDFkVfFb8LcJXFwWiLYd8aY78z1AbNPxCa2Cw5G85PaRkzjKybP6h01ITVOHO6M03lLwPj8p6Sr6fEg==",
"version": "0.25.0",
"resolved": "https://registry.npmjs.org/@authorizerdev/authorizer-react/-/authorizer-react-0.25.0.tgz",
"integrity": "sha512-Dt2rZf+cGCVb8dqcJ/9l8Trx+QeXnTdfhER6r/cq0iOnFC9MqWzQPB3RgrlUoMLHtZvKNDXIk1HvfD5hSX9lhw==",
"dependencies": {
"@authorizerdev/authorizer-js": "^0.10.0",
"@authorizerdev/authorizer-js": "^0.14.0",
"final-form": "^4.20.2",
"react-final-form": "^6.5.3",
"styled-components": "^5.3.0"
@@ -816,7 +816,7 @@
"node_modules/tr46": {
"version": "0.0.3",
"resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz",
"integrity": "sha1-gYT9NH2snNwYWZLzpmIuFLnZq2o="
"integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw=="
},
"node_modules/typescript": {
"version": "4.3.5",
@@ -838,12 +838,12 @@
"node_modules/webidl-conversions": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz",
"integrity": "sha1-JFNCdeKnvGvnvIZhHMFq4KVlSHE="
"integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ=="
},
"node_modules/whatwg-url": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz",
"integrity": "sha1-lmRU6HZUYuN2RNNib2dCzotwll0=",
"integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==",
"dependencies": {
"tr46": "~0.0.3",
"webidl-conversions": "^3.0.0"
@@ -852,19 +852,19 @@
},
"dependencies": {
"@authorizerdev/authorizer-js": {
"version": "0.10.0",
"resolved": "https://registry.npmjs.org/@authorizerdev/authorizer-js/-/authorizer-js-0.10.0.tgz",
"integrity": "sha512-REM8FLD/Ej9gzA2zDGDAke6QFss33ubePlTDmLDmIYUuQmpHFlO5mCCS6nVsKkN7F/Bcwkmp+eUNQjkdGCaKLg==",
"version": "0.14.0",
"resolved": "https://registry.npmjs.org/@authorizerdev/authorizer-js/-/authorizer-js-0.14.0.tgz",
"integrity": "sha512-cpeeFrmG623QPLn+nf+ACHayZYqW8xokIidGikeboBDJtuAAQB50a54/7HwLHriG2FB7WvPuHQ/9LFFX//N1lg==",
"requires": {
"node-fetch": "^2.6.1"
}
},
"@authorizerdev/authorizer-react": {
"version": "0.17.0",
"resolved": "https://registry.npmjs.org/@authorizerdev/authorizer-react/-/authorizer-react-0.17.0.tgz",
"integrity": "sha512-7WcNCU7hDFkVfFb8LcJXFwWiLYd8aY78z1AbNPxCa2Cw5G85PaRkzjKybP6h01ITVOHO6M03lLwPj8p6Sr6fEg==",
"version": "0.25.0",
"resolved": "https://registry.npmjs.org/@authorizerdev/authorizer-react/-/authorizer-react-0.25.0.tgz",
"integrity": "sha512-Dt2rZf+cGCVb8dqcJ/9l8Trx+QeXnTdfhER6r/cq0iOnFC9MqWzQPB3RgrlUoMLHtZvKNDXIk1HvfD5hSX9lhw==",
"requires": {
"@authorizerdev/authorizer-js": "^0.10.0",
"@authorizerdev/authorizer-js": "^0.14.0",
"final-form": "^4.20.2",
"react-final-form": "^6.5.3",
"styled-components": "^5.3.0"
@@ -1482,7 +1482,7 @@
"tr46": {
"version": "0.0.3",
"resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz",
"integrity": "sha1-gYT9NH2snNwYWZLzpmIuFLnZq2o="
"integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw=="
},
"typescript": {
"version": "4.3.5",
@@ -1497,12 +1497,12 @@
"webidl-conversions": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz",
"integrity": "sha1-JFNCdeKnvGvnvIZhHMFq4KVlSHE="
"integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ=="
},
"whatwg-url": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz",
"integrity": "sha1-lmRU6HZUYuN2RNNib2dCzotwll0=",
"integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==",
"requires": {
"tr46": "~0.0.3",
"webidl-conversions": "^3.0.0"

View File

@@ -11,7 +11,7 @@
"author": "Lakhan Samani",
"license": "ISC",
"dependencies": {
"@authorizerdev/authorizer-react": "^0.17.0",
"@authorizerdev/authorizer-react": "^0.25.0",
"@types/react": "^17.0.15",
"@types/react-dom": "^17.0.9",
"esbuild": "^0.12.17",

View File

@@ -72,11 +72,13 @@ export default function Login({ urlProps }: { urlProps: Record<string, any> }) {
</Footer>
</Fragment>
)}
{config.is_sign_up_enabled && (
<FooterContent>
Don't have an account? <Link to="/app/signup"> Sign Up</Link>
</FooterContent>
)}
{config.is_basic_authentication_enabled &&
!config.is_magic_link_login_enabled &&
config.is_sign_up_enabled && (
<FooterContent>
Don't have an account? <Link to="/app/signup"> Sign Up</Link>
</FooterContent>
)}
</Fragment>
);
}

View File

@@ -2529,7 +2529,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",
@@ -3133,7 +3134,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",
@@ -3843,7 +3845,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",
@@ -4029,7 +4032,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",

View File

@@ -71,6 +71,18 @@ const Features = ({ variables, setVariables }: any) => {
/>
</Flex>
</Flex>
<Flex>
<Flex w="100%" justifyContent="start" alignItems="center">
<Text fontSize="sm">Disable Strong Password:</Text>
</Flex>
<Flex justifyContent="start" mb={3}>
<InputField
variables={variables}
setVariables={setVariables}
inputType={SwitchInputType.DISABLE_STRONG_PASSWORD}
/>
</Flex>
</Flex>
</Stack>
</div>
);

View File

@@ -9,7 +9,13 @@ import {
Divider,
useMediaQuery,
} from '@chakra-ui/react';
import { FaGoogle, FaGithub, FaFacebookF } from 'react-icons/fa';
import {
FaGoogle,
FaGithub,
FaFacebookF,
FaLinkedin,
FaApple,
} from 'react-icons/fa';
import { TextInputType, HiddenInputType } from '../../constants';
const OAuthConfig = ({
@@ -182,6 +188,82 @@ 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"
>
<FaLinkedin style={{ color: '#3b5998' }} />
</Center>
<Center
w={isNotSmallerScreen ? '70%' : '100%'}
mt={isNotSmallerScreen ? '0' : '3'}
marginRight="1.5%"
>
<InputField
borderRadius={5}
variables={envVariables}
setVariables={setVariables}
inputType={TextInputType.LINKEDIN_CLIENT_ID}
placeholder="LinkedIn 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.LINKEDIN_CLIENT_SECRET}
placeholder="LinkedIn Client Secret"
/>
</Center>
</Flex>
<Flex direction={isNotSmallerScreen ? 'row' : 'column'}>
<Center
w={isNotSmallerScreen ? '55px' : '35px'}
h="35px"
marginRight="1.5%"
border="1px solid #3b5998"
borderRadius="5px"
>
<FaApple style={{ color: '#3b5998' }} />
</Center>
<Center
w={isNotSmallerScreen ? '70%' : '100%'}
mt={isNotSmallerScreen ? '0' : '3'}
marginRight="1.5%"
>
<InputField
borderRadius={5}
variables={envVariables}
setVariables={setVariables}
inputType={TextInputType.APPLE_CLIENT_ID}
placeholder="Apple 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.APPLE_CLIENT_SECRET}
placeholder="Apple CLient Secret"
/>
</Center>
</Flex>
</Stack>
</Box>
</div>

View File

@@ -1,67 +1,68 @@
import React from "react";
import { Flex, Stack, Center, Text, useMediaQuery } from "@chakra-ui/react";
import { ArrayInputType } from "../../constants";
import InputField from "../InputField";
import React from 'react';
import { Flex, Stack, Center, Text, useMediaQuery } from '@chakra-ui/react';
import { ArrayInputType } from '../../constants';
import InputField from '../InputField';
const Roles = ({ variables, setVariables }: any) => {
const [isNotSmallerScreen] = useMediaQuery("(min-width:600px)");
return (
<div>
{" "}
<Text fontSize="md" paddingTop="2%" fontWeight="bold" mb={5}>
Roles
</Text>
<Stack spacing={6} padding="2% 0%">
<Flex direction={isNotSmallerScreen ? "row" : "column"}>
<Flex w="30%" justifyContent="start" alignItems="center">
<Text fontSize="sm">Roles:</Text>
</Flex>
<Center
w={isNotSmallerScreen ? "70%" : "100%"}
mt={isNotSmallerScreen ? "0" : "2"}
overflow="hidden"
>
<InputField
borderRadius={7}
variables={variables}
setVariables={setVariables}
inputType={ArrayInputType.ROLES}
/>
</Center>
</Flex>
<Flex direction={isNotSmallerScreen ? "row" : "column"}>
<Flex w="30%" justifyContent="start" alignItems="center">
<Text fontSize="sm">Default Roles:</Text>
</Flex>
<Center
w={isNotSmallerScreen ? "70%" : "100%"}
mt={isNotSmallerScreen ? "0" : "2"}
>
<InputField
variables={variables}
setVariables={setVariables}
inputType={ArrayInputType.DEFAULT_ROLES}
/>
</Center>
</Flex>
<Flex direction={isNotSmallerScreen ? "row" : "column"}>
<Flex w="30%" justifyContent="start" alignItems="center">
<Text fontSize="sm">Protected Roles:</Text>
</Flex>
<Center
w={isNotSmallerScreen ? "70%" : "100%"}
mt={isNotSmallerScreen ? "0" : "2"}
>
<InputField
variables={variables}
setVariables={setVariables}
inputType={ArrayInputType.PROTECTED_ROLES}
/>
</Center>
</Flex>
</Stack>
</div>
);
const [isNotSmallerScreen] = useMediaQuery('(min-width:600px)');
return (
<div>
{' '}
<Text fontSize="md" paddingTop="2%" fontWeight="bold" mb={5}>
Roles
</Text>
<Stack spacing={6} padding="2% 0%">
<Flex direction={isNotSmallerScreen ? 'row' : 'column'}>
<Flex w="30%" justifyContent="start" alignItems="center">
<Text fontSize="sm">Roles:</Text>
</Flex>
<Center
w={isNotSmallerScreen ? '70%' : '100%'}
mt={isNotSmallerScreen ? '0' : '2'}
overflow="hidden"
>
<InputField
borderRadius={7}
variables={variables}
setVariables={setVariables}
inputType={ArrayInputType.ROLES}
/>
</Center>
</Flex>
<Flex direction={isNotSmallerScreen ? 'row' : 'column'}>
<Flex w="30%" justifyContent="start" alignItems="center">
<Text fontSize="sm">Default Roles:</Text>
</Flex>
<Center
w={isNotSmallerScreen ? '70%' : '100%'}
mt={isNotSmallerScreen ? '0' : '2'}
>
<InputField
variables={variables}
setVariables={setVariables}
inputType={ArrayInputType.DEFAULT_ROLES}
/>
</Center>
</Flex>
<Flex direction={isNotSmallerScreen ? 'row' : 'column'}>
<Flex w="30%" justifyContent="start" alignItems="center">
<Text fontSize="sm">Protected Roles:</Text>
</Flex>
<Center
w={isNotSmallerScreen ? '70%' : '100%'}
mt={isNotSmallerScreen ? '0' : '2'}
>
<InputField
variables={variables}
setVariables={setVariables}
inputType={ArrayInputType.PROTECTED_ROLES}
/>
</Center>
</Flex>
</Stack>
</div>
);
};
export default Roles;

View File

@@ -7,6 +7,8 @@ export const TextInputType = {
GOOGLE_CLIENT_ID: 'GOOGLE_CLIENT_ID',
GITHUB_CLIENT_ID: 'GITHUB_CLIENT_ID',
FACEBOOK_CLIENT_ID: 'FACEBOOK_CLIENT_ID',
LINKEDIN_CLIENT_ID: 'LINKEDIN_CLIENT_ID',
APPLE_CLIENT_ID: 'APPLE_CLIENT_ID',
JWT_ROLE_CLAIM: 'JWT_ROLE_CLAIM',
REDIS_URL: 'REDIS_URL',
SMTP_HOST: 'SMTP_HOST',
@@ -31,6 +33,8 @@ export const HiddenInputType = {
GOOGLE_CLIENT_SECRET: 'GOOGLE_CLIENT_SECRET',
GITHUB_CLIENT_SECRET: 'GITHUB_CLIENT_SECRET',
FACEBOOK_CLIENT_SECRET: 'FACEBOOK_CLIENT_SECRET',
LINKEDIN_CLIENT_SECRET: 'LINKEDIN_CLIENT_SECRET',
APPLE_CLIENT_SECRET: 'APPLE_CLIENT_SECRET',
JWT_SECRET: 'JWT_SECRET',
SMTP_PASSWORD: 'SMTP_PASSWORD',
ADMIN_SECRET: 'ADMIN_SECRET',
@@ -63,6 +67,7 @@ export const SwitchInputType = {
DISABLE_BASIC_AUTHENTICATION: 'DISABLE_BASIC_AUTHENTICATION',
DISABLE_SIGN_UP: 'DISABLE_SIGN_UP',
DISABLE_REDIS_FOR_ENV: 'DISABLE_REDIS_FOR_ENV',
DISABLE_STRONG_PASSWORD: 'DISABLE_STRONG_PASSWORD',
};
export const DateInputType = {
@@ -99,6 +104,10 @@ export interface envVarTypes {
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;
ROLES: [string] | [];
DEFAULT_ROLES: [string] | [];
PROTECTED_ROLES: [string] | [];
@@ -123,6 +132,7 @@ export interface envVarTypes {
DISABLE_EMAIL_VERIFICATION: boolean;
DISABLE_BASIC_AUTHENTICATION: boolean;
DISABLE_SIGN_UP: boolean;
DISABLE_STRONG_PASSWORD: boolean;
OLD_ADMIN_SECRET: string;
DATABASE_NAME: string;
DATABASE_TYPE: string;

View File

@@ -26,9 +26,13 @@ export const EnvVariablesQuery = `
GITHUB_CLIENT_SECRET,
FACEBOOK_CLIENT_ID,
FACEBOOK_CLIENT_SECRET,
ROLES,
LINKEDIN_CLIENT_ID,
LINKEDIN_CLIENT_SECRET,
APPLE_CLIENT_ID,
APPLE_CLIENT_SECRET,
DEFAULT_ROLES,
PROTECTED_ROLES,
ROLES,
JWT_TYPE,
JWT_SECRET,
JWT_ROLE_CLAIM,
@@ -49,6 +53,7 @@ export const EnvVariablesQuery = `
DISABLE_EMAIL_VERIFICATION,
DISABLE_BASIC_AUTHENTICATION,
DISABLE_SIGN_UP,
DISABLE_STRONG_PASSWORD,
DISABLE_REDIS_FOR_ENV,
CUSTOM_ACCESS_TOKEN_SCRIPT,
DATABASE_NAME,

View File

@@ -46,6 +46,10 @@ const Environment = () => {
GITHUB_CLIENT_SECRET: '',
FACEBOOK_CLIENT_ID: '',
FACEBOOK_CLIENT_SECRET: '',
LINKEDIN_CLIENT_ID: '',
LINKEDIN_CLIENT_SECRET: '',
APPLE_CLIENT_ID: '',
APPLE_CLIENT_SECRET: '',
ROLES: [],
DEFAULT_ROLES: [],
PROTECTED_ROLES: [],
@@ -70,6 +74,7 @@ const Environment = () => {
DISABLE_EMAIL_VERIFICATION: false,
DISABLE_BASIC_AUTHENTICATION: false,
DISABLE_SIGN_UP: false,
DISABLE_STRONG_PASSWORD: false,
OLD_ADMIN_SECRET: '',
DATABASE_NAME: '',
DATABASE_TYPE: '',
@@ -83,6 +88,8 @@ const Environment = () => {
GOOGLE_CLIENT_SECRET: false,
GITHUB_CLIENT_SECRET: false,
FACEBOOK_CLIENT_SECRET: false,
LINKEDIN_CLIENT_SECRET: false,
APPLE_CLIENT_SECRET: false,
JWT_SECRET: false,
SMTP_PASSWORD: false,
ADMIN_SECRET: false,

6
package-lock.json generated Normal file
View File

@@ -0,0 +1,6 @@
{
"name": "authorizer",
"lockfileVersion": 2,
"requires": true,
"packages": {}
}

View File

@@ -0,0 +1,18 @@
package constants
const (
// AuthRecipeMethodBasicAuth is the basic_auth auth method
AuthRecipeMethodBasicAuth = "basic_auth"
// AuthRecipeMethodMagicLinkLogin is the magic_link_login auth method
AuthRecipeMethodMagicLinkLogin = "magic_link_login"
// AuthRecipeMethodGoogle is the google auth method
AuthRecipeMethodGoogle = "google"
// AuthRecipeMethodGithub is the github auth method
AuthRecipeMethodGithub = "github"
// AuthRecipeMethodFacebook is the facebook auth method
AuthRecipeMethodFacebook = "facebook"
// AuthRecipeMethodLinkedin is the linkedin auth method
AuthRecipeMethodLinkedIn = "linkedin"
// AuthRecipeMethodApple is the apple auth method
AuthRecipeMethodApple = "apple"
)

View File

@@ -19,4 +19,10 @@ const (
DbTypeMariaDB = "mariadb"
// DbTypeCassandra is the cassandra database type
DbTypeCassandraDB = "cassandradb"
// DbTypeScyllaDB is the scylla database type
DbTypeScyllaDB = "scylladb"
// DbTypeCockroachDB is the cockroach database type
DbTypeCockroachDB = "cockroachdb"
// DbTypePlanetScaleDB is the planetscale database type
DbTypePlanetScaleDB = "planetscale"
)

View File

@@ -3,14 +3,8 @@ package constants
var VERSION = "0.0.1"
const (
// Envstore identifier
// StringStore string store identifier
// StringStoreIdentifier = "stringStore"
// // BoolStore bool store identifier
// BoolStoreIdentifier = "boolStore"
// // SliceStore slice store identifier
// SliceStoreIdentifier = "sliceStore"
// TestEnv is used for testing
TestEnv = "test"
// EnvKeyEnv key for env variable ENV
EnvKeyEnv = "ENV"
// EnvKeyEnvPath key for cli arg variable ENV_PATH
@@ -81,6 +75,14 @@ const (
EnvKeyFacebookClientID = "FACEBOOK_CLIENT_ID"
// EnvKeyFacebookClientSecret key for env variable FACEBOOK_CLIENT_SECRET
EnvKeyFacebookClientSecret = "FACEBOOK_CLIENT_SECRET"
// EnvKeyLinkedinClientID key for env variable LINKEDIN_CLIENT_ID
EnvKeyLinkedInClientID = "LINKEDIN_CLIENT_ID"
// EnvKeyLinkedinClientSecret key for env variable LINKEDIN_CLIENT_SECRET
EnvKeyLinkedInClientSecret = "LINKEDIN_CLIENT_SECRET"
// EnvKeyAppleClientID key for env variable APPLE_CLIENT_ID
EnvKeyAppleClientID = "APPLE_CLIENT_ID"
// EnvKeyAppleClientSecret key for env variable APPLE_CLIENT_SECRET
EnvKeyAppleClientSecret = "APPLE_CLIENT_SECRET"
// EnvKeyOrganizationName key for env variable ORGANIZATION_NAME
EnvKeyOrganizationName = "ORGANIZATION_NAME"
// EnvKeyOrganizationLogo key for env variable ORGANIZATION_LOGO
@@ -113,6 +115,8 @@ const (
EnvKeyDisableSignUp = "DISABLE_SIGN_UP"
// EnvKeyDisableRedisForEnv key for env variable DISABLE_REDIS_FOR_ENV
EnvKeyDisableRedisForEnv = "DISABLE_REDIS_FOR_ENV"
// EnvKeyDisableStrongPassword key for env variable DISABLE_STRONG_PASSWORD
EnvKeyDisableStrongPassword = "DISABLE_STRONG_PASSWORD"
// Slice variables
// EnvKeyRoles key for env variable ROLES

View File

@@ -8,4 +8,7 @@ const (
FacebookUserInfoURL = "https://graph.facebook.com/me?fields=id,first_name,last_name,name,email,picture&access_token="
// 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"
// 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~))"
)

View File

@@ -1,14 +0,0 @@
package constants
const (
// SignupMethodBasicAuth is the basic_auth signup method
SignupMethodBasicAuth = "basic_auth"
// SignupMethodMagicLinkLogin is the magic_link_login signup method
SignupMethodMagicLinkLogin = "magic_link_login"
// SignupMethodGoogle is the google signup method
SignupMethodGoogle = "google"
// SignupMethodGithub is the github signup method
SignupMethodGithub = "github"
// SignupMethodFacebook is the facebook signup method
SignupMethodFacebook = "facebook"
)

View File

@@ -7,4 +7,6 @@ const (
TokenTypeAccessToken = "access_token"
// TokenTypeIdentityToken is the identity_token token type
TokenTypeIdentityToken = "id_token"
// TokenTypeSessionToken is the session_token type used for browser session
TokenTypeSessionToken = "session_token"
)

View File

@@ -0,0 +1,18 @@
package constants
const (
// UserLoginWebhookEvent name for login event
UserLoginWebhookEvent = `user.login`
// UserCreatedWebhookEvent name for user creation event
// This is triggered when user entry is created but still not verified
UserCreatedWebhookEvent = `user.created`
// UserSignUpWebhookEvent name for signup event
UserSignUpWebhookEvent = `user.signup`
// UserAccessRevokedWebhookEvent name for user access revoke event
UserAccessRevokedWebhookEvent = `user.access_revoked`
// UserAccessEnabledWebhookEvent name for user access enable event
UserAccessEnabledWebhookEvent = `user.access_enabled`
// UserDeletedWebhookEvent name for user deleted event
UserDeletedWebhookEvent = `user.deleted`
)

View File

@@ -20,10 +20,10 @@ func InitDB() error {
envs := memorystore.RequiredEnvStoreObj.GetRequiredEnv()
isSQL := envs.DatabaseType != constants.DbTypeArangodb && envs.DatabaseType != constants.DbTypeMongodb && envs.DatabaseType != constants.DbTypeCassandraDB
isSQL := envs.DatabaseType != constants.DbTypeArangodb && envs.DatabaseType != constants.DbTypeMongodb && envs.DatabaseType != constants.DbTypeCassandraDB && envs.DatabaseType != constants.DbTypeScyllaDB
isArangoDB := envs.DatabaseType == constants.DbTypeArangodb
isMongoDB := envs.DatabaseType == constants.DbTypeMongodb
isCassandra := envs.DatabaseType == constants.DbTypeCassandraDB
isCassandra := envs.DatabaseType == constants.DbTypeCassandraDB || envs.DatabaseType == constants.DbTypeScyllaDB
if isSQL {
log.Info("Initializing SQL Driver for: ", envs.DatabaseType)

View File

@@ -6,6 +6,8 @@ type CollectionList struct {
VerificationRequest string
Session string
Env string
Webhook string
WebhookLog string
}
var (
@@ -17,5 +19,7 @@ var (
VerificationRequest: Prefix + "verification_requests",
Session: Prefix + "sessions",
Env: Prefix + "env",
Webhook: Prefix + "webhook",
WebhookLog: Prefix + "webhook_log",
}
)

View File

@@ -6,8 +6,7 @@ package models
type Session 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"`
UserID string `gorm:"type:char(36),index:" json:"user_id" bson:"user_id" cql:"user_id"`
User User `gorm:"constraint:OnUpdate:CASCADE,OnDelete:CASCADE;" json:"-" bson:"-" cql:"-"`
UserID string `gorm:"type:char(36)" json:"user_id" bson:"user_id" cql:"user_id"`
UserAgent string `json:"user_agent" bson:"user_agent" cql:"user_agent"`
IP string `json:"ip" bson:"ip" cql:"ip"`
CreatedAt int64 `json:"created_at" bson:"created_at" cql:"created_at"`

View File

@@ -38,9 +38,13 @@ func (user *User) AsAPIUser() *model.User {
email := user.Email
createdAt := user.CreatedAt
updatedAt := user.UpdatedAt
revokedTimestamp := user.RevokedTimestamp
id := user.ID
if strings.Contains(id, Collections.WebhookLog+"/") {
id = strings.TrimPrefix(id, Collections.WebhookLog+"/")
}
return &model.User{
ID: user.ID,
ID: id,
Email: user.Email,
EmailVerified: isEmailVerified,
SignupMethods: user.SignupMethods,
@@ -55,7 +59,7 @@ func (user *User) AsAPIUser() *model.User {
PhoneNumberVerified: &isPhoneVerified,
Picture: user.Picture,
Roles: strings.Split(user.Roles, ","),
RevokedTimestamp: revokedTimestamp,
RevokedTimestamp: user.RevokedTimestamp,
CreatedAt: &createdAt,
UpdatedAt: &updatedAt,
}

View File

@@ -1,6 +1,10 @@
package models
import "github.com/authorizerdev/authorizer/server/graph/model"
import (
"strings"
"github.com/authorizerdev/authorizer/server/graph/model"
)
// Note: any change here should be reflected in providers/casandra/provider.go as it does not have model support in collection creation
@@ -27,8 +31,13 @@ func (v *VerificationRequest) AsAPIVerificationRequest() *model.VerificationRequ
redirectURI := v.RedirectURI
expires := v.ExpiresAt
identifier := v.Identifier
id := v.ID
if strings.Contains(id, Collections.WebhookLog+"/") {
id = strings.TrimPrefix(id, Collections.WebhookLog+"/")
}
return &model.VerificationRequest{
ID: v.ID,
ID: id,
Token: &token,
Identifier: &identifier,
Expires: &expires,

View File

@@ -0,0 +1,41 @@
package models
import (
"encoding/json"
"strings"
"github.com/authorizerdev/authorizer/server/graph/model"
)
// Note: any change here should be reflected in providers/casandra/provider.go as it does not have model support in collection creation
// Webhook model for db
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"`
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"`
}
func (w *Webhook) AsAPIWebhook() *model.Webhook {
headersMap := make(map[string]interface{})
json.Unmarshal([]byte(w.Headers), &headersMap)
id := w.ID
if strings.Contains(id, Collections.Webhook+"/") {
id = strings.TrimPrefix(id, Collections.Webhook+"/")
}
return &model.Webhook{
ID: id,
EventName: &w.EventName,
Endpoint: &w.EndPoint,
Headers: headersMap,
Enabled: &w.Enabled,
CreatedAt: &w.CreatedAt,
UpdatedAt: &w.UpdatedAt,
}
}

View File

@@ -0,0 +1,37 @@
package models
import (
"strings"
"github.com/authorizerdev/authorizer/server/graph/model"
)
// Note: any change here should be reflected in providers/casandra/provider.go as it does not have model support in collection creation
// WebhookLog model for db
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"`
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"`
}
func (w *WebhookLog) AsAPIWebhookLog() *model.WebhookLog {
id := w.ID
if strings.Contains(id, Collections.WebhookLog+"/") {
id = strings.TrimPrefix(id, Collections.WebhookLog+"/")
}
return &model.WebhookLog{
ID: id,
HTTPStatus: &w.HttpStatus,
Response: &w.Response,
Request: &w.Request,
WebhookID: &w.WebhookID,
CreatedAt: &w.CreatedAt,
UpdatedAt: &w.UpdatedAt,
}
}

View File

@@ -1,6 +1,7 @@
package arangodb
import (
"context"
"fmt"
"time"
@@ -11,15 +12,15 @@ import (
)
// AddEnv to save environment information in database
func (p *provider) AddEnv(env models.Env) (models.Env, error) {
func (p *provider) AddEnv(ctx context.Context, env models.Env) (models.Env, error) {
if env.ID == "" {
env.ID = uuid.New().String()
}
env.CreatedAt = time.Now().Unix()
env.UpdatedAt = time.Now().Unix()
configCollection, _ := p.db.Collection(nil, models.Collections.Env)
meta, err := configCollection.CreateDocument(arangoDriver.WithOverwrite(nil), env)
configCollection, _ := p.db.Collection(ctx, models.Collections.Env)
meta, err := configCollection.CreateDocument(arangoDriver.WithOverwrite(ctx), env)
if err != nil {
return env, err
}
@@ -29,10 +30,10 @@ func (p *provider) AddEnv(env models.Env) (models.Env, error) {
}
// UpdateEnv to update environment information in database
func (p *provider) UpdateEnv(env models.Env) (models.Env, error) {
func (p *provider) UpdateEnv(ctx context.Context, env models.Env) (models.Env, error) {
env.UpdatedAt = time.Now().Unix()
collection, _ := p.db.Collection(nil, models.Collections.Env)
meta, err := collection.UpdateDocument(nil, env.Key, env)
collection, _ := p.db.Collection(ctx, models.Collections.Env)
meta, err := collection.UpdateDocument(ctx, env.Key, env)
if err != nil {
return env, err
}
@@ -43,11 +44,11 @@ func (p *provider) UpdateEnv(env models.Env) (models.Env, error) {
}
// GetEnv to get environment information from database
func (p *provider) GetEnv() (models.Env, error) {
func (p *provider) GetEnv(ctx context.Context) (models.Env, error) {
var env models.Env
query := fmt.Sprintf("FOR d in %s RETURN d", models.Collections.Env)
cursor, err := p.db.Query(nil, query, nil)
cursor, err := p.db.Query(ctx, query, nil)
if err != nil {
return env, err
}
@@ -60,7 +61,7 @@ func (p *provider) GetEnv() (models.Env, error) {
}
break
}
_, err := cursor.ReadDocument(nil, &env)
_, err := cursor.ReadDocument(ctx, &env)
if err != nil {
return env, err
}

View File

@@ -107,6 +107,33 @@ func NewProvider() (*provider, error) {
}
}
webhookCollectionExists, err := arangodb.CollectionExists(ctx, models.Collections.Webhook)
if !webhookCollectionExists {
_, err = arangodb.CreateCollection(ctx, models.Collections.Webhook, nil)
if err != nil {
return nil, err
}
}
webhookCollection, _ := arangodb.Collection(nil, models.Collections.Webhook)
webhookCollection.EnsureHashIndex(ctx, []string{"event_name"}, &arangoDriver.EnsureHashIndexOptions{
Unique: true,
Sparse: true,
})
webhookLogCollectionExists, err := arangodb.CollectionExists(ctx, models.Collections.WebhookLog)
if !webhookLogCollectionExists {
_, err = arangodb.CreateCollection(ctx, models.Collections.WebhookLog, nil)
if err != nil {
return nil, err
}
}
webhookLogCollection, _ := arangodb.Collection(nil, models.Collections.WebhookLog)
webhookLogCollection.EnsureHashIndex(ctx, []string{"webhook_id"}, &arangoDriver.EnsureHashIndexOptions{
Sparse: true,
})
return &provider{
db: arangodb,
}, err

View File

@@ -1,7 +1,7 @@
package arangodb
import (
"fmt"
"context"
"time"
"github.com/authorizerdev/authorizer/server/db/models"
@@ -9,31 +9,17 @@ import (
)
// AddSession to save session information in database
func (p *provider) AddSession(session models.Session) error {
func (p *provider) AddSession(ctx context.Context, session models.Session) error {
if session.ID == "" {
session.ID = uuid.New().String()
}
session.CreatedAt = time.Now().Unix()
session.UpdatedAt = time.Now().Unix()
sessionCollection, _ := p.db.Collection(nil, models.Collections.Session)
_, err := sessionCollection.CreateDocument(nil, session)
sessionCollection, _ := p.db.Collection(ctx, models.Collections.Session)
_, err := sessionCollection.CreateDocument(ctx, session)
if err != nil {
return err
}
return nil
}
// DeleteSession to delete session information from database
func (p *provider) DeleteSession(userId string) error {
query := fmt.Sprintf(`FOR d IN %s FILTER d.user_id == @userId REMOVE { _key: d._key } IN %s`, models.Collections.Session, models.Collections.Session)
bindVars := map[string]interface{}{
"userId": userId,
}
cursor, err := p.db.Query(nil, query, bindVars)
if err != nil {
return err
}
defer cursor.Close()
return nil
}

View File

@@ -15,7 +15,7 @@ import (
)
// AddUser to save user information in database
func (p *provider) AddUser(user models.User) (models.User, error) {
func (p *provider) AddUser(ctx context.Context, user models.User) (models.User, error) {
if user.ID == "" {
user.ID = uuid.New().String()
}
@@ -30,8 +30,8 @@ func (p *provider) AddUser(user models.User) (models.User, error) {
user.CreatedAt = time.Now().Unix()
user.UpdatedAt = time.Now().Unix()
userCollection, _ := p.db.Collection(nil, models.Collections.User)
meta, err := userCollection.CreateDocument(arangoDriver.WithOverwrite(nil), user)
userCollection, _ := p.db.Collection(ctx, models.Collections.User)
meta, err := userCollection.CreateDocument(arangoDriver.WithOverwrite(ctx), user)
if err != nil {
return user, err
}
@@ -42,10 +42,10 @@ func (p *provider) AddUser(user models.User) (models.User, error) {
}
// UpdateUser to update user information in database
func (p *provider) UpdateUser(user models.User) (models.User, error) {
func (p *provider) UpdateUser(ctx context.Context, user models.User) (models.User, error) {
user.UpdatedAt = time.Now().Unix()
collection, _ := p.db.Collection(nil, models.Collections.User)
meta, err := collection.UpdateDocument(nil, user.Key, user)
collection, _ := p.db.Collection(ctx, models.Collections.User)
meta, err := collection.UpdateDocument(ctx, user.Key, user)
if err != nil {
return user, err
}
@@ -56,24 +56,34 @@ func (p *provider) UpdateUser(user models.User) (models.User, error) {
}
// DeleteUser to delete user information from database
func (p *provider) DeleteUser(user models.User) error {
collection, _ := p.db.Collection(nil, models.Collections.User)
_, err := collection.RemoveDocument(nil, user.Key)
func (p *provider) DeleteUser(ctx context.Context, user models.User) error {
collection, _ := p.db.Collection(ctx, models.Collections.User)
_, err := collection.RemoveDocument(ctx, user.Key)
if err != nil {
return err
}
query := fmt.Sprintf(`FOR d IN %s FILTER d.user_id == @user_id REMOVE { _key: d._key } IN %s`, models.Collections.Session, models.Collections.Session)
bindVars := map[string]interface{}{
"user_id": user.ID,
}
cursor, err := p.db.Query(ctx, query, bindVars)
if err != nil {
return err
}
defer cursor.Close()
return nil
}
// ListUsers to get list of users from database
func (p *provider) ListUsers(pagination model.Pagination) (*model.Users, error) {
func (p *provider) ListUsers(ctx context.Context, pagination model.Pagination) (*model.Users, error) {
var users []*model.User
ctx := driver.WithQueryFullCount(context.Background())
sctx := driver.WithQueryFullCount(ctx)
query := fmt.Sprintf("FOR d in %s SORT d.created_at DESC LIMIT %d, %d RETURN d", models.Collections.User, pagination.Offset, pagination.Limit)
cursor, err := p.db.Query(ctx, query, nil)
cursor, err := p.db.Query(sctx, query, nil)
if err != nil {
return nil, err
}
@@ -84,7 +94,7 @@ func (p *provider) ListUsers(pagination model.Pagination) (*model.Users, error)
for {
var user models.User
meta, err := cursor.ReadDocument(nil, &user)
meta, err := cursor.ReadDocument(ctx, &user)
if arangoDriver.IsNoMoreDocuments(err) {
break
@@ -104,7 +114,7 @@ func (p *provider) ListUsers(pagination model.Pagination) (*model.Users, error)
}
// GetUserByEmail to get user information from database using email address
func (p *provider) GetUserByEmail(email string) (models.User, error) {
func (p *provider) GetUserByEmail(ctx context.Context, email string) (models.User, error) {
var user models.User
query := fmt.Sprintf("FOR d in %s FILTER d.email == @email RETURN d", models.Collections.User)
@@ -112,7 +122,7 @@ func (p *provider) GetUserByEmail(email string) (models.User, error) {
"email": email,
}
cursor, err := p.db.Query(nil, query, bindVars)
cursor, err := p.db.Query(ctx, query, bindVars)
if err != nil {
return user, err
}
@@ -125,7 +135,7 @@ func (p *provider) GetUserByEmail(email string) (models.User, error) {
}
break
}
_, err := cursor.ReadDocument(nil, &user)
_, err := cursor.ReadDocument(ctx, &user)
if err != nil {
return user, err
}
@@ -135,7 +145,7 @@ func (p *provider) GetUserByEmail(email string) (models.User, error) {
}
// GetUserByID to get user information from database using user ID
func (p *provider) GetUserByID(id string) (models.User, error) {
func (p *provider) GetUserByID(ctx context.Context, id string) (models.User, error) {
var user models.User
query := fmt.Sprintf("FOR d in %s FILTER d._id == @id LIMIT 1 RETURN d", models.Collections.User)
@@ -143,7 +153,7 @@ func (p *provider) GetUserByID(id string) (models.User, error) {
"id": id,
}
cursor, err := p.db.Query(nil, query, bindVars)
cursor, err := p.db.Query(ctx, query, bindVars)
if err != nil {
return user, err
}
@@ -156,7 +166,7 @@ func (p *provider) GetUserByID(id string) (models.User, error) {
}
break
}
_, err := cursor.ReadDocument(nil, &user)
_, err := cursor.ReadDocument(ctx, &user)
if err != nil {
return user, err
}

View File

@@ -12,15 +12,15 @@ import (
)
// AddVerification to save verification request in database
func (p *provider) AddVerificationRequest(verificationRequest models.VerificationRequest) (models.VerificationRequest, error) {
func (p *provider) AddVerificationRequest(ctx context.Context, verificationRequest models.VerificationRequest) (models.VerificationRequest, error) {
if verificationRequest.ID == "" {
verificationRequest.ID = uuid.New().String()
}
verificationRequest.CreatedAt = time.Now().Unix()
verificationRequest.UpdatedAt = time.Now().Unix()
verificationRequestRequestCollection, _ := p.db.Collection(nil, models.Collections.VerificationRequest)
meta, err := verificationRequestRequestCollection.CreateDocument(nil, verificationRequest)
verificationRequestRequestCollection, _ := p.db.Collection(ctx, models.Collections.VerificationRequest)
meta, err := verificationRequestRequestCollection.CreateDocument(ctx, verificationRequest)
if err != nil {
return verificationRequest, err
}
@@ -31,14 +31,14 @@ func (p *provider) AddVerificationRequest(verificationRequest models.Verificatio
}
// GetVerificationRequestByToken to get verification request from database using token
func (p *provider) GetVerificationRequestByToken(token string) (models.VerificationRequest, error) {
func (p *provider) GetVerificationRequestByToken(ctx context.Context, token string) (models.VerificationRequest, error) {
var verificationRequest models.VerificationRequest
query := fmt.Sprintf("FOR d in %s FILTER d.token == @token LIMIT 1 RETURN d", models.Collections.VerificationRequest)
bindVars := map[string]interface{}{
"token": token,
}
cursor, err := p.db.Query(nil, query, bindVars)
cursor, err := p.db.Query(ctx, query, bindVars)
if err != nil {
return verificationRequest, err
}
@@ -51,7 +51,7 @@ func (p *provider) GetVerificationRequestByToken(token string) (models.Verificat
}
break
}
_, err := cursor.ReadDocument(nil, &verificationRequest)
_, err := cursor.ReadDocument(ctx, &verificationRequest)
if err != nil {
return verificationRequest, err
}
@@ -61,7 +61,7 @@ func (p *provider) GetVerificationRequestByToken(token string) (models.Verificat
}
// GetVerificationRequestByEmail to get verification request by email from database
func (p *provider) GetVerificationRequestByEmail(email string, identifier string) (models.VerificationRequest, error) {
func (p *provider) GetVerificationRequestByEmail(ctx context.Context, email string, identifier string) (models.VerificationRequest, error) {
var verificationRequest models.VerificationRequest
query := fmt.Sprintf("FOR d in %s FILTER d.email == @email FILTER d.identifier == @identifier LIMIT 1 RETURN d", models.Collections.VerificationRequest)
@@ -70,7 +70,7 @@ func (p *provider) GetVerificationRequestByEmail(email string, identifier string
"identifier": identifier,
}
cursor, err := p.db.Query(nil, query, bindVars)
cursor, err := p.db.Query(ctx, query, bindVars)
if err != nil {
return verificationRequest, err
}
@@ -83,7 +83,7 @@ func (p *provider) GetVerificationRequestByEmail(email string, identifier string
}
break
}
_, err := cursor.ReadDocument(nil, &verificationRequest)
_, err := cursor.ReadDocument(ctx, &verificationRequest)
if err != nil {
return verificationRequest, err
}
@@ -93,12 +93,12 @@ func (p *provider) GetVerificationRequestByEmail(email string, identifier string
}
// ListVerificationRequests to get list of verification requests from database
func (p *provider) ListVerificationRequests(pagination model.Pagination) (*model.VerificationRequests, error) {
func (p *provider) ListVerificationRequests(ctx context.Context, pagination model.Pagination) (*model.VerificationRequests, error) {
var verificationRequests []*model.VerificationRequest
ctx := driver.WithQueryFullCount(context.Background())
sctx := driver.WithQueryFullCount(ctx)
query := fmt.Sprintf("FOR d in %s SORT d.created_at DESC LIMIT %d, %d RETURN d", models.Collections.VerificationRequest, pagination.Offset, pagination.Limit)
cursor, err := p.db.Query(ctx, query, nil)
cursor, err := p.db.Query(sctx, query, nil)
if err != nil {
return nil, err
}
@@ -109,7 +109,7 @@ func (p *provider) ListVerificationRequests(pagination model.Pagination) (*model
for {
var verificationRequest models.VerificationRequest
meta, err := cursor.ReadDocument(nil, &verificationRequest)
meta, err := cursor.ReadDocument(ctx, &verificationRequest)
if driver.IsNoMoreDocuments(err) {
break
@@ -130,7 +130,7 @@ func (p *provider) ListVerificationRequests(pagination model.Pagination) (*model
}
// DeleteVerificationRequest to delete verification request from database
func (p *provider) DeleteVerificationRequest(verificationRequest models.VerificationRequest) error {
func (p *provider) DeleteVerificationRequest(ctx context.Context, verificationRequest models.VerificationRequest) error {
collection, _ := p.db.Collection(nil, models.Collections.VerificationRequest)
_, err := collection.RemoveDocument(nil, verificationRequest.Key)
if err != nil {

View File

@@ -0,0 +1,161 @@
package arangodb
import (
"context"
"fmt"
"time"
"github.com/arangodb/go-driver"
arangoDriver "github.com/arangodb/go-driver"
"github.com/authorizerdev/authorizer/server/db/models"
"github.com/authorizerdev/authorizer/server/graph/model"
"github.com/google/uuid"
)
// AddWebhook to add webhook
func (p *provider) AddWebhook(ctx context.Context, webhook models.Webhook) (*model.Webhook, error) {
if webhook.ID == "" {
webhook.ID = uuid.New().String()
}
webhook.Key = webhook.ID
webhook.CreatedAt = time.Now().Unix()
webhook.UpdatedAt = time.Now().Unix()
webhookCollection, _ := p.db.Collection(ctx, models.Collections.Webhook)
_, err := webhookCollection.CreateDocument(ctx, webhook)
if err != nil {
return nil, err
}
return webhook.AsAPIWebhook(), nil
}
// UpdateWebhook to update webhook
func (p *provider) UpdateWebhook(ctx context.Context, webhook models.Webhook) (*model.Webhook, error) {
webhook.UpdatedAt = time.Now().Unix()
webhookCollection, _ := p.db.Collection(ctx, models.Collections.Webhook)
meta, err := webhookCollection.UpdateDocument(ctx, webhook.Key, webhook)
if err != nil {
return nil, err
}
webhook.Key = meta.Key
webhook.ID = meta.ID.String()
return webhook.AsAPIWebhook(), nil
}
// ListWebhooks to list webhook
func (p *provider) ListWebhook(ctx context.Context, pagination model.Pagination) (*model.Webhooks, error) {
webhooks := []*model.Webhook{}
query := fmt.Sprintf("FOR d in %s SORT d.created_at DESC LIMIT %d, %d RETURN d", models.Collections.Webhook, pagination.Offset, pagination.Limit)
sctx := driver.WithQueryFullCount(ctx)
cursor, err := p.db.Query(sctx, query, nil)
if err != nil {
return nil, err
}
defer cursor.Close()
paginationClone := pagination
paginationClone.Total = cursor.Statistics().FullCount()
for {
var webhook models.Webhook
meta, err := cursor.ReadDocument(ctx, &webhook)
if arangoDriver.IsNoMoreDocuments(err) {
break
} else if err != nil {
return nil, err
}
if meta.Key != "" {
webhooks = append(webhooks, webhook.AsAPIWebhook())
}
}
return &model.Webhooks{
Pagination: &paginationClone,
Webhooks: webhooks,
}, nil
}
// GetWebhookByID to get webhook by id
func (p *provider) GetWebhookByID(ctx context.Context, webhookID string) (*model.Webhook, error) {
var webhook models.Webhook
query := fmt.Sprintf("FOR d in %s FILTER d._key == @webhook_id RETURN d", models.Collections.Webhook)
bindVars := map[string]interface{}{
"webhook_id": webhookID,
}
cursor, err := p.db.Query(ctx, query, bindVars)
if err != nil {
return nil, err
}
defer cursor.Close()
for {
if !cursor.HasMore() {
if webhook.Key == "" {
return nil, fmt.Errorf("webhook not found")
}
break
}
_, err := cursor.ReadDocument(ctx, &webhook)
if err != nil {
return nil, err
}
}
return webhook.AsAPIWebhook(), nil
}
// GetWebhookByEventName to get webhook by event_name
func (p *provider) GetWebhookByEventName(ctx context.Context, eventName string) (*model.Webhook, error) {
var webhook models.Webhook
query := fmt.Sprintf("FOR d in %s FILTER d.event_name == @event_name RETURN d", models.Collections.Webhook)
bindVars := map[string]interface{}{
"event_name": eventName,
}
cursor, err := p.db.Query(ctx, query, bindVars)
if err != nil {
return nil, err
}
defer cursor.Close()
for {
if !cursor.HasMore() {
if webhook.Key == "" {
return nil, fmt.Errorf("webhook not found")
}
break
}
_, err := cursor.ReadDocument(ctx, &webhook)
if err != nil {
return nil, err
}
}
return webhook.AsAPIWebhook(), nil
}
// DeleteWebhook to delete webhook
func (p *provider) DeleteWebhook(ctx context.Context, webhook *model.Webhook) error {
webhookCollection, _ := p.db.Collection(ctx, models.Collections.Webhook)
_, err := webhookCollection.RemoveDocument(ctx, webhook.ID)
if err != nil {
return err
}
query := fmt.Sprintf("FOR d IN %s FILTER d.webhook_id == @webhook_id REMOVE { _key: d._key } IN %s", models.Collections.WebhookLog, models.Collections.WebhookLog)
bindVars := map[string]interface{}{
"webhook_id": webhook.ID,
}
cursor, err := p.db.Query(ctx, query, bindVars)
if err != nil {
return err
}
defer cursor.Close()
return nil
}

View File

@@ -0,0 +1,75 @@
package arangodb
import (
"context"
"fmt"
"time"
"github.com/arangodb/go-driver"
arangoDriver "github.com/arangodb/go-driver"
"github.com/authorizerdev/authorizer/server/db/models"
"github.com/authorizerdev/authorizer/server/graph/model"
"github.com/google/uuid"
)
// AddWebhookLog to add webhook log
func (p *provider) AddWebhookLog(ctx context.Context, webhookLog models.WebhookLog) (*model.WebhookLog, error) {
if webhookLog.ID == "" {
webhookLog.ID = uuid.New().String()
}
webhookLog.Key = webhookLog.ID
webhookLog.CreatedAt = time.Now().Unix()
webhookLog.UpdatedAt = time.Now().Unix()
webhookLogCollection, _ := p.db.Collection(ctx, models.Collections.WebhookLog)
_, err := webhookLogCollection.CreateDocument(ctx, webhookLog)
if err != nil {
return nil, err
}
return webhookLog.AsAPIWebhookLog(), nil
}
// ListWebhookLogs to list webhook logs
func (p *provider) ListWebhookLogs(ctx context.Context, pagination model.Pagination, webhookID string) (*model.WebhookLogs, error) {
webhookLogs := []*model.WebhookLog{}
bindVariables := map[string]interface{}{}
query := fmt.Sprintf("FOR d in %s SORT d.created_at DESC LIMIT %d, %d RETURN d", models.Collections.WebhookLog, pagination.Offset, pagination.Limit)
if webhookID != "" {
query = fmt.Sprintf("FOR d in %s FILTER d.webhook_id == @webhook_id SORT d.created_at DESC LIMIT %d, %d RETURN d", models.Collections.WebhookLog, pagination.Offset, pagination.Limit)
bindVariables = map[string]interface{}{
"webhook_id": webhookID,
}
}
sctx := driver.WithQueryFullCount(ctx)
cursor, err := p.db.Query(sctx, query, bindVariables)
if err != nil {
return nil, err
}
defer cursor.Close()
paginationClone := pagination
paginationClone.Total = cursor.Statistics().FullCount()
for {
var webhookLog models.WebhookLog
meta, err := cursor.ReadDocument(ctx, &webhookLog)
if arangoDriver.IsNoMoreDocuments(err) {
break
} else if err != nil {
return nil, err
}
if meta.Key != "" {
webhookLogs = append(webhookLogs, webhookLog.AsAPIWebhookLog())
}
}
return &model.WebhookLogs{
Pagination: &paginationClone,
WebhookLogs: webhookLogs,
}, nil
}

View File

@@ -1,6 +1,7 @@
package cassandradb
import (
"context"
"fmt"
"time"
@@ -10,7 +11,7 @@ import (
)
// AddEnv to save environment information in database
func (p *provider) AddEnv(env models.Env) (models.Env, error) {
func (p *provider) AddEnv(ctx context.Context, env models.Env) (models.Env, error) {
if env.ID == "" {
env.ID = uuid.New().String()
}
@@ -27,7 +28,7 @@ func (p *provider) AddEnv(env models.Env) (models.Env, error) {
}
// UpdateEnv to update environment information in database
func (p *provider) UpdateEnv(env models.Env) (models.Env, error) {
func (p *provider) UpdateEnv(ctx context.Context, env models.Env) (models.Env, error) {
env.UpdatedAt = time.Now().Unix()
updateEnvQuery := fmt.Sprintf("UPDATE %s SET env = '%s', updated_at = %d WHERE id = '%s'", KeySpace+"."+models.Collections.Env, env.EnvData, env.UpdatedAt, env.ID)
@@ -39,7 +40,7 @@ func (p *provider) UpdateEnv(env models.Env) (models.Env, error) {
}
// GetEnv to get environment information from database
func (p *provider) GetEnv() (models.Env, error) {
func (p *provider) GetEnv(ctx context.Context) (models.Env, error) {
var env models.Env
query := fmt.Sprintf("SELECT id, env, hash, created_at, updated_at FROM %s LIMIT 1", KeySpace+"."+models.Collections.Env)

View File

@@ -5,6 +5,7 @@ import (
"crypto/x509"
"fmt"
"strings"
"time"
"github.com/authorizerdev/authorizer/server/constants"
"github.com/authorizerdev/authorizer/server/crypto"
@@ -29,6 +30,8 @@ func NewProvider() (*provider, error) {
dbPort := memorystore.RequiredEnvStoreObj.GetRequiredEnv().DatabasePort
if dbPort != "" && dbHost != "" {
dbURL = fmt.Sprintf("%s:%s", dbHost, dbPort)
} else if dbHost != "" {
dbURL = dbHost
}
}
@@ -94,6 +97,8 @@ func NewProvider() (*provider, error) {
NumRetries: 3,
}
cassandraClient.Consistency = gocql.LocalQuorum
cassandraClient.ConnectTimeout = 10 * time.Second
cassandraClient.ProtoVersion = 4
session, err := cassandraClient.CreateSession()
if err != nil {
@@ -138,6 +143,11 @@ func NewProvider() (*provider, error) {
if err != nil {
return nil, err
}
sessionIndexQuery := fmt.Sprintf("CREATE INDEX IF NOT EXISTS authorizer_session_user_id ON %s.%s (user_id)", KeySpace, models.Collections.Session)
err = session.Query(sessionIndexQuery).Exec()
if err != nil {
return nil, err
}
userCollectionQuery := fmt.Sprintf("CREATE TABLE IF NOT EXISTS %s.%s (id text, email text, email_verified_at bigint, password text, signup_methods text, given_name text, family_name text, middle_name text, nickname text, gender text, birthdate text, phone_number text, phone_number_verified_at bigint, picture text, roles text, updated_at bigint, created_at bigint, revoked_timestamp bigint, PRIMARY KEY (id))", KeySpace, models.Collections.User)
err = session.Query(userCollectionQuery).Exec()
@@ -172,6 +182,28 @@ func NewProvider() (*provider, error) {
return nil, err
}
webhookCollectionQuery := fmt.Sprintf("CREATE TABLE IF NOT EXISTS %s.%s (id text, event_name text, endpoint text, enabled boolean, headers text, updated_at bigint, created_at bigint, PRIMARY KEY (id))", KeySpace, models.Collections.Webhook)
err = session.Query(webhookCollectionQuery).Exec()
if err != nil {
return nil, err
}
webhookIndexQuery := fmt.Sprintf("CREATE INDEX IF NOT EXISTS authorizer_webhook_event_name ON %s.%s (event_name)", KeySpace, models.Collections.Webhook)
err = session.Query(webhookIndexQuery).Exec()
if err != nil {
return nil, err
}
webhookLogCollectionQuery := fmt.Sprintf("CREATE TABLE IF NOT EXISTS %s.%s (id text, http_status bigint, response text, request text, webhook_id text,updated_at bigint, created_at bigint, PRIMARY KEY (id))", KeySpace, models.Collections.WebhookLog)
err = session.Query(webhookLogCollectionQuery).Exec()
if err != nil {
return nil, err
}
webhookLogIndexQuery := fmt.Sprintf("CREATE INDEX IF NOT EXISTS authorizer_webhook_log_webhook_id ON %s.%s (webhook_id)", KeySpace, models.Collections.WebhookLog)
err = session.Query(webhookLogIndexQuery).Exec()
if err != nil {
return nil, err
}
return &provider{
db: session,
}, err

View File

@@ -1,6 +1,7 @@
package cassandradb
import (
"context"
"fmt"
"time"
@@ -9,7 +10,7 @@ import (
)
// AddSession to save session information in database
func (p *provider) AddSession(session models.Session) error {
func (p *provider) AddSession(ctx context.Context, session models.Session) error {
if session.ID == "" {
session.ID = uuid.New().String()
}
@@ -24,13 +25,3 @@ func (p *provider) AddSession(session models.Session) error {
}
return nil
}
// DeleteSession to delete session information from database
func (p *provider) DeleteSession(userId string) error {
deleteSessionQuery := fmt.Sprintf("DELETE FROM %s WHERE user_id = '%s'", KeySpace+"."+models.Collections.Session, userId)
err := p.db.Query(deleteSessionQuery).Exec()
if err != nil {
return err
}
return nil
}

View File

@@ -1,6 +1,7 @@
package cassandradb
import (
"context"
"encoding/json"
"fmt"
"reflect"
@@ -16,7 +17,7 @@ import (
)
// AddUser to save user information in database
func (p *provider) AddUser(user models.User) (models.User, error) {
func (p *provider) AddUser(ctx context.Context, user models.User) (models.User, error) {
if user.ID == "" {
user.ID = uuid.New().String()
}
@@ -79,7 +80,7 @@ func (p *provider) AddUser(user models.User) (models.User, error) {
}
// UpdateUser to update user information in database
func (p *provider) UpdateUser(user models.User) (models.User, error) {
func (p *provider) UpdateUser(ctx context.Context, user models.User) (models.User, error) {
user.UpdatedAt = time.Now().Unix()
bytes, err := json.Marshal(user)
@@ -97,10 +98,11 @@ func (p *provider) UpdateUser(user models.User) (models.User, error) {
updateFields := ""
for key, value := range userMap {
if value != nil && key != "_id" {
if key == "_id" {
continue
}
if key == "_id" {
if key == "_key" {
continue
}
@@ -130,14 +132,36 @@ func (p *provider) UpdateUser(user models.User) (models.User, error) {
}
// DeleteUser to delete user information from database
func (p *provider) DeleteUser(user models.User) error {
func (p *provider) DeleteUser(ctx context.Context, user models.User) error {
query := fmt.Sprintf("DELETE FROM %s WHERE id = '%s'", KeySpace+"."+models.Collections.User, user.ID)
err := p.db.Query(query).Exec()
return err
if err != nil {
return err
}
getSessionsQuery := fmt.Sprintf("SELECT id FROM %s WHERE user_id = '%s' ALLOW FILTERING", KeySpace+"."+models.Collections.Session, user.ID)
scanner := p.db.Query(getSessionsQuery).Iter().Scanner()
sessionIDs := ""
for scanner.Next() {
var wlID string
err = scanner.Scan(&wlID)
if err != nil {
return err
}
sessionIDs += fmt.Sprintf("'%s',", wlID)
}
sessionIDs = strings.TrimSuffix(sessionIDs, ",")
deleteSessionQuery := fmt.Sprintf("DELETE FROM %s WHERE id IN (%s)", KeySpace+"."+models.Collections.Session, sessionIDs)
err = p.db.Query(deleteSessionQuery).Exec()
if err != nil {
return err
}
return nil
}
// ListUsers to get list of users from database
func (p *provider) ListUsers(pagination model.Pagination) (*model.Users, error) {
func (p *provider) ListUsers(ctx context.Context, pagination model.Pagination) (*model.Users, error) {
responseUsers := []*model.User{}
paginationClone := pagination
totalCountQuery := fmt.Sprintf(`SELECT COUNT(*) FROM %s`, KeySpace+"."+models.Collections.User)
@@ -171,9 +195,9 @@ func (p *provider) ListUsers(pagination model.Pagination) (*model.Users, error)
}
// GetUserByEmail to get user information from database using email address
func (p *provider) GetUserByEmail(email string) (models.User, error) {
func (p *provider) GetUserByEmail(ctx context.Context, email string) (models.User, error) {
var user models.User
query := fmt.Sprintf("SELECT id, email, email_verified_at, password, signup_methods, given_name, family_name, middle_name, nickname, birthdate, phone_number, phone_number_verified_at, picture, roles, revoked_timestamp, created_at, updated_at FROM %s WHERE email = '%s' LIMIT 1", KeySpace+"."+models.Collections.User, email)
query := fmt.Sprintf("SELECT id, email, email_verified_at, password, signup_methods, given_name, family_name, middle_name, nickname, birthdate, phone_number, phone_number_verified_at, picture, roles, revoked_timestamp, created_at, updated_at FROM %s WHERE email = '%s' LIMIT 1 ALLOW FILTERING", KeySpace+"."+models.Collections.User, email)
err := p.db.Query(query).Consistency(gocql.One).Scan(&user.ID, &user.Email, &user.EmailVerifiedAt, &user.Password, &user.SignupMethods, &user.GivenName, &user.FamilyName, &user.MiddleName, &user.Nickname, &user.Birthdate, &user.PhoneNumber, &user.PhoneNumberVerifiedAt, &user.Picture, &user.Roles, &user.RevokedTimestamp, &user.CreatedAt, &user.UpdatedAt)
if err != nil {
return user, err
@@ -182,7 +206,7 @@ func (p *provider) GetUserByEmail(email string) (models.User, error) {
}
// GetUserByID to get user information from database using user ID
func (p *provider) GetUserByID(id string) (models.User, error) {
func (p *provider) GetUserByID(ctx context.Context, id string) (models.User, error) {
var user models.User
query := fmt.Sprintf("SELECT id, email, email_verified_at, password, signup_methods, given_name, family_name, middle_name, nickname, birthdate, phone_number, phone_number_verified_at, picture, roles, revoked_timestamp, created_at, updated_at FROM %s WHERE id = '%s' LIMIT 1", KeySpace+"."+models.Collections.User, id)
err := p.db.Query(query).Consistency(gocql.One).Scan(&user.ID, &user.Email, &user.EmailVerifiedAt, &user.Password, &user.SignupMethods, &user.GivenName, &user.FamilyName, &user.MiddleName, &user.Nickname, &user.Birthdate, &user.PhoneNumber, &user.PhoneNumberVerifiedAt, &user.Picture, &user.Roles, &user.RevokedTimestamp, &user.CreatedAt, &user.UpdatedAt)

View File

@@ -1,6 +1,7 @@
package cassandradb
import (
"context"
"fmt"
"time"
@@ -11,7 +12,7 @@ import (
)
// AddVerification to save verification request in database
func (p *provider) AddVerificationRequest(verificationRequest models.VerificationRequest) (models.VerificationRequest, error) {
func (p *provider) AddVerificationRequest(ctx context.Context, verificationRequest models.VerificationRequest) (models.VerificationRequest, error) {
if verificationRequest.ID == "" {
verificationRequest.ID = uuid.New().String()
}
@@ -28,7 +29,7 @@ func (p *provider) AddVerificationRequest(verificationRequest models.Verificatio
}
// GetVerificationRequestByToken to get verification request from database using token
func (p *provider) GetVerificationRequestByToken(token string) (models.VerificationRequest, error) {
func (p *provider) GetVerificationRequestByToken(ctx context.Context, token string) (models.VerificationRequest, error) {
var verificationRequest models.VerificationRequest
query := fmt.Sprintf(`SELECT id, jwt_token, identifier, expires_at, email, nonce, redirect_uri, created_at, updated_at FROM %s WHERE jwt_token = '%s' LIMIT 1`, KeySpace+"."+models.Collections.VerificationRequest, token)
@@ -40,7 +41,7 @@ func (p *provider) GetVerificationRequestByToken(token string) (models.Verificat
}
// GetVerificationRequestByEmail to get verification request by email from database
func (p *provider) GetVerificationRequestByEmail(email string, identifier string) (models.VerificationRequest, error) {
func (p *provider) GetVerificationRequestByEmail(ctx context.Context, email string, identifier string) (models.VerificationRequest, error) {
var verificationRequest models.VerificationRequest
query := fmt.Sprintf(`SELECT id, jwt_token, identifier, expires_at, email, nonce, redirect_uri, created_at, updated_at FROM %s WHERE email = '%s' AND identifier = '%s' LIMIT 1 ALLOW FILTERING`, KeySpace+"."+models.Collections.VerificationRequest, email, identifier)
@@ -53,7 +54,7 @@ func (p *provider) GetVerificationRequestByEmail(email string, identifier string
}
// ListVerificationRequests to get list of verification requests from database
func (p *provider) ListVerificationRequests(pagination model.Pagination) (*model.VerificationRequests, error) {
func (p *provider) ListVerificationRequests(ctx context.Context, pagination model.Pagination) (*model.VerificationRequests, error) {
var verificationRequests []*model.VerificationRequest
paginationClone := pagination
@@ -89,7 +90,7 @@ func (p *provider) ListVerificationRequests(pagination model.Pagination) (*model
}
// DeleteVerificationRequest to delete verification request from database
func (p *provider) DeleteVerificationRequest(verificationRequest models.VerificationRequest) error {
func (p *provider) DeleteVerificationRequest(ctx context.Context, verificationRequest models.VerificationRequest) error {
query := fmt.Sprintf("DELETE FROM %s WHERE id = '%s'", KeySpace+"."+models.Collections.VerificationRequest, verificationRequest.ID)
err := p.db.Query(query).Exec()
if err != nil {

View File

@@ -0,0 +1,172 @@
package cassandradb
import (
"context"
"encoding/json"
"fmt"
"reflect"
"strings"
"time"
"github.com/authorizerdev/authorizer/server/db/models"
"github.com/authorizerdev/authorizer/server/graph/model"
"github.com/gocql/gocql"
"github.com/google/uuid"
)
// AddWebhook to add webhook
func (p *provider) AddWebhook(ctx context.Context, webhook models.Webhook) (*model.Webhook, error) {
if webhook.ID == "" {
webhook.ID = uuid.New().String()
}
webhook.Key = webhook.ID
webhook.CreatedAt = time.Now().Unix()
webhook.UpdatedAt = time.Now().Unix()
existingHook, _ := p.GetWebhookByEventName(ctx, webhook.EventName)
if existingHook != nil {
return nil, fmt.Errorf("Webhook with %s event_name already exists", webhook.EventName)
}
insertQuery := fmt.Sprintf("INSERT INTO %s (id, event_name, endpoint, headers, enabled, created_at, updated_at) VALUES ('%s', '%s', '%s', '%s', %t, %d, %d)", KeySpace+"."+models.Collections.Webhook, webhook.ID, webhook.EventName, webhook.EndPoint, webhook.Headers, webhook.Enabled, webhook.CreatedAt, webhook.UpdatedAt)
err := p.db.Query(insertQuery).Exec()
if err != nil {
return nil, err
}
return webhook.AsAPIWebhook(), nil
}
// UpdateWebhook to update webhook
func (p *provider) UpdateWebhook(ctx context.Context, webhook models.Webhook) (*model.Webhook, error) {
webhook.UpdatedAt = time.Now().Unix()
bytes, err := json.Marshal(webhook)
if err != nil {
return nil, err
}
// use decoder instead of json.Unmarshall, because it converts int64 -> float64 after unmarshalling
decoder := json.NewDecoder(strings.NewReader(string(bytes)))
decoder.UseNumber()
webhookMap := map[string]interface{}{}
err = decoder.Decode(&webhookMap)
if err != nil {
return nil, err
}
updateFields := ""
for key, value := range webhookMap {
if key == "_id" {
continue
}
if key == "_key" {
continue
}
if value == nil {
updateFields += fmt.Sprintf("%s = null,", key)
continue
}
valueType := reflect.TypeOf(value)
if valueType.Name() == "string" {
updateFields += fmt.Sprintf("%s = '%s', ", key, value.(string))
} else {
updateFields += fmt.Sprintf("%s = %v, ", key, value)
}
}
updateFields = strings.Trim(updateFields, " ")
updateFields = strings.TrimSuffix(updateFields, ",")
query := fmt.Sprintf("UPDATE %s SET %s WHERE id = '%s'", KeySpace+"."+models.Collections.Webhook, updateFields, webhook.ID)
err = p.db.Query(query).Exec()
if err != nil {
return nil, err
}
return webhook.AsAPIWebhook(), nil
}
// ListWebhooks to list webhook
func (p *provider) ListWebhook(ctx context.Context, pagination model.Pagination) (*model.Webhooks, error) {
webhooks := []*model.Webhook{}
paginationClone := pagination
totalCountQuery := fmt.Sprintf(`SELECT COUNT(*) FROM %s`, KeySpace+"."+models.Collections.Webhook)
err := p.db.Query(totalCountQuery).Consistency(gocql.One).Scan(&paginationClone.Total)
if err != nil {
return nil, err
}
// 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, endpoint, headers, enabled, created_at, updated_at FROM %s LIMIT %d", KeySpace+"."+models.Collections.Webhook, pagination.Limit+pagination.Offset)
scanner := p.db.Query(query).Iter().Scanner()
counter := int64(0)
for scanner.Next() {
if counter >= pagination.Offset {
var webhook models.Webhook
err := scanner.Scan(&webhook.ID, &webhook.EventName, &webhook.EndPoint, &webhook.Headers, &webhook.Enabled, &webhook.CreatedAt, &webhook.UpdatedAt)
if err != nil {
return nil, err
}
webhooks = append(webhooks, webhook.AsAPIWebhook())
}
counter++
}
return &model.Webhooks{
Pagination: &paginationClone,
Webhooks: webhooks,
}, nil
}
// GetWebhookByID to get webhook by id
func (p *provider) GetWebhookByID(ctx context.Context, webhookID string) (*model.Webhook, error) {
var webhook models.Webhook
query := fmt.Sprintf(`SELECT id, event_name, endpoint, headers, enabled, created_at, updated_at FROM %s WHERE id = '%s' LIMIT 1`, KeySpace+"."+models.Collections.Webhook, webhookID)
err := p.db.Query(query).Consistency(gocql.One).Scan(&webhook.ID, &webhook.EventName, &webhook.EndPoint, &webhook.Headers, &webhook.Enabled, &webhook.CreatedAt, &webhook.UpdatedAt)
if err != nil {
return nil, err
}
return webhook.AsAPIWebhook(), nil
}
// GetWebhookByEventName to get webhook by event_name
func (p *provider) GetWebhookByEventName(ctx context.Context, eventName string) (*model.Webhook, error) {
var webhook models.Webhook
query := fmt.Sprintf(`SELECT id, event_name, endpoint, headers, enabled, created_at, updated_at FROM %s WHERE event_name = '%s' LIMIT 1 ALLOW FILTERING`, KeySpace+"."+models.Collections.Webhook, eventName)
err := p.db.Query(query).Consistency(gocql.One).Scan(&webhook.ID, &webhook.EventName, &webhook.EndPoint, &webhook.Headers, &webhook.Enabled, &webhook.CreatedAt, &webhook.UpdatedAt)
if err != nil {
return nil, err
}
return webhook.AsAPIWebhook(), nil
}
// DeleteWebhook to delete webhook
func (p *provider) DeleteWebhook(ctx context.Context, webhook *model.Webhook) error {
query := fmt.Sprintf("DELETE FROM %s WHERE id = '%s'", KeySpace+"."+models.Collections.Webhook, webhook.ID)
err := p.db.Query(query).Exec()
if err != nil {
return err
}
getWebhookLogQuery := fmt.Sprintf("SELECT id FROM %s WHERE webhook_id = '%s' ALLOW FILTERING", KeySpace+"."+models.Collections.WebhookLog, webhook.ID)
scanner := p.db.Query(getWebhookLogQuery).Iter().Scanner()
webhookLogIDs := ""
for scanner.Next() {
var wlID string
err = scanner.Scan(&wlID)
if err != nil {
return err
}
webhookLogIDs += fmt.Sprintf("'%s',", wlID)
}
webhookLogIDs = strings.TrimSuffix(webhookLogIDs, ",")
query = fmt.Sprintf("DELETE FROM %s WHERE id IN (%s)", KeySpace+"."+models.Collections.WebhookLog, webhookLogIDs)
err = p.db.Query(query).Exec()
return err
}

View File

@@ -0,0 +1,70 @@
package cassandradb
import (
"context"
"fmt"
"time"
"github.com/authorizerdev/authorizer/server/db/models"
"github.com/authorizerdev/authorizer/server/graph/model"
"github.com/gocql/gocql"
"github.com/google/uuid"
)
// AddWebhookLog to add webhook log
func (p *provider) AddWebhookLog(ctx context.Context, webhookLog models.WebhookLog) (*model.WebhookLog, error) {
if webhookLog.ID == "" {
webhookLog.ID = uuid.New().String()
}
webhookLog.Key = webhookLog.ID
webhookLog.CreatedAt = time.Now().Unix()
webhookLog.UpdatedAt = time.Now().Unix()
insertQuery := fmt.Sprintf("INSERT INTO %s (id, http_status, response, request, webhook_id, created_at, updated_at) VALUES ('%s', %d,'%s', '%s', '%s', %d, %d)", KeySpace+"."+models.Collections.WebhookLog, webhookLog.ID, webhookLog.HttpStatus, webhookLog.Response, webhookLog.Request, webhookLog.WebhookID, webhookLog.CreatedAt, webhookLog.UpdatedAt)
err := p.db.Query(insertQuery).Exec()
if err != nil {
return nil, err
}
return webhookLog.AsAPIWebhookLog(), nil
}
// ListWebhookLogs to list webhook logs
func (p *provider) ListWebhookLogs(ctx context.Context, pagination model.Pagination, webhookID string) (*model.WebhookLogs, error) {
webhookLogs := []*model.WebhookLog{}
paginationClone := pagination
totalCountQuery := fmt.Sprintf(`SELECT COUNT(*) FROM %s`, KeySpace+"."+models.Collections.WebhookLog)
// 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, http_status, response, request, webhook_id, created_at, updated_at FROM %s LIMIT %d", KeySpace+"."+models.Collections.WebhookLog, pagination.Limit+pagination.Offset)
if webhookID != "" {
totalCountQuery = fmt.Sprintf(`SELECT COUNT(*) FROM %s WHERE webhook_id='%s' ALLOW FILTERING`, KeySpace+"."+models.Collections.WebhookLog, webhookID)
query = fmt.Sprintf("SELECT id, http_status, response, request, webhook_id, created_at, updated_at FROM %s WHERE webhook_id = '%s' LIMIT %d ALLOW FILTERING", KeySpace+"."+models.Collections.WebhookLog, webhookID, pagination.Limit+pagination.Offset)
}
err := p.db.Query(totalCountQuery).Consistency(gocql.One).Scan(&paginationClone.Total)
if err != nil {
return nil, err
}
scanner := p.db.Query(query).Iter().Scanner()
counter := int64(0)
for scanner.Next() {
if counter >= pagination.Offset {
var webhookLog models.WebhookLog
err := scanner.Scan(&webhookLog.ID, &webhookLog.HttpStatus, &webhookLog.Response, &webhookLog.Request, &webhookLog.WebhookID, &webhookLog.CreatedAt, &webhookLog.UpdatedAt)
if err != nil {
return nil, err
}
webhookLogs = append(webhookLogs, webhookLog.AsAPIWebhookLog())
}
counter++
}
return &model.WebhookLogs{
Pagination: &paginationClone,
WebhookLogs: webhookLogs,
}, nil
}

View File

@@ -1,6 +1,7 @@
package mongodb
import (
"context"
"fmt"
"time"
@@ -11,7 +12,7 @@ import (
)
// AddEnv to save environment information in database
func (p *provider) AddEnv(env models.Env) (models.Env, error) {
func (p *provider) AddEnv(ctx context.Context, env models.Env) (models.Env, error) {
if env.ID == "" {
env.ID = uuid.New().String()
}
@@ -20,7 +21,7 @@ func (p *provider) AddEnv(env models.Env) (models.Env, error) {
env.UpdatedAt = time.Now().Unix()
env.Key = env.ID
configCollection := p.db.Collection(models.Collections.Env, options.Collection())
_, err := configCollection.InsertOne(nil, env)
_, err := configCollection.InsertOne(ctx, env)
if err != nil {
return env, err
}
@@ -28,10 +29,10 @@ func (p *provider) AddEnv(env models.Env) (models.Env, error) {
}
// UpdateEnv to update environment information in database
func (p *provider) UpdateEnv(env models.Env) (models.Env, error) {
func (p *provider) UpdateEnv(ctx context.Context, env models.Env) (models.Env, error) {
env.UpdatedAt = time.Now().Unix()
configCollection := p.db.Collection(models.Collections.Env, options.Collection())
_, err := configCollection.UpdateOne(nil, bson.M{"_id": bson.M{"$eq": env.ID}}, bson.M{"$set": env}, options.MergeUpdateOptions())
_, err := configCollection.UpdateOne(ctx, bson.M{"_id": bson.M{"$eq": env.ID}}, bson.M{"$set": env}, options.MergeUpdateOptions())
if err != nil {
return env, err
}
@@ -39,14 +40,14 @@ func (p *provider) UpdateEnv(env models.Env) (models.Env, error) {
}
// GetEnv to get environment information from database
func (p *provider) GetEnv() (models.Env, error) {
func (p *provider) GetEnv(ctx context.Context) (models.Env, error) {
var env models.Env
configCollection := p.db.Collection(models.Collections.Env, options.Collection())
cursor, err := configCollection.Find(nil, bson.M{}, options.Find())
cursor, err := configCollection.Find(ctx, bson.M{}, options.Find())
if err != nil {
return env, err
}
defer cursor.Close(nil)
defer cursor.Close(ctx)
for cursor.Next(nil) {
err := cursor.Decode(&env)

View File

@@ -83,6 +83,24 @@ func NewProvider() (*provider, error) {
mongodb.CreateCollection(ctx, models.Collections.Env, options.CreateCollection())
mongodb.CreateCollection(ctx, models.Collections.Webhook, options.CreateCollection())
webhookCollection := mongodb.Collection(models.Collections.Webhook, options.Collection())
webhookCollection.Indexes().CreateMany(ctx, []mongo.IndexModel{
{
Keys: bson.M{"event_name": 1},
Options: options.Index().SetUnique(true).SetSparse(true),
},
}, options.CreateIndexes())
mongodb.CreateCollection(ctx, models.Collections.WebhookLog, options.CreateCollection())
webhookLogCollection := mongodb.Collection(models.Collections.WebhookLog, options.Collection())
webhookLogCollection.Indexes().CreateMany(ctx, []mongo.IndexModel{
{
Keys: bson.M{"webhook_id": 1},
Options: options.Index().SetSparse(true),
},
}, options.CreateIndexes())
return &provider{
db: mongodb,
}, nil

View File

@@ -1,16 +1,16 @@
package mongodb
import (
"context"
"time"
"github.com/authorizerdev/authorizer/server/db/models"
"github.com/google/uuid"
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/mongo/options"
)
// AddSession to save session information in database
func (p *provider) AddSession(session models.Session) error {
func (p *provider) AddSession(ctx context.Context, session models.Session) error {
if session.ID == "" {
session.ID = uuid.New().String()
}
@@ -19,17 +19,7 @@ func (p *provider) AddSession(session models.Session) error {
session.CreatedAt = time.Now().Unix()
session.UpdatedAt = time.Now().Unix()
sessionCollection := p.db.Collection(models.Collections.Session, options.Collection())
_, err := sessionCollection.InsertOne(nil, session)
if err != nil {
return err
}
return nil
}
// DeleteSession to delete session information from database
func (p *provider) DeleteSession(userId string) error {
sessionCollection := p.db.Collection(models.Collections.Session, options.Collection())
_, err := sessionCollection.DeleteMany(nil, bson.M{"user_id": userId}, options.Delete())
_, err := sessionCollection.InsertOne(ctx, session)
if err != nil {
return err
}

View File

@@ -1,6 +1,7 @@
package mongodb
import (
"context"
"time"
"github.com/authorizerdev/authorizer/server/constants"
@@ -13,7 +14,7 @@ import (
)
// AddUser to save user information in database
func (p *provider) AddUser(user models.User) (models.User, error) {
func (p *provider) AddUser(ctx context.Context, user models.User) (models.User, error) {
if user.ID == "" {
user.ID = uuid.New().String()
}
@@ -29,7 +30,7 @@ func (p *provider) AddUser(user models.User) (models.User, error) {
user.UpdatedAt = time.Now().Unix()
user.Key = user.ID
userCollection := p.db.Collection(models.Collections.User, options.Collection())
_, err := userCollection.InsertOne(nil, user)
_, err := userCollection.InsertOne(ctx, user)
if err != nil {
return user, err
}
@@ -38,10 +39,10 @@ func (p *provider) AddUser(user models.User) (models.User, error) {
}
// UpdateUser to update user information in database
func (p *provider) UpdateUser(user models.User) (models.User, error) {
func (p *provider) UpdateUser(ctx context.Context, user models.User) (models.User, error) {
user.UpdatedAt = time.Now().Unix()
userCollection := p.db.Collection(models.Collections.User, options.Collection())
_, err := userCollection.UpdateOne(nil, bson.M{"_id": bson.M{"$eq": user.ID}}, bson.M{"$set": user}, options.MergeUpdateOptions())
_, err := userCollection.UpdateOne(ctx, bson.M{"_id": bson.M{"$eq": user.ID}}, bson.M{"$set": user}, options.MergeUpdateOptions())
if err != nil {
return user, err
}
@@ -49,9 +50,15 @@ func (p *provider) UpdateUser(user models.User) (models.User, error) {
}
// DeleteUser to delete user information from database
func (p *provider) DeleteUser(user models.User) error {
func (p *provider) DeleteUser(ctx context.Context, user models.User) error {
userCollection := p.db.Collection(models.Collections.User, options.Collection())
_, err := userCollection.DeleteOne(nil, bson.M{"_id": user.ID}, options.Delete())
_, err := userCollection.DeleteOne(ctx, bson.M{"_id": user.ID}, options.Delete())
if err != nil {
return err
}
sessionCollection := p.db.Collection(models.Collections.Session, options.Collection())
_, err = sessionCollection.DeleteMany(ctx, bson.M{"user_id": user.ID}, options.Delete())
if err != nil {
return err
}
@@ -60,7 +67,7 @@ func (p *provider) DeleteUser(user models.User) error {
}
// ListUsers to get list of users from database
func (p *provider) ListUsers(pagination model.Pagination) (*model.Users, error) {
func (p *provider) ListUsers(ctx context.Context, pagination model.Pagination) (*model.Users, error) {
var users []*model.User
opts := options.Find()
opts.SetLimit(pagination.Limit)
@@ -68,23 +75,22 @@ func (p *provider) ListUsers(pagination model.Pagination) (*model.Users, error)
opts.SetSort(bson.M{"created_at": -1})
paginationClone := pagination
// TODO add pagination total
userCollection := p.db.Collection(models.Collections.User, options.Collection())
count, err := userCollection.CountDocuments(nil, bson.M{}, options.Count())
count, err := userCollection.CountDocuments(ctx, bson.M{}, options.Count())
if err != nil {
return nil, err
}
paginationClone.Total = count
cursor, err := userCollection.Find(nil, bson.M{}, opts)
cursor, err := userCollection.Find(ctx, bson.M{}, opts)
if err != nil {
return nil, err
}
defer cursor.Close(nil)
defer cursor.Close(ctx)
for cursor.Next(nil) {
for cursor.Next(ctx) {
var user models.User
err := cursor.Decode(&user)
if err != nil {
@@ -100,10 +106,10 @@ func (p *provider) ListUsers(pagination model.Pagination) (*model.Users, error)
}
// GetUserByEmail to get user information from database using email address
func (p *provider) GetUserByEmail(email string) (models.User, error) {
func (p *provider) GetUserByEmail(ctx context.Context, email string) (models.User, error) {
var user models.User
userCollection := p.db.Collection(models.Collections.User, options.Collection())
err := userCollection.FindOne(nil, bson.M{"email": email}).Decode(&user)
err := userCollection.FindOne(ctx, bson.M{"email": email}).Decode(&user)
if err != nil {
return user, err
}
@@ -112,11 +118,11 @@ func (p *provider) GetUserByEmail(email string) (models.User, error) {
}
// GetUserByID to get user information from database using user ID
func (p *provider) GetUserByID(id string) (models.User, error) {
func (p *provider) GetUserByID(ctx context.Context, id string) (models.User, error) {
var user models.User
userCollection := p.db.Collection(models.Collections.User, options.Collection())
err := userCollection.FindOne(nil, bson.M{"_id": id}).Decode(&user)
err := userCollection.FindOne(ctx, bson.M{"_id": id}).Decode(&user)
if err != nil {
return user, err
}

View File

@@ -1,6 +1,7 @@
package mongodb
import (
"context"
"time"
"github.com/authorizerdev/authorizer/server/db/models"
@@ -11,7 +12,7 @@ import (
)
// AddVerification to save verification request in database
func (p *provider) AddVerificationRequest(verificationRequest models.VerificationRequest) (models.VerificationRequest, error) {
func (p *provider) AddVerificationRequest(ctx context.Context, verificationRequest models.VerificationRequest) (models.VerificationRequest, error) {
if verificationRequest.ID == "" {
verificationRequest.ID = uuid.New().String()
@@ -19,7 +20,7 @@ func (p *provider) AddVerificationRequest(verificationRequest models.Verificatio
verificationRequest.UpdatedAt = time.Now().Unix()
verificationRequest.Key = verificationRequest.ID
verificationRequestCollection := p.db.Collection(models.Collections.VerificationRequest, options.Collection())
_, err := verificationRequestCollection.InsertOne(nil, verificationRequest)
_, err := verificationRequestCollection.InsertOne(ctx, verificationRequest)
if err != nil {
return verificationRequest, err
}
@@ -29,11 +30,11 @@ func (p *provider) AddVerificationRequest(verificationRequest models.Verificatio
}
// GetVerificationRequestByToken to get verification request from database using token
func (p *provider) GetVerificationRequestByToken(token string) (models.VerificationRequest, error) {
func (p *provider) GetVerificationRequestByToken(ctx context.Context, token string) (models.VerificationRequest, error) {
var verificationRequest models.VerificationRequest
verificationRequestCollection := p.db.Collection(models.Collections.VerificationRequest, options.Collection())
err := verificationRequestCollection.FindOne(nil, bson.M{"token": token}).Decode(&verificationRequest)
err := verificationRequestCollection.FindOne(ctx, bson.M{"token": token}).Decode(&verificationRequest)
if err != nil {
return verificationRequest, err
}
@@ -42,11 +43,11 @@ func (p *provider) GetVerificationRequestByToken(token string) (models.Verificat
}
// GetVerificationRequestByEmail to get verification request by email from database
func (p *provider) GetVerificationRequestByEmail(email string, identifier string) (models.VerificationRequest, error) {
func (p *provider) GetVerificationRequestByEmail(ctx context.Context, email string, identifier string) (models.VerificationRequest, error) {
var verificationRequest models.VerificationRequest
verificationRequestCollection := p.db.Collection(models.Collections.VerificationRequest, options.Collection())
err := verificationRequestCollection.FindOne(nil, bson.M{"email": email, "identifier": identifier}).Decode(&verificationRequest)
err := verificationRequestCollection.FindOne(ctx, bson.M{"email": email, "identifier": identifier}).Decode(&verificationRequest)
if err != nil {
return verificationRequest, err
}
@@ -55,7 +56,7 @@ func (p *provider) GetVerificationRequestByEmail(email string, identifier string
}
// ListVerificationRequests to get list of verification requests from database
func (p *provider) ListVerificationRequests(pagination model.Pagination) (*model.VerificationRequests, error) {
func (p *provider) ListVerificationRequests(ctx context.Context, pagination model.Pagination) (*model.VerificationRequests, error) {
var verificationRequests []*model.VerificationRequest
opts := options.Find()
@@ -65,17 +66,17 @@ func (p *provider) ListVerificationRequests(pagination model.Pagination) (*model
verificationRequestCollection := p.db.Collection(models.Collections.VerificationRequest, options.Collection())
verificationRequestCollectionCount, err := verificationRequestCollection.CountDocuments(nil, bson.M{})
verificationRequestCollectionCount, err := verificationRequestCollection.CountDocuments(ctx, bson.M{})
paginationClone := pagination
paginationClone.Total = verificationRequestCollectionCount
cursor, err := verificationRequestCollection.Find(nil, bson.M{}, opts)
cursor, err := verificationRequestCollection.Find(ctx, bson.M{}, opts)
if err != nil {
return nil, err
}
defer cursor.Close(nil)
defer cursor.Close(ctx)
for cursor.Next(nil) {
for cursor.Next(ctx) {
var verificationRequest models.VerificationRequest
err := cursor.Decode(&verificationRequest)
if err != nil {
@@ -91,9 +92,9 @@ func (p *provider) ListVerificationRequests(pagination model.Pagination) (*model
}
// DeleteVerificationRequest to delete verification request from database
func (p *provider) DeleteVerificationRequest(verificationRequest models.VerificationRequest) error {
func (p *provider) DeleteVerificationRequest(ctx context.Context, verificationRequest models.VerificationRequest) error {
verificationRequestCollection := p.db.Collection(models.Collections.VerificationRequest, options.Collection())
_, err := verificationRequestCollection.DeleteOne(nil, bson.M{"_id": verificationRequest.ID}, options.Delete())
_, err := verificationRequestCollection.DeleteOne(ctx, bson.M{"_id": verificationRequest.ID}, options.Delete())
if err != nil {
return err
}

View File

@@ -0,0 +1,120 @@
package mongodb
import (
"context"
"time"
"github.com/authorizerdev/authorizer/server/db/models"
"github.com/authorizerdev/authorizer/server/graph/model"
"github.com/google/uuid"
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/mongo/options"
)
// AddWebhook to add webhook
func (p *provider) AddWebhook(ctx context.Context, webhook models.Webhook) (*model.Webhook, error) {
if webhook.ID == "" {
webhook.ID = uuid.New().String()
}
webhook.Key = webhook.ID
webhook.CreatedAt = time.Now().Unix()
webhook.UpdatedAt = time.Now().Unix()
webhookCollection := p.db.Collection(models.Collections.Webhook, options.Collection())
_, err := webhookCollection.InsertOne(ctx, webhook)
if err != nil {
return nil, err
}
return webhook.AsAPIWebhook(), nil
}
// UpdateWebhook to update webhook
func (p *provider) UpdateWebhook(ctx context.Context, webhook models.Webhook) (*model.Webhook, error) {
webhook.UpdatedAt = time.Now().Unix()
webhookCollection := p.db.Collection(models.Collections.Webhook, options.Collection())
_, err := webhookCollection.UpdateOne(ctx, bson.M{"_id": bson.M{"$eq": webhook.ID}}, bson.M{"$set": webhook}, options.MergeUpdateOptions())
if err != nil {
return nil, err
}
return webhook.AsAPIWebhook(), nil
}
// ListWebhooks to list webhook
func (p *provider) ListWebhook(ctx context.Context, pagination model.Pagination) (*model.Webhooks, error) {
var webhooks []*model.Webhook
opts := options.Find()
opts.SetLimit(pagination.Limit)
opts.SetSkip(pagination.Offset)
opts.SetSort(bson.M{"created_at": -1})
paginationClone := pagination
webhookCollection := p.db.Collection(models.Collections.Webhook, options.Collection())
count, err := webhookCollection.CountDocuments(ctx, bson.M{}, options.Count())
if err != nil {
return nil, err
}
paginationClone.Total = count
cursor, err := webhookCollection.Find(ctx, bson.M{}, opts)
if err != nil {
return nil, err
}
defer cursor.Close(ctx)
for cursor.Next(ctx) {
var webhook models.Webhook
err := cursor.Decode(&webhook)
if err != nil {
return nil, err
}
webhooks = append(webhooks, webhook.AsAPIWebhook())
}
return &model.Webhooks{
Pagination: &paginationClone,
Webhooks: webhooks,
}, nil
}
// GetWebhookByID to get webhook by id
func (p *provider) GetWebhookByID(ctx context.Context, webhookID string) (*model.Webhook, error) {
var webhook models.Webhook
webhookCollection := p.db.Collection(models.Collections.Webhook, options.Collection())
err := webhookCollection.FindOne(ctx, bson.M{"_id": webhookID}).Decode(&webhook)
if err != nil {
return nil, err
}
return webhook.AsAPIWebhook(), nil
}
// GetWebhookByEventName to get webhook by event_name
func (p *provider) GetWebhookByEventName(ctx context.Context, eventName string) (*model.Webhook, error) {
var webhook models.Webhook
webhookCollection := p.db.Collection(models.Collections.Webhook, options.Collection())
err := webhookCollection.FindOne(ctx, bson.M{"event_name": eventName}).Decode(&webhook)
if err != nil {
return nil, err
}
return webhook.AsAPIWebhook(), nil
}
// DeleteWebhook to delete webhook
func (p *provider) DeleteWebhook(ctx context.Context, webhook *model.Webhook) error {
webhookCollection := p.db.Collection(models.Collections.Webhook, options.Collection())
_, err := webhookCollection.DeleteOne(nil, bson.M{"_id": webhook.ID}, options.Delete())
if err != nil {
return err
}
webhookLogCollection := p.db.Collection(models.Collections.WebhookLog, options.Collection())
_, err = webhookLogCollection.DeleteMany(nil, bson.M{"webhook_id": webhook.ID}, options.Delete())
if err != nil {
return err
}
return nil
}

View File

@@ -0,0 +1,74 @@
package mongodb
import (
"context"
"time"
"github.com/authorizerdev/authorizer/server/db/models"
"github.com/authorizerdev/authorizer/server/graph/model"
"github.com/google/uuid"
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/mongo/options"
)
// AddWebhookLog to add webhook log
func (p *provider) AddWebhookLog(ctx context.Context, webhookLog models.WebhookLog) (*model.WebhookLog, error) {
if webhookLog.ID == "" {
webhookLog.ID = uuid.New().String()
}
webhookLog.Key = webhookLog.ID
webhookLog.CreatedAt = time.Now().Unix()
webhookLog.UpdatedAt = time.Now().Unix()
webhookLogCollection := p.db.Collection(models.Collections.WebhookLog, options.Collection())
_, err := webhookLogCollection.InsertOne(ctx, webhookLog)
if err != nil {
return nil, err
}
return webhookLog.AsAPIWebhookLog(), nil
}
// ListWebhookLogs to list webhook logs
func (p *provider) ListWebhookLogs(ctx context.Context, pagination model.Pagination, webhookID string) (*model.WebhookLogs, error) {
webhookLogs := []*model.WebhookLog{}
opts := options.Find()
opts.SetLimit(pagination.Limit)
opts.SetSkip(pagination.Offset)
opts.SetSort(bson.M{"created_at": -1})
paginationClone := pagination
query := bson.M{}
if webhookID != "" {
query = bson.M{"webhook_id": webhookID}
}
webhookLogCollection := p.db.Collection(models.Collections.WebhookLog, options.Collection())
count, err := webhookLogCollection.CountDocuments(ctx, query, options.Count())
if err != nil {
return nil, err
}
paginationClone.Total = count
cursor, err := webhookLogCollection.Find(ctx, query, opts)
if err != nil {
return nil, err
}
defer cursor.Close(ctx)
for cursor.Next(ctx) {
var webhookLog models.WebhookLog
err := cursor.Decode(&webhookLog)
if err != nil {
return nil, err
}
webhookLogs = append(webhookLogs, webhookLog.AsAPIWebhookLog())
}
return &model.WebhookLogs{
Pagination: &paginationClone,
WebhookLogs: webhookLogs,
}, nil
}

View File

@@ -1,6 +1,7 @@
package provider_template
import (
"context"
"time"
"github.com/authorizerdev/authorizer/server/db/models"
@@ -8,7 +9,7 @@ import (
)
// AddEnv to save environment information in database
func (p *provider) AddEnv(env models.Env) (models.Env, error) {
func (p *provider) AddEnv(ctx context.Context, env models.Env) (models.Env, error) {
if env.ID == "" {
env.ID = uuid.New().String()
}
@@ -19,13 +20,13 @@ func (p *provider) AddEnv(env models.Env) (models.Env, error) {
}
// UpdateEnv to update environment information in database
func (p *provider) UpdateEnv(env models.Env) (models.Env, error) {
func (p *provider) UpdateEnv(ctx context.Context, env models.Env) (models.Env, error) {
env.UpdatedAt = time.Now().Unix()
return env, nil
}
// GetEnv to get environment information from database
func (p *provider) GetEnv() (models.Env, error) {
func (p *provider) GetEnv(ctx context.Context) (models.Env, error) {
var env models.Env
return env, nil

View File

@@ -1,6 +1,7 @@
package provider_template
import (
"context"
"time"
"github.com/authorizerdev/authorizer/server/db/models"
@@ -8,7 +9,7 @@ import (
)
// AddSession to save session information in database
func (p *provider) AddSession(session models.Session) error {
func (p *provider) AddSession(ctx context.Context, session models.Session) error {
if session.ID == "" {
session.ID = uuid.New().String()
}
@@ -19,6 +20,6 @@ func (p *provider) AddSession(session models.Session) error {
}
// DeleteSession to delete session information from database
func (p *provider) DeleteSession(userId string) error {
func (p *provider) DeleteSession(ctx context.Context, userId string) error {
return nil
}

View File

@@ -1,6 +1,7 @@
package provider_template
import (
"context"
"time"
"github.com/authorizerdev/authorizer/server/constants"
@@ -11,7 +12,7 @@ import (
)
// AddUser to save user information in database
func (p *provider) AddUser(user models.User) (models.User, error) {
func (p *provider) AddUser(ctx context.Context, user models.User) (models.User, error) {
if user.ID == "" {
user.ID = uuid.New().String()
}
@@ -31,30 +32,30 @@ func (p *provider) AddUser(user models.User) (models.User, error) {
}
// UpdateUser to update user information in database
func (p *provider) UpdateUser(user models.User) (models.User, error) {
func (p *provider) UpdateUser(ctx context.Context, user models.User) (models.User, error) {
user.UpdatedAt = time.Now().Unix()
return user, nil
}
// DeleteUser to delete user information from database
func (p *provider) DeleteUser(user models.User) error {
func (p *provider) DeleteUser(ctx context.Context, user models.User) error {
return nil
}
// ListUsers to get list of users from database
func (p *provider) ListUsers(pagination model.Pagination) (*model.Users, error) {
func (p *provider) ListUsers(ctx context.Context, pagination model.Pagination) (*model.Users, error) {
return nil, nil
}
// GetUserByEmail to get user information from database using email address
func (p *provider) GetUserByEmail(email string) (models.User, error) {
func (p *provider) GetUserByEmail(ctx context.Context, email string) (models.User, error) {
var user models.User
return user, nil
}
// GetUserByID to get user information from database using user ID
func (p *provider) GetUserByID(id string) (models.User, error) {
func (p *provider) GetUserByID(ctx context.Context, id string) (models.User, error) {
var user models.User
return user, nil

View File

@@ -1,6 +1,7 @@
package provider_template
import (
"context"
"time"
"github.com/authorizerdev/authorizer/server/db/models"
@@ -9,7 +10,7 @@ import (
)
// AddVerification to save verification request in database
func (p *provider) AddVerificationRequest(verificationRequest models.VerificationRequest) (models.VerificationRequest, error) {
func (p *provider) AddVerificationRequest(ctx context.Context, verificationRequest models.VerificationRequest) (models.VerificationRequest, error) {
if verificationRequest.ID == "" {
verificationRequest.ID = uuid.New().String()
}
@@ -21,25 +22,25 @@ func (p *provider) AddVerificationRequest(verificationRequest models.Verificatio
}
// GetVerificationRequestByToken to get verification request from database using token
func (p *provider) GetVerificationRequestByToken(token string) (models.VerificationRequest, error) {
func (p *provider) GetVerificationRequestByToken(ctx context.Context, token string) (models.VerificationRequest, error) {
var verificationRequest models.VerificationRequest
return verificationRequest, nil
}
// GetVerificationRequestByEmail to get verification request by email from database
func (p *provider) GetVerificationRequestByEmail(email string, identifier string) (models.VerificationRequest, error) {
func (p *provider) GetVerificationRequestByEmail(ctx context.Context, email string, identifier string) (models.VerificationRequest, error) {
var verificationRequest models.VerificationRequest
return verificationRequest, nil
}
// ListVerificationRequests to get list of verification requests from database
func (p *provider) ListVerificationRequests(pagination model.Pagination) (*model.VerificationRequests, error) {
func (p *provider) ListVerificationRequests(ctx context.Context, pagination model.Pagination) (*model.VerificationRequests, error) {
return nil, nil
}
// DeleteVerificationRequest to delete verification request from database
func (p *provider) DeleteVerificationRequest(verificationRequest models.VerificationRequest) error {
func (p *provider) DeleteVerificationRequest(ctx context.Context, verificationRequest models.VerificationRequest) error {
return nil
}

View File

@@ -0,0 +1,49 @@
package provider_template
import (
"context"
"time"
"github.com/authorizerdev/authorizer/server/db/models"
"github.com/authorizerdev/authorizer/server/graph/model"
"github.com/google/uuid"
)
// AddWebhook to add webhook
func (p *provider) AddWebhook(ctx context.Context, webhook models.Webhook) (*model.Webhook, error) {
if webhook.ID == "" {
webhook.ID = uuid.New().String()
}
webhook.Key = webhook.ID
webhook.CreatedAt = time.Now().Unix()
webhook.UpdatedAt = time.Now().Unix()
return webhook.AsAPIWebhook(), nil
}
// UpdateWebhook to update webhook
func (p *provider) UpdateWebhook(ctx context.Context, webhook models.Webhook) (*model.Webhook, error) {
webhook.UpdatedAt = time.Now().Unix()
return webhook.AsAPIWebhook(), nil
}
// ListWebhooks to list webhook
func (p *provider) ListWebhook(ctx context.Context, pagination model.Pagination) (*model.Webhooks, error) {
return nil, nil
}
// GetWebhookByID to get webhook by id
func (p *provider) GetWebhookByID(ctx context.Context, webhookID string) (*model.Webhook, error) {
return nil, nil
}
// GetWebhookByEventName to get webhook by event_name
func (p *provider) GetWebhookByEventName(ctx context.Context, eventName string) (*model.Webhook, error) {
return nil, nil
}
// DeleteWebhook to delete webhook
func (p *provider) DeleteWebhook(ctx context.Context, webhook *model.Webhook) error {
// Also delete webhook logs for given webhook id
return nil
}

View File

@@ -0,0 +1,27 @@
package provider_template
import (
"context"
"time"
"github.com/authorizerdev/authorizer/server/db/models"
"github.com/authorizerdev/authorizer/server/graph/model"
"github.com/google/uuid"
)
// AddWebhookLog to add webhook log
func (p *provider) AddWebhookLog(ctx context.Context, webhookLog models.WebhookLog) (*model.WebhookLog, error) {
if webhookLog.ID == "" {
webhookLog.ID = uuid.New().String()
}
webhookLog.Key = webhookLog.ID
webhookLog.CreatedAt = time.Now().Unix()
webhookLog.UpdatedAt = time.Now().Unix()
return webhookLog.AsAPIWebhookLog(), nil
}
// ListWebhookLogs to list webhook logs
func (p *provider) ListWebhookLogs(ctx context.Context, pagination model.Pagination, webhookID string) (*model.WebhookLogs, error) {
return nil, nil
}

View File

@@ -1,44 +1,62 @@
package providers
import (
"context"
"github.com/authorizerdev/authorizer/server/db/models"
"github.com/authorizerdev/authorizer/server/graph/model"
)
type Provider interface {
// AddUser to save user information in database
AddUser(user models.User) (models.User, error)
AddUser(ctx context.Context, user models.User) (models.User, error)
// UpdateUser to update user information in database
UpdateUser(user models.User) (models.User, error)
UpdateUser(ctx context.Context, user models.User) (models.User, error)
// DeleteUser to delete user information from database
DeleteUser(user models.User) error
DeleteUser(ctx context.Context, user models.User) error
// ListUsers to get list of users from database
ListUsers(pagination model.Pagination) (*model.Users, error)
ListUsers(ctx context.Context, pagination model.Pagination) (*model.Users, error)
// GetUserByEmail to get user information from database using email address
GetUserByEmail(email string) (models.User, error)
GetUserByEmail(ctx context.Context, email string) (models.User, error)
// GetUserByID to get user information from database using user ID
GetUserByID(id string) (models.User, error)
GetUserByID(ctx context.Context, id string) (models.User, error)
// AddVerification to save verification request in database
AddVerificationRequest(verificationRequest models.VerificationRequest) (models.VerificationRequest, error)
AddVerificationRequest(ctx context.Context, verificationRequest models.VerificationRequest) (models.VerificationRequest, error)
// GetVerificationRequestByToken to get verification request from database using token
GetVerificationRequestByToken(token string) (models.VerificationRequest, error)
GetVerificationRequestByToken(ctx context.Context, token string) (models.VerificationRequest, error)
// GetVerificationRequestByEmail to get verification request by email from database
GetVerificationRequestByEmail(email string, identifier string) (models.VerificationRequest, error)
GetVerificationRequestByEmail(ctx context.Context, email string, identifier string) (models.VerificationRequest, error)
// ListVerificationRequests to get list of verification requests from database
ListVerificationRequests(pagination model.Pagination) (*model.VerificationRequests, error)
ListVerificationRequests(ctx context.Context, pagination model.Pagination) (*model.VerificationRequests, error)
// DeleteVerificationRequest to delete verification request from database
DeleteVerificationRequest(verificationRequest models.VerificationRequest) error
DeleteVerificationRequest(ctx context.Context, verificationRequest models.VerificationRequest) error
// AddSession to save session information in database
AddSession(session models.Session) error
// DeleteSession to delete session information from database
DeleteSession(userId string) error
AddSession(ctx context.Context, session models.Session) error
// AddEnv to save environment information in database
AddEnv(env models.Env) (models.Env, error)
AddEnv(ctx context.Context, env models.Env) (models.Env, error)
// UpdateEnv to update environment information in database
UpdateEnv(env models.Env) (models.Env, error)
UpdateEnv(ctx context.Context, env models.Env) (models.Env, error)
// GetEnv to get environment information from database
GetEnv() (models.Env, error)
GetEnv(ctx context.Context) (models.Env, error)
// AddWebhook to add webhook
AddWebhook(ctx context.Context, webhook models.Webhook) (*model.Webhook, error)
// UpdateWebhook to update webhook
UpdateWebhook(ctx context.Context, webhook models.Webhook) (*model.Webhook, error)
// ListWebhooks to list webhook
ListWebhook(ctx context.Context, pagination model.Pagination) (*model.Webhooks, error)
// GetWebhookByID to get webhook by id
GetWebhookByID(ctx context.Context, webhookID string) (*model.Webhook, error)
// GetWebhookByEventName to get webhook by event_name
GetWebhookByEventName(ctx context.Context, eventName string) (*model.Webhook, error)
// DeleteWebhook to delete webhook
DeleteWebhook(ctx context.Context, webhook *model.Webhook) error
// AddWebhookLog to add webhook log
AddWebhookLog(ctx context.Context, webhookLog models.WebhookLog) (*model.WebhookLog, error)
// ListWebhookLogs to list webhook logs
ListWebhookLogs(ctx context.Context, pagination model.Pagination, webhookID string) (*model.WebhookLogs, error)
}

View File

@@ -1,6 +1,7 @@
package sql
import (
"context"
"time"
"github.com/authorizerdev/authorizer/server/db/models"
@@ -8,7 +9,7 @@ import (
)
// AddEnv to save environment information in database
func (p *provider) AddEnv(env models.Env) (models.Env, error) {
func (p *provider) AddEnv(ctx context.Context, env models.Env) (models.Env, error) {
if env.ID == "" {
env.ID = uuid.New().String()
}
@@ -25,7 +26,7 @@ func (p *provider) AddEnv(env models.Env) (models.Env, error) {
}
// UpdateEnv to update environment information in database
func (p *provider) UpdateEnv(env models.Env) (models.Env, error) {
func (p *provider) UpdateEnv(ctx context.Context, env models.Env) (models.Env, error) {
env.UpdatedAt = time.Now().Unix()
result := p.db.Save(&env)
@@ -36,7 +37,7 @@ func (p *provider) UpdateEnv(env models.Env) (models.Env, error) {
}
// GetEnv to get environment information from database
func (p *provider) GetEnv() (models.Env, error) {
func (p *provider) GetEnv(ctx context.Context) (models.Env, error) {
var env models.Env
result := p.db.First(&env)

View File

@@ -46,11 +46,11 @@ func NewProvider() (*provider, error) {
dbURL := memorystore.RequiredEnvStoreObj.GetRequiredEnv().DatabaseURL
switch dbType {
case constants.DbTypePostgres, constants.DbTypeYugabyte:
case constants.DbTypePostgres, constants.DbTypeYugabyte, constants.DbTypeCockroachDB:
sqlDB, err = gorm.Open(postgres.Open(dbURL), ormConfig)
case constants.DbTypeSqlite:
sqlDB, err = gorm.Open(sqlite.Open(dbURL), ormConfig)
case constants.DbTypeMysql, constants.DbTypeMariaDB:
case constants.DbTypeMysql, constants.DbTypeMariaDB, constants.DbTypePlanetScaleDB:
sqlDB, err = gorm.Open(mysql.Open(dbURL), ormConfig)
case constants.DbTypeSqlserver:
sqlDB, err = gorm.Open(sqlserver.Open(dbURL), ormConfig)
@@ -60,7 +60,7 @@ func NewProvider() (*provider, error) {
return nil, err
}
err = sqlDB.AutoMigrate(&models.User{}, &models.VerificationRequest{}, &models.Session{}, &models.Env{})
err = sqlDB.AutoMigrate(&models.User{}, &models.VerificationRequest{}, &models.Session{}, &models.Env{}, &models.Webhook{}, models.WebhookLog{})
if err != nil {
return nil, err
}

View File

@@ -1,6 +1,7 @@
package sql
import (
"context"
"time"
"github.com/authorizerdev/authorizer/server/db/models"
@@ -9,7 +10,7 @@ import (
)
// AddSession to save session information in database
func (p *provider) AddSession(session models.Session) error {
func (p *provider) AddSession(ctx context.Context, session models.Session) error {
if session.ID == "" {
session.ID = uuid.New().String()
}
@@ -26,13 +27,3 @@ func (p *provider) AddSession(session models.Session) error {
}
return nil
}
// DeleteSession to delete session information from database
func (p *provider) DeleteSession(userId string) error {
result := p.db.Where("user_id = ?", userId).Delete(&models.Session{})
if result.Error != nil {
return result.Error
}
return nil
}

View File

@@ -1,6 +1,7 @@
package sql
import (
"context"
"time"
"github.com/authorizerdev/authorizer/server/constants"
@@ -12,7 +13,7 @@ import (
)
// AddUser to save user information in database
func (p *provider) AddUser(user models.User) (models.User, error) {
func (p *provider) AddUser(ctx context.Context, user models.User) (models.User, error) {
if user.ID == "" {
user.ID = uuid.New().String()
}
@@ -42,7 +43,7 @@ func (p *provider) AddUser(user models.User) (models.User, error) {
}
// UpdateUser to update user information in database
func (p *provider) UpdateUser(user models.User) (models.User, error) {
func (p *provider) UpdateUser(ctx context.Context, user models.User) (models.User, error) {
user.UpdatedAt = time.Now().Unix()
result := p.db.Save(&user)
@@ -55,18 +56,23 @@ func (p *provider) UpdateUser(user models.User) (models.User, error) {
}
// DeleteUser to delete user information from database
func (p *provider) DeleteUser(user models.User) error {
func (p *provider) DeleteUser(ctx context.Context, user models.User) error {
result := p.db.Delete(&user)
if result.Error != nil {
return result.Error
}
result = p.db.Where("user_id = ?", user.ID).Delete(&models.Session{})
if result.Error != nil {
return result.Error
}
return nil
}
// ListUsers to get list of users from database
func (p *provider) ListUsers(pagination model.Pagination) (*model.Users, error) {
func (p *provider) ListUsers(ctx context.Context, pagination model.Pagination) (*model.Users, error) {
var users []models.User
result := p.db.Limit(int(pagination.Limit)).Offset(int(pagination.Offset)).Order("created_at DESC").Find(&users)
if result.Error != nil {
@@ -94,10 +100,9 @@ func (p *provider) ListUsers(pagination model.Pagination) (*model.Users, error)
}
// GetUserByEmail to get user information from database using email address
func (p *provider) GetUserByEmail(email string) (models.User, error) {
func (p *provider) GetUserByEmail(ctx context.Context, email string) (models.User, error) {
var user models.User
result := p.db.Where("email = ?", email).First(&user)
if result.Error != nil {
return user, result.Error
}
@@ -106,11 +111,10 @@ func (p *provider) GetUserByEmail(email string) (models.User, error) {
}
// GetUserByID to get user information from database using user ID
func (p *provider) GetUserByID(id string) (models.User, error) {
func (p *provider) GetUserByID(ctx context.Context, id string) (models.User, error) {
var user models.User
result := p.db.Where("id = ?", id).First(&user)
if result.Error != nil {
return user, result.Error
}

View File

@@ -1,6 +1,7 @@
package sql
import (
"context"
"time"
"github.com/authorizerdev/authorizer/server/db/models"
@@ -10,7 +11,7 @@ import (
)
// AddVerification to save verification request in database
func (p *provider) AddVerificationRequest(verificationRequest models.VerificationRequest) (models.VerificationRequest, error) {
func (p *provider) AddVerificationRequest(ctx context.Context, verificationRequest models.VerificationRequest) (models.VerificationRequest, error) {
if verificationRequest.ID == "" {
verificationRequest.ID = uuid.New().String()
}
@@ -31,7 +32,7 @@ func (p *provider) AddVerificationRequest(verificationRequest models.Verificatio
}
// GetVerificationRequestByToken to get verification request from database using token
func (p *provider) GetVerificationRequestByToken(token string) (models.VerificationRequest, error) {
func (p *provider) GetVerificationRequestByToken(ctx context.Context, token string) (models.VerificationRequest, error) {
var verificationRequest models.VerificationRequest
result := p.db.Where("token = ?", token).First(&verificationRequest)
@@ -43,7 +44,7 @@ func (p *provider) GetVerificationRequestByToken(token string) (models.Verificat
}
// GetVerificationRequestByEmail to get verification request by email from database
func (p *provider) GetVerificationRequestByEmail(email string, identifier string) (models.VerificationRequest, error) {
func (p *provider) GetVerificationRequestByEmail(ctx context.Context, email string, identifier string) (models.VerificationRequest, error) {
var verificationRequest models.VerificationRequest
result := p.db.Where("email = ? AND identifier = ?", email, identifier).First(&verificationRequest)
@@ -56,7 +57,7 @@ func (p *provider) GetVerificationRequestByEmail(email string, identifier string
}
// ListVerificationRequests to get list of verification requests from database
func (p *provider) ListVerificationRequests(pagination model.Pagination) (*model.VerificationRequests, error) {
func (p *provider) ListVerificationRequests(ctx context.Context, pagination model.Pagination) (*model.VerificationRequests, error) {
var verificationRequests []models.VerificationRequest
result := p.db.Limit(int(pagination.Limit)).Offset(int(pagination.Offset)).Order("created_at DESC").Find(&verificationRequests)
@@ -85,7 +86,7 @@ func (p *provider) ListVerificationRequests(pagination model.Pagination) (*model
}
// DeleteVerificationRequest to delete verification request from database
func (p *provider) DeleteVerificationRequest(verificationRequest models.VerificationRequest) error {
func (p *provider) DeleteVerificationRequest(ctx context.Context, verificationRequest models.VerificationRequest) error {
result := p.db.Delete(&verificationRequest)
if result.Error != nil {

View File

@@ -0,0 +1,104 @@
package sql
import (
"context"
"time"
"github.com/authorizerdev/authorizer/server/db/models"
"github.com/authorizerdev/authorizer/server/graph/model"
"github.com/google/uuid"
)
// AddWebhook to add webhook
func (p *provider) AddWebhook(ctx context.Context, webhook models.Webhook) (*model.Webhook, error) {
if webhook.ID == "" {
webhook.ID = uuid.New().String()
}
webhook.Key = webhook.ID
webhook.CreatedAt = time.Now().Unix()
webhook.UpdatedAt = time.Now().Unix()
res := p.db.Create(&webhook)
if res.Error != nil {
return nil, res.Error
}
return webhook.AsAPIWebhook(), nil
}
// UpdateWebhook to update webhook
func (p *provider) UpdateWebhook(ctx context.Context, webhook models.Webhook) (*model.Webhook, error) {
webhook.UpdatedAt = time.Now().Unix()
result := p.db.Save(&webhook)
if result.Error != nil {
return nil, result.Error
}
return webhook.AsAPIWebhook(), nil
}
// ListWebhooks to list webhook
func (p *provider) ListWebhook(ctx context.Context, pagination model.Pagination) (*model.Webhooks, error) {
var webhooks []models.Webhook
result := p.db.Limit(int(pagination.Limit)).Offset(int(pagination.Offset)).Order("created_at DESC").Find(&webhooks)
if result.Error != nil {
return nil, result.Error
}
var total int64
totalRes := p.db.Model(&models.Webhook{}).Count(&total)
if totalRes.Error != nil {
return nil, totalRes.Error
}
paginationClone := pagination
paginationClone.Total = total
responseWebhooks := []*model.Webhook{}
for _, w := range webhooks {
responseWebhooks = append(responseWebhooks, w.AsAPIWebhook())
}
return &model.Webhooks{
Pagination: &paginationClone,
Webhooks: responseWebhooks,
}, nil
}
// GetWebhookByID to get webhook by id
func (p *provider) GetWebhookByID(ctx context.Context, webhookID string) (*model.Webhook, error) {
var webhook models.Webhook
result := p.db.Where("id = ?", webhookID).First(&webhook)
if result.Error != nil {
return nil, result.Error
}
return webhook.AsAPIWebhook(), nil
}
// GetWebhookByEventName to get webhook by event_name
func (p *provider) GetWebhookByEventName(ctx context.Context, eventName string) (*model.Webhook, error) {
var webhook models.Webhook
result := p.db.Where("event_name = ?", eventName).First(&webhook)
if result.Error != nil {
return nil, result.Error
}
return webhook.AsAPIWebhook(), nil
}
// DeleteWebhook to delete webhook
func (p *provider) DeleteWebhook(ctx context.Context, webhook *model.Webhook) error {
result := p.db.Delete(&models.Webhook{
ID: webhook.ID,
})
if result.Error != nil {
return result.Error
}
result = p.db.Where("webhook_id = ?", webhook.ID).Delete(&models.WebhookLog{})
if result.Error != nil {
return result.Error
}
return nil
}

View File

@@ -0,0 +1,68 @@
package sql
import (
"context"
"time"
"github.com/authorizerdev/authorizer/server/db/models"
"github.com/authorizerdev/authorizer/server/graph/model"
"github.com/google/uuid"
"gorm.io/gorm"
"gorm.io/gorm/clause"
)
// AddWebhookLog to add webhook log
func (p *provider) AddWebhookLog(ctx context.Context, webhookLog models.WebhookLog) (*model.WebhookLog, error) {
if webhookLog.ID == "" {
webhookLog.ID = uuid.New().String()
}
webhookLog.Key = webhookLog.ID
webhookLog.CreatedAt = time.Now().Unix()
webhookLog.UpdatedAt = time.Now().Unix()
res := p.db.Clauses(
clause.OnConflict{
DoNothing: true,
}).Create(&webhookLog)
if res.Error != nil {
return nil, res.Error
}
return webhookLog.AsAPIWebhookLog(), nil
}
// ListWebhookLogs to list webhook logs
func (p *provider) ListWebhookLogs(ctx context.Context, pagination model.Pagination, webhookID string) (*model.WebhookLogs, error) {
var webhookLogs []models.WebhookLog
var result *gorm.DB
var totalRes *gorm.DB
var total int64
if webhookID != "" {
result = p.db.Where("webhook_id = ?", webhookID).Limit(int(pagination.Limit)).Offset(int(pagination.Offset)).Order("created_at DESC").Find(&webhookLogs)
totalRes = p.db.Where("webhook_id = ?", webhookID).Model(&models.WebhookLog{}).Count(&total)
} else {
result = p.db.Limit(int(pagination.Limit)).Offset(int(pagination.Offset)).Order("created_at DESC").Find(&webhookLogs)
totalRes = p.db.Model(&models.WebhookLog{}).Count(&total)
}
if result.Error != nil {
return nil, result.Error
}
if totalRes.Error != nil {
return nil, totalRes.Error
}
paginationClone := pagination
paginationClone.Total = total
responseWebhookLogs := []*model.WebhookLog{}
for _, w := range webhookLogs {
responseWebhookLogs = append(responseWebhookLogs, w.AsAPIWebhookLog())
}
return &model.WebhookLogs{
WebhookLogs: responseWebhookLogs,
Pagination: &paginationClone,
}, nil
}

View File

@@ -37,7 +37,7 @@ func SendMail(to []string, Subject, bodyMessage string) error {
if err != nil {
return err
}
if envKey == "test" {
if envKey == constants.TestEnv {
return nil
}
m := gomail.NewMessage()

46
server/env/env.go vendored
View File

@@ -68,6 +68,10 @@ func InitAllEnv() error {
osGithubClientSecret := os.Getenv(constants.EnvKeyGithubClientSecret)
osFacebookClientID := os.Getenv(constants.EnvKeyFacebookClientID)
osFacebookClientSecret := os.Getenv(constants.EnvKeyFacebookClientSecret)
osLinkedInClientID := os.Getenv(constants.EnvKeyLinkedInClientID)
osLinkedInClientSecret := os.Getenv(constants.EnvKeyLinkedInClientSecret)
osAppleClientID := os.Getenv(constants.EnvKeyAppleClientID)
osAppleClientSecret := os.Getenv(constants.EnvKeyAppleClientSecret)
osResetPasswordURL := os.Getenv(constants.EnvKeyResetPasswordURL)
osOrganizationName := os.Getenv(constants.EnvKeyOrganizationName)
osOrganizationLogo := os.Getenv(constants.EnvKeyOrganizationLogo)
@@ -79,6 +83,7 @@ func InitAllEnv() error {
osDisableLoginPage := os.Getenv(constants.EnvKeyDisableLoginPage)
osDisableSignUp := os.Getenv(constants.EnvKeyDisableSignUp)
osDisableRedisForEnv := os.Getenv(constants.EnvKeyDisableRedisForEnv)
osDisableStrongPassword := os.Getenv(constants.EnvKeyDisableStrongPassword)
// os slice vars
osAllowedOrigins := os.Getenv(constants.EnvKeyAllowedOrigins)
@@ -345,6 +350,34 @@ func InitAllEnv() error {
envData[constants.EnvKeyFacebookClientSecret] = osFacebookClientSecret
}
if val, ok := envData[constants.EnvKeyLinkedInClientID]; !ok || val == "" {
envData[constants.EnvKeyLinkedInClientID] = osLinkedInClientID
}
if osFacebookClientID != "" && envData[constants.EnvKeyLinkedInClientID] != osFacebookClientID {
envData[constants.EnvKeyLinkedInClientID] = osLinkedInClientID
}
if val, ok := envData[constants.EnvKeyLinkedInClientSecret]; !ok || val == "" {
envData[constants.EnvKeyLinkedInClientSecret] = osLinkedInClientSecret
}
if osFacebookClientSecret != "" && envData[constants.EnvKeyLinkedInClientSecret] != osFacebookClientSecret {
envData[constants.EnvKeyLinkedInClientSecret] = osLinkedInClientSecret
}
if val, ok := envData[constants.EnvKeyAppleClientID]; !ok || val == "" {
envData[constants.EnvKeyAppleClientID] = osAppleClientID
}
if osFacebookClientID != "" && envData[constants.EnvKeyAppleClientID] != osFacebookClientID {
envData[constants.EnvKeyAppleClientID] = osAppleClientID
}
if val, ok := envData[constants.EnvKeyAppleClientSecret]; !ok || val == "" {
envData[constants.EnvKeyAppleClientSecret] = osAppleClientSecret
}
if osFacebookClientSecret != "" && envData[constants.EnvKeyAppleClientSecret] != osFacebookClientSecret {
envData[constants.EnvKeyAppleClientSecret] = osAppleClientSecret
}
if val, ok := envData[constants.EnvKeyResetPasswordURL]; !ok || val == "" {
envData[constants.EnvKeyResetPasswordURL] = strings.TrimPrefix(osResetPasswordURL, "/")
}
@@ -444,6 +477,19 @@ func InitAllEnv() error {
}
}
if _, ok := envData[constants.EnvKeyDisableStrongPassword]; !ok {
envData[constants.EnvKeyDisableStrongPassword] = osDisableStrongPassword == "true"
}
if osDisableStrongPassword != "" {
boolValue, err := strconv.ParseBool(osDisableStrongPassword)
if err != nil {
return err
}
if boolValue != envData[constants.EnvKeyDisableStrongPassword].(bool) {
envData[constants.EnvKeyDisableStrongPassword] = boolValue
}
}
// no need to add nil check as its already done above
if envData[constants.EnvKeySmtpHost] == "" || envData[constants.EnvKeySmtpUsername] == "" || envData[constants.EnvKeySmtpPassword] == "" || envData[constants.EnvKeySenderEmail] == "" && envData[constants.EnvKeySmtpPort] == "" {
envData[constants.EnvKeyDisableEmailVerification] = true

View File

@@ -1,6 +1,7 @@
package env
import (
"context"
"encoding/json"
"os"
"reflect"
@@ -58,7 +59,8 @@ func fixBackwardCompatibility(data map[string]interface{}) (bool, map[string]int
// GetEnvData returns the env data from database
func GetEnvData() (map[string]interface{}, error) {
var result map[string]interface{}
env, err := db.Provider.GetEnv()
ctx := context.Background()
env, err := db.Provider.GetEnv(ctx)
// config not found in db
if err != nil {
log.Debug("Error while getting env data from db: ", err)
@@ -108,9 +110,10 @@ func GetEnvData() (map[string]interface{}, error) {
// PersistEnv persists the environment variables to the database
func PersistEnv() error {
env, err := db.Provider.GetEnv()
ctx := context.Background()
env, err := db.Provider.GetEnv(ctx)
// config not found in db
if err != nil {
if err != nil || env.EnvData == "" {
// AES encryption needs 32 bit key only, so we chop off last 4 characters from 36 bit uuid
hash := uuid.New().String()[:36-4]
err := memorystore.Provider.UpdateEnvVariable(constants.EnvKeyEncryptionKey, hash)
@@ -137,7 +140,7 @@ func PersistEnv() error {
EnvData: encryptedConfig,
}
env, err = db.Provider.AddEnv(env)
env, err = db.Provider.AddEnv(ctx, env)
if err != nil {
log.Debug("Error while persisting env data to db: ", err)
return err
@@ -171,7 +174,7 @@ func PersistEnv() error {
err = json.Unmarshal(decryptedConfigs, &storeData)
if err != nil {
log.Debug("Error while unmarshalling env data: ", err)
log.Debug("Error while un-marshalling env data: ", err)
return err
}
@@ -198,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:
case constants.EnvKeyIsProd, constants.EnvKeyDisableBasicAuthentication, constants.EnvKeyDisableEmailVerification, constants.EnvKeyDisableLoginPage, constants.EnvKeyDisableMagicLinkLogin, constants.EnvKeyDisableSignUp, constants.EnvKeyDisableRedisForEnv, constants.EnvKeyDisableStrongPassword:
if envValueBool, err := strconv.ParseBool(envValue); err == nil {
if value.(bool) != envValueBool {
storeData[key] = envValueBool
@@ -251,7 +254,7 @@ func PersistEnv() error {
}
env.EnvData = encryptedConfig
_, err = db.Provider.UpdateEnv(env)
_, err = db.Provider.UpdateEnv(ctx, env)
if err != nil {
log.Debug("Failed to Update Config: ", err)
return err

File diff suppressed because it is too large Load Diff

View File

@@ -2,6 +2,13 @@
package model
type AddWebhookRequest struct {
EventName string `json:"event_name"`
Endpoint string `json:"endpoint"`
Enabled bool `json:"enabled"`
Headers map[string]interface{} `json:"headers"`
}
type AdminLoginInput struct {
AdminSecret string `json:"admin_secret"`
}
@@ -55,6 +62,7 @@ type Env struct {
DisableLoginPage bool `json:"DISABLE_LOGIN_PAGE"`
DisableSignUp bool `json:"DISABLE_SIGN_UP"`
DisableRedisForEnv bool `json:"DISABLE_REDIS_FOR_ENV"`
DisableStrongPassword bool `json:"DISABLE_STRONG_PASSWORD"`
Roles []string `json:"ROLES"`
ProtectedRoles []string `json:"PROTECTED_ROLES"`
DefaultRoles []string `json:"DEFAULT_ROLES"`
@@ -65,6 +73,10 @@ type Env struct {
GithubClientSecret *string `json:"GITHUB_CLIENT_SECRET"`
FacebookClientID *string `json:"FACEBOOK_CLIENT_ID"`
FacebookClientSecret *string `json:"FACEBOOK_CLIENT_SECRET"`
LinkedinClientID *string `json:"LINKEDIN_CLIENT_ID"`
LinkedinClientSecret *string `json:"LINKEDIN_CLIENT_SECRET"`
AppleClientID *string `json:"APPLE_CLIENT_ID"`
AppleClientSecret *string `json:"APPLE_CLIENT_SECRET"`
OrganizationName *string `json:"ORGANIZATION_NAME"`
OrganizationLogo *string `json:"ORGANIZATION_LOGO"`
}
@@ -95,6 +107,11 @@ type InviteMemberInput struct {
RedirectURI *string `json:"redirect_uri"`
}
type ListWebhookLogRequest struct {
Pagination *PaginationInput `json:"pagination"`
WebhookID *string `json:"webhook_id"`
}
type LoginInput struct {
Email string `json:"email"`
Password string `json:"password"`
@@ -116,10 +133,13 @@ type Meta struct {
IsGoogleLoginEnabled bool `json:"is_google_login_enabled"`
IsFacebookLoginEnabled bool `json:"is_facebook_login_enabled"`
IsGithubLoginEnabled bool `json:"is_github_login_enabled"`
IsLinkedinLoginEnabled bool `json:"is_linkedin_login_enabled"`
IsAppleLoginEnabled bool `json:"is_apple_login_enabled"`
IsEmailVerificationEnabled bool `json:"is_email_verification_enabled"`
IsBasicAuthenticationEnabled bool `json:"is_basic_authentication_enabled"`
IsMagicLinkLoginEnabled bool `json:"is_magic_link_login_enabled"`
IsSignUpEnabled bool `json:"is_sign_up_enabled"`
IsStrongPasswordEnabled bool `json:"is_strong_password_enabled"`
}
type OAuthRevokeInput struct {
@@ -179,6 +199,17 @@ type SignUpInput struct {
RedirectURI *string `json:"redirect_uri"`
}
type TestEndpointRequest struct {
Endpoint string `json:"endpoint"`
EventName string `json:"event_name"`
Headers map[string]interface{} `json:"headers"`
}
type TestEndpointResponse struct {
HTTPStatus *int64 `json:"http_status"`
Response map[string]interface{} `json:"response"`
}
type UpdateAccessInput struct {
UserID string `json:"user_id"`
}
@@ -206,6 +237,7 @@ type UpdateEnvInput struct {
DisableLoginPage *bool `json:"DISABLE_LOGIN_PAGE"`
DisableSignUp *bool `json:"DISABLE_SIGN_UP"`
DisableRedisForEnv *bool `json:"DISABLE_REDIS_FOR_ENV"`
DisableStrongPassword *bool `json:"DISABLE_STRONG_PASSWORD"`
Roles []string `json:"ROLES"`
ProtectedRoles []string `json:"PROTECTED_ROLES"`
DefaultRoles []string `json:"DEFAULT_ROLES"`
@@ -216,6 +248,10 @@ type UpdateEnvInput struct {
GithubClientSecret *string `json:"GITHUB_CLIENT_SECRET"`
FacebookClientID *string `json:"FACEBOOK_CLIENT_ID"`
FacebookClientSecret *string `json:"FACEBOOK_CLIENT_SECRET"`
LinkedinClientID *string `json:"LINKEDIN_CLIENT_ID"`
LinkedinClientSecret *string `json:"LINKEDIN_CLIENT_SECRET"`
AppleClientID *string `json:"APPLE_CLIENT_ID"`
AppleClientSecret *string `json:"APPLE_CLIENT_SECRET"`
OrganizationName *string `json:"ORGANIZATION_NAME"`
OrganizationLogo *string `json:"ORGANIZATION_LOGO"`
}
@@ -250,6 +286,14 @@ type UpdateUserInput struct {
Roles []*string `json:"roles"`
}
type UpdateWebhookRequest struct {
ID string `json:"id"`
EventName *string `json:"event_name"`
Endpoint *string `json:"endpoint"`
Enabled *bool `json:"enabled"`
Headers map[string]interface{} `json:"headers"`
}
type User struct {
ID string `json:"id"`
Email string `json:"email"`
@@ -306,3 +350,37 @@ type VerificationRequests struct {
type VerifyEmailInput struct {
Token string `json:"token"`
}
type Webhook struct {
ID string `json:"id"`
EventName *string `json:"event_name"`
Endpoint *string `json:"endpoint"`
Enabled *bool `json:"enabled"`
Headers map[string]interface{} `json:"headers"`
CreatedAt *int64 `json:"created_at"`
UpdatedAt *int64 `json:"updated_at"`
}
type WebhookLog struct {
ID string `json:"id"`
HTTPStatus *int64 `json:"http_status"`
Response *string `json:"response"`
Request *string `json:"request"`
WebhookID *string `json:"webhook_id"`
CreatedAt *int64 `json:"created_at"`
UpdatedAt *int64 `json:"updated_at"`
}
type WebhookLogs struct {
Pagination *Pagination `json:"pagination"`
WebhookLogs []*WebhookLog `json:"webhook_logs"`
}
type WebhookRequest struct {
ID string `json:"id"`
}
type Webhooks struct {
Pagination *Pagination `json:"pagination"`
Webhooks []*Webhook `json:"webhooks"`
}

View File

@@ -18,10 +18,13 @@ type Meta {
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!
}
type User {
@@ -118,6 +121,7 @@ type Env {
DISABLE_LOGIN_PAGE: Boolean!
DISABLE_SIGN_UP: Boolean!
DISABLE_REDIS_FOR_ENV: Boolean!
DISABLE_STRONG_PASSWORD: Boolean!
ROLES: [String!]
PROTECTED_ROLES: [String!]
DEFAULT_ROLES: [String!]
@@ -128,6 +132,10 @@ type Env {
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
}
@@ -165,6 +173,7 @@ input UpdateEnvInput {
DISABLE_LOGIN_PAGE: Boolean
DISABLE_SIGN_UP: Boolean
DISABLE_REDIS_FOR_ENV: Boolean
DISABLE_STRONG_PASSWORD: Boolean
ROLES: [String!]
PROTECTED_ROLES: [String!]
DEFAULT_ROLES: [String!]
@@ -175,6 +184,10 @@ input UpdateEnvInput {
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
}
@@ -311,6 +324,71 @@ input GenerateJWTKeysInput {
type: String!
}
type Webhook {
id: ID!
event_name: String
endpoint: String
enabled: Boolean
headers: Map
created_at: Int64
updated_at: Int64
}
type Webhooks {
pagination: Pagination!
webhooks: [Webhook!]!
}
type WebhookLog {
id: ID!
http_status: Int64
response: String
request: String
webhook_id: ID
created_at: Int64
updated_at: Int64
}
type TestEndpointResponse {
http_status: Int64
response: Map
}
input ListWebhookLogRequest {
pagination: PaginationInput
webhook_id: String
}
input AddWebhookRequest {
event_name: String!
endpoint: String!
enabled: Boolean!
headers: Map
}
input UpdateWebhookRequest {
id: ID!
event_name: String
endpoint: String
enabled: Boolean
headers: Map
}
input WebhookRequest {
id: ID!
}
input TestEndpointRequest {
endpoint: String!
event_name: String!
headers: Map
}
type WebhookLogs {
pagination: Pagination!
webhook_logs: [WebhookLog!]!
}
type Mutation {
signup(params: SignUpInput!): AuthResponse!
login(params: LoginInput!): AuthResponse!
@@ -333,6 +411,10 @@ type Mutation {
_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!
}
type Query {
@@ -345,4 +427,7 @@ type Query {
_verification_requests(params: PaginatedInput): VerificationRequests!
_admin_session: Response!
_env: Env!
_webhook(params: WebhookRequest!): Webhook!
_webhooks(params: PaginatedInput): Webhooks!
_webhook_logs(params: ListWebhookLogRequest): WebhookLogs!
}

View File

@@ -91,6 +91,22 @@ func (r *mutationResolver) GenerateJwtKeys(ctx context.Context, params model.Gen
return resolvers.GenerateJWTKeysResolver(ctx, params)
}
func (r *mutationResolver) AddWebhook(ctx context.Context, params model.AddWebhookRequest) (*model.Response, error) {
return resolvers.AddWebhookResolver(ctx, params)
}
func (r *mutationResolver) UpdateWebhook(ctx context.Context, params model.UpdateWebhookRequest) (*model.Response, error) {
return resolvers.UpdateWebhookResolver(ctx, params)
}
func (r *mutationResolver) DeleteWebhook(ctx context.Context, params model.WebhookRequest) (*model.Response, error) {
return resolvers.DeleteWebhookResolver(ctx, params)
}
func (r *mutationResolver) TestEndpoint(ctx context.Context, params model.TestEndpointRequest) (*model.TestEndpointResponse, error) {
return resolvers.TestEndpointResolver(ctx, params)
}
func (r *queryResolver) Meta(ctx context.Context) (*model.Meta, error) {
return resolvers.MetaResolver(ctx)
}
@@ -123,6 +139,18 @@ func (r *queryResolver) Env(ctx context.Context) (*model.Env, error) {
return resolvers.EnvResolver(ctx)
}
func (r *queryResolver) Webhook(ctx context.Context, params model.WebhookRequest) (*model.Webhook, error) {
return resolvers.WebhookResolver(ctx, params)
}
func (r *queryResolver) Webhooks(ctx context.Context, params *model.PaginatedInput) (*model.Webhooks, error) {
return resolvers.WebhooksResolver(ctx, params)
}
func (r *queryResolver) WebhookLogs(ctx context.Context, params *model.ListWebhookLogRequest) (*model.WebhookLogs, error) {
return resolvers.WebhookLogsResolver(ctx, params)
}
// Mutation returns generated.MutationResolver implementation.
func (r *Resolver) Mutation() generated.MutationResolver { return &mutationResolver{r} }

View File

@@ -199,7 +199,7 @@ func AuthorizeHandler() gin.HandlerFunc {
return
}
userID := claims.Subject
user, err := db.Provider.GetUserByID(userID)
user, err := db.Provider.GetUserByID(gc, userID)
if err != nil {
if isQuery {
gc.Redirect(http.StatusFound, loginURL)
@@ -218,16 +218,18 @@ func AuthorizeHandler() gin.HandlerFunc {
return
}
sessionKey := user.ID
if claims.LoginMethod != "" {
sessionKey = claims.LoginMethod + ":" + user.ID
}
// if user is logged in
// based on the response type, generate the response
// based on the response type code, generate the response
if isResponseTypeCode {
// rollover the session for security
err = memorystore.Provider.RemoveState(sessionToken)
if err != nil {
log.Debug("Failed to remove state: ", err)
}
go memorystore.Provider.DeleteUserSession(sessionKey, claims.Nonce)
nonce := uuid.New().String()
newSessionTokenData, newSessionToken, err := token.CreateSessionToken(user, nonce, claims.Roles, scope)
newSessionTokenData, newSessionToken, err := token.CreateSessionToken(user, nonce, claims.Roles, scope, claims.LoginMethod)
if err != nil {
if isQuery {
gc.Redirect(http.StatusFound, loginURL)
@@ -246,7 +248,7 @@ func AuthorizeHandler() gin.HandlerFunc {
return
}
memorystore.Provider.SetState(newSessionToken, newSessionTokenData.Nonce+"@"+user.ID)
memorystore.Provider.SetUserSession(user.ID, constants.TokenTypeSessionToken+"_"+newSessionTokenData.Nonce, newSessionToken)
cookie.SetSession(gc, newSessionToken)
code := uuid.New().String()
memorystore.Provider.SetState(codeChallenge, code+"@"+newSessionToken)
@@ -265,7 +267,7 @@ func AuthorizeHandler() gin.HandlerFunc {
if isResponseTypeToken {
// rollover the session for security
authToken, err := token.CreateAuthToken(gc, user, claims.Roles, scope)
authToken, err := token.CreateAuthToken(gc, user, claims.Roles, scope, claims.LoginMethod)
if err != nil {
if isQuery {
gc.Redirect(http.StatusFound, loginURL)
@@ -283,9 +285,10 @@ func AuthorizeHandler() gin.HandlerFunc {
}
return
}
memorystore.Provider.RemoveState(sessionToken)
memorystore.Provider.SetState(authToken.FingerPrintHash, authToken.FingerPrint+"@"+user.ID)
memorystore.Provider.SetState(authToken.AccessToken.Token, authToken.FingerPrint+"@"+user.ID)
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)
expiresIn := authToken.AccessToken.ExpiresAt - time.Now().Unix()
@@ -308,7 +311,7 @@ func AuthorizeHandler() gin.HandlerFunc {
if authToken.RefreshToken != nil {
res["refresh_token"] = authToken.RefreshToken.Token
params += "&refresh_token=" + authToken.RefreshToken.Token
memorystore.Provider.SetState(authToken.RefreshToken.Token, authToken.FingerPrint+"@"+user.ID)
memorystore.Provider.SetUserSession(sessionKey, constants.TokenTypeRefreshToken+"_"+authToken.FingerPrint, authToken.RefreshToken.Token)
}
if isQuery {

View File

@@ -1,6 +1,7 @@
package handlers
import (
"encoding/json"
"net/http"
"strings"
@@ -10,6 +11,7 @@ import (
"github.com/authorizerdev/authorizer/server/cookie"
"github.com/authorizerdev/authorizer/server/crypto"
"github.com/authorizerdev/authorizer/server/memorystore"
"github.com/authorizerdev/authorizer/server/token"
)
// Handler to logout user
@@ -35,12 +37,17 @@ func LogoutHandler() gin.HandlerFunc {
return
}
fingerPrint := string(decryptedFingerPrint)
err = memorystore.Provider.RemoveState(fingerPrint)
var sessionData token.SessionData
err = json.Unmarshal([]byte(decryptedFingerPrint), &sessionData)
if err != nil {
log.Debug("Failed to remove state: ", err)
log.Debug("Failed to decrypt fingerprint: ", err)
gc.JSON(http.StatusUnauthorized, gin.H{
"error": err.Error(),
})
return
}
memorystore.Provider.DeleteUserSession(sessionData.Subject, sessionData.Nonce)
cookie.DeleteSession(gc)
if redirectURL != "" {

View File

@@ -2,6 +2,7 @@ package handlers
import (
"context"
"encoding/base64"
"encoding/json"
"fmt"
"io/ioutil"
@@ -27,39 +28,45 @@ import (
// OAuthCallbackHandler handles the OAuth callback for various oauth providers
func OAuthCallbackHandler() gin.HandlerFunc {
return func(c *gin.Context) {
provider := c.Param("oauth_provider")
state := c.Request.FormValue("state")
return func(ctx *gin.Context) {
provider := ctx.Param("oauth_provider")
state := ctx.Request.FormValue("state")
sessionState, err := memorystore.Provider.GetState(state)
if sessionState == "" || err != nil {
log.Debug("Invalid oauth state: ", state)
c.JSON(400, gin.H{"error": "invalid oauth state"})
ctx.JSON(400, gin.H{"error": "invalid oauth state"})
}
memorystore.Provider.GetState(state)
// contains random token, redirect url, role
sessionSplit := strings.Split(state, "___")
if len(sessionSplit) < 3 {
log.Debug("Unable to get redirect url from state: ", state)
c.JSON(400, gin.H{"error": "invalid redirect url"})
ctx.JSON(400, gin.H{"error": "invalid redirect url"})
return
}
// remove state from store
go memorystore.Provider.RemoveState(state)
stateValue := sessionSplit[0]
redirectURL := sessionSplit[1]
inputRoles := strings.Split(sessionSplit[2], ",")
scopes := strings.Split(sessionSplit[3], ",")
user := models.User{}
code := c.Request.FormValue("code")
code := ctx.Request.FormValue("code")
switch provider {
case constants.SignupMethodGoogle:
case constants.AuthRecipeMethodGoogle:
user, err = processGoogleUserInfo(code)
case constants.SignupMethodGithub:
case constants.AuthRecipeMethodGithub:
user, err = processGithubUserInfo(code)
case constants.SignupMethodFacebook:
case constants.AuthRecipeMethodFacebook:
user, err = processFacebookUserInfo(code)
case constants.AuthRecipeMethodLinkedIn:
user, err = processLinkedInUserInfo(code)
case constants.AuthRecipeMethodApple:
user, err = processAppleUserInfo(code)
default:
log.Info("Invalid oauth provider")
err = fmt.Errorf(`invalid oauth provider`)
@@ -67,23 +74,24 @@ func OAuthCallbackHandler() gin.HandlerFunc {
if err != nil {
log.Debug("Failed to process user info: ", err)
c.JSON(400, gin.H{"error": err.Error()})
ctx.JSON(400, gin.H{"error": err.Error()})
return
}
existingUser, err := db.Provider.GetUserByEmail(user.Email)
existingUser, err := db.Provider.GetUserByEmail(ctx, user.Email)
log := log.WithField("user", user.Email)
isSignUp := false
if err != nil {
isSignupDisabled, err := memorystore.Provider.GetBoolStoreEnvVariable(constants.EnvKeyDisableSignUp)
if err != nil {
log.Debug("Failed to get signup disabled env variable: ", err)
c.JSON(400, gin.H{"error": err.Error()})
ctx.JSON(400, gin.H{"error": err.Error()})
return
}
if isSignupDisabled {
log.Debug("Failed to signup as disabled")
c.JSON(400, gin.H{"error": "signup is disabled for this instance"})
ctx.JSON(400, gin.H{"error": "signup is disabled for this instance"})
return
}
// user not registered, register user and generate session token
@@ -106,18 +114,21 @@ func OAuthCallbackHandler() gin.HandlerFunc {
if hasProtectedRole {
log.Debug("Signup is not allowed with protected roles:", inputRoles)
c.JSON(400, gin.H{"error": "invalid role"})
ctx.JSON(400, gin.H{"error": "invalid role"})
return
}
user.Roles = strings.Join(inputRoles, ",")
now := time.Now().Unix()
user.EmailVerifiedAt = &now
user, _ = db.Provider.AddUser(user)
user, _ = db.Provider.AddUser(ctx, user)
isSignUp = true
} else {
user = existingUser
if user.RevokedTimestamp != nil {
log.Debug("User access revoked at: ", user.RevokedTimestamp)
c.JSON(400, gin.H{"error": "user access has been revoked"})
ctx.JSON(400, gin.H{"error": "user access has been revoked"})
return
}
// user exists in db, check if method was google
@@ -126,7 +137,6 @@ func OAuthCallbackHandler() gin.HandlerFunc {
if !strings.Contains(signupMethod, provider) {
signupMethod = signupMethod + "," + provider
}
user = existingUser
user.SignupMethods = signupMethod
if user.EmailVerifiedAt == nil {
@@ -167,7 +177,7 @@ func OAuthCallbackHandler() gin.HandlerFunc {
if hasProtectedRole {
log.Debug("Invalid role. User is using protected unassigned role")
c.JSON(400, gin.H{"error": "invalid role"})
ctx.JSON(400, gin.H{"error": "invalid role"})
return
} else {
user.Roles = existingUser.Roles + "," + strings.Join(unasignedRoles, ",")
@@ -176,18 +186,18 @@ func OAuthCallbackHandler() gin.HandlerFunc {
user.Roles = existingUser.Roles
}
user, err = db.Provider.UpdateUser(user)
user, err = db.Provider.UpdateUser(ctx, user)
if err != nil {
log.Debug("Failed to update user: ", err)
c.JSON(500, gin.H{"error": err.Error()})
ctx.JSON(500, gin.H{"error": err.Error()})
return
}
}
authToken, err := token.CreateAuthToken(c, user, inputRoles, scopes)
authToken, err := token.CreateAuthToken(ctx, user, inputRoles, scopes, provider)
if err != nil {
log.Debug("Failed to create auth token: ", err)
c.JSON(500, gin.H{"error": err.Error()})
ctx.JSON(500, gin.H{"error": err.Error()})
}
expiresIn := authToken.AccessToken.ExpiresAt - time.Now().Unix()
@@ -197,27 +207,35 @@ func OAuthCallbackHandler() gin.HandlerFunc {
params := "access_token=" + authToken.AccessToken.Token + "&token_type=bearer&expires_in=" + strconv.FormatInt(expiresIn, 10) + "&state=" + stateValue + "&id_token=" + authToken.IDToken.Token
cookie.SetSession(c, authToken.FingerPrintHash)
memorystore.Provider.SetState(authToken.FingerPrintHash, authToken.FingerPrint+"@"+user.ID)
memorystore.Provider.SetState(authToken.AccessToken.Token, authToken.FingerPrint+"@"+user.ID)
sessionKey := provider + ":" + user.ID
cookie.SetSession(ctx, authToken.FingerPrintHash)
memorystore.Provider.SetUserSession(sessionKey, constants.TokenTypeSessionToken+"_"+authToken.FingerPrint, authToken.FingerPrintHash)
memorystore.Provider.SetUserSession(sessionKey, constants.TokenTypeAccessToken+"_"+authToken.FingerPrint, authToken.AccessToken.Token)
if authToken.RefreshToken != nil {
params = params + `&refresh_token=` + authToken.RefreshToken.Token
memorystore.Provider.SetState(authToken.RefreshToken.Token, authToken.FingerPrint+"@"+user.ID)
memorystore.Provider.SetUserSession(sessionKey, constants.TokenTypeRefreshToken+"_"+authToken.FingerPrint, authToken.RefreshToken.Token)
}
go db.Provider.AddSession(models.Session{
UserID: user.ID,
UserAgent: utils.GetUserAgent(c.Request),
IP: utils.GetIP(c.Request),
})
go func() {
if isSignUp {
utils.RegisterEvent(ctx, constants.UserSignUpWebhookEvent, provider, user)
} else {
utils.RegisterEvent(ctx, constants.UserLoginWebhookEvent, provider, user)
}
db.Provider.AddSession(ctx, models.Session{
UserID: user.ID,
UserAgent: utils.GetUserAgent(ctx.Request),
IP: utils.GetIP(ctx.Request),
})
}()
if strings.Contains(redirectURL, "?") {
redirectURL = redirectURL + "&" + params
} else {
redirectURL = redirectURL + "?" + params
redirectURL = redirectURL + "?" + strings.TrimPrefix(params, "&")
}
c.Redirect(http.StatusTemporaryRedirect, redirectURL)
ctx.Redirect(http.StatusFound, redirectURL)
}
}
@@ -256,7 +274,7 @@ func processGoogleUserInfo(code string) (models.User, error) {
func processGithubUserInfo(code string) (models.User, error) {
user := models.User{}
token, err := oauth.OAuthProviders.GithubConfig.Exchange(oauth2.NoContext, code)
oauth2Token, err := oauth.OAuthProviders.GithubConfig.Exchange(oauth2.NoContext, code)
if err != nil {
log.Debug("Failed to exchange code for token: ", err)
return user, fmt.Errorf("invalid github exchange code: %s", err.Error())
@@ -268,7 +286,7 @@ func processGithubUserInfo(code string) (models.User, error) {
return user, fmt.Errorf("error creating github user info request: %s", err.Error())
}
req.Header = http.Header{
"Authorization": []string{fmt.Sprintf("token %s", token.AccessToken)},
"Authorization": []string{fmt.Sprintf("token %s", oauth2Token.AccessToken)},
}
response, err := client.Do(req)
@@ -283,6 +301,10 @@ func processGithubUserInfo(code string) (models.User, error) {
log.Debug("Failed to read github user info response body: ", err)
return user, fmt.Errorf("failed to read github response body: %s", err.Error())
}
if response.StatusCode >= 400 {
log.Debug("Failed to request github user info: ", string(body))
return user, fmt.Errorf("failed to request github user info: %s", string(body))
}
userRawData := make(map[string]string)
json.Unmarshal(body, &userRawData)
@@ -311,13 +333,13 @@ func processGithubUserInfo(code string) (models.User, error) {
func processFacebookUserInfo(code string) (models.User, error) {
user := models.User{}
token, err := oauth.OAuthProviders.FacebookConfig.Exchange(oauth2.NoContext, code)
oauth2Token, err := oauth.OAuthProviders.FacebookConfig.Exchange(oauth2.NoContext, code)
if err != nil {
log.Debug("Invalid facebook exchange code: ", err)
return user, fmt.Errorf("invalid facebook exchange code: %s", err.Error())
}
client := http.Client{}
req, err := http.NewRequest("GET", constants.FacebookUserInfoURL+token.AccessToken, nil)
req, err := http.NewRequest("GET", constants.FacebookUserInfoURL+oauth2Token.AccessToken, nil)
if err != nil {
log.Debug("Error creating facebook user info request: ", err)
return user, fmt.Errorf("error creating facebook user info request: %s", err.Error())
@@ -335,7 +357,10 @@ func processFacebookUserInfo(code string) (models.User, error) {
log.Debug("Failed to read facebook response: ", err)
return user, fmt.Errorf("failed to read facebook response body: %s", err.Error())
}
if response.StatusCode >= 400 {
log.Debug("Failed to request facebook user info: ", string(body))
return user, fmt.Errorf("failed to request facebook user info: %s", string(body))
}
userRawData := make(map[string]interface{})
json.Unmarshal(body, &userRawData)
@@ -356,3 +381,138 @@ func processFacebookUserInfo(code string) (models.User, error) {
return user, nil
}
func processLinkedInUserInfo(code string) (models.User, error) {
user := models.User{}
oauth2Token, err := oauth.OAuthProviders.LinkedInConfig.Exchange(oauth2.NoContext, code)
if err != nil {
log.Debug("Failed to exchange code for token: ", err)
return user, fmt.Errorf("invalid linkedin exchange code: %s", err.Error())
}
client := http.Client{}
req, err := http.NewRequest("GET", constants.LinkedInUserInfoURL, nil)
if err != nil {
log.Debug("Failed to create linkedin user info request: ", err)
return user, fmt.Errorf("error creating linkedin 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 linkedin user info: ", err)
return user, err
}
defer response.Body.Close()
body, err := ioutil.ReadAll(response.Body)
if err != nil {
log.Debug("Failed to read linkedin user info response body: ", err)
return user, fmt.Errorf("failed to read linkedin response body: %s", err.Error())
}
if response.StatusCode >= 400 {
log.Debug("Failed to request linkedin user info: ", string(body))
return user, fmt.Errorf("failed to request linkedin user info: %s", string(body))
}
userRawData := make(map[string]interface{})
json.Unmarshal(body, &userRawData)
req, err = http.NewRequest("GET", constants.LinkedInEmailURL, nil)
if err != nil {
log.Debug("Failed to create linkedin email info request: ", err)
return user, fmt.Errorf("error creating linkedin 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 linkedin email info: ", err)
return user, err
}
defer response.Body.Close()
body, err = ioutil.ReadAll(response.Body)
if err != nil {
log.Debug("Failed to read linkedin email info response body: ", err)
return user, fmt.Errorf("failed to read linkedin email response body: %s", err.Error())
}
if response.StatusCode >= 400 {
log.Debug("Failed to request linkedin user info: ", string(body))
return user, fmt.Errorf("failed to request linkedin user info: %s", string(body))
}
emailRawData := make(map[string]interface{})
json.Unmarshal(body, &emailRawData)
firstName := userRawData["localizedFirstName"].(string)
lastName := userRawData["localizedLastName"].(string)
profilePicture := userRawData["profilePicture"].(map[string]interface{})["displayImage~"].(map[string]interface{})["elements"].([]interface{})[0].(map[string]interface{})["identifiers"].([]interface{})[0].(map[string]interface{})["identifier"].(string)
emailAddress := emailRawData["elements"].([]interface{})[0].(map[string]interface{})["handle~"].(map[string]interface{})["emailAddress"].(string)
user = models.User{
GivenName: &firstName,
FamilyName: &lastName,
Picture: &profilePicture,
Email: emailAddress,
}
return user, nil
}
func processAppleUserInfo(code string) (models.User, error) {
user := models.User{}
oauth2Token, err := oauth.OAuthProviders.AppleConfig.Exchange(oauth2.NoContext, code)
if err != nil {
log.Debug("Failed to exchange code for token: ", err)
return user, fmt.Errorf("invalid apple exchange code: %s", err.Error())
}
// Extract the ID Token from OAuth2 token.
rawIDToken, ok := oauth2Token.Extra("id_token").(string)
if !ok {
log.Debug("Failed to extract ID Token from OAuth2 token")
return user, fmt.Errorf("unable to extract id_token")
}
tokenSplit := strings.Split(rawIDToken, ".")
claimsData := tokenSplit[1]
decodedClaimsData, err := base64.RawURLEncoding.DecodeString(claimsData)
if err != nil {
log.Debugf("Failed to decrypt claims %s: %s", claimsData, err.Error())
return user, fmt.Errorf("failed to decrypt claims data: %s", err.Error())
}
claims := make(map[string]interface{})
err = json.Unmarshal(decodedClaimsData, &claims)
if err != nil {
log.Debug("Failed to unmarshal claims data: ", err)
return user, fmt.Errorf("failed to unmarshal claims data: %s", err.Error())
}
if val, ok := claims["email"]; !ok {
log.Debug("Failed to extract email from claims.")
return user, fmt.Errorf("unable to extract email, please check the scopes enabled for your app. It needs `email`, `name` scopes")
} else {
user.Email = val.(string)
}
if val, ok := claims["name"]; ok {
nameData := val.(map[string]interface{})
if nameVal, ok := nameData["firstName"]; ok {
givenName := nameVal.(string)
user.GivenName = &givenName
}
if nameVal, ok := nameData["lastName"]; ok {
familyName := nameVal.(string)
user.FamilyName = &familyName
}
}
return user, err
}

View File

@@ -6,6 +6,7 @@ import (
"github.com/gin-gonic/gin"
log "github.com/sirupsen/logrus"
"golang.org/x/oauth2"
"github.com/authorizerdev/authorizer/server/constants"
"github.com/authorizerdev/authorizer/server/memorystore"
@@ -99,13 +100,13 @@ func OAuthLoginHandler() gin.HandlerFunc {
provider := c.Param("oauth_provider")
isProviderConfigured := true
switch provider {
case constants.SignupMethodGoogle:
case constants.AuthRecipeMethodGoogle:
if oauth.OAuthProviders.GoogleConfig == nil {
log.Debug("Google OAuth provider is not configured")
isProviderConfigured = false
break
}
err := memorystore.Provider.SetState(oauthStateString, constants.SignupMethodGoogle)
err := memorystore.Provider.SetState(oauthStateString, constants.AuthRecipeMethodGoogle)
if err != nil {
log.Debug("Error setting state: ", err)
c.JSON(500, gin.H{
@@ -114,16 +115,16 @@ func OAuthLoginHandler() gin.HandlerFunc {
return
}
// during the init of OAuthProvider authorizer url might be empty
oauth.OAuthProviders.GoogleConfig.RedirectURL = hostname + "/oauth_callback/google"
oauth.OAuthProviders.GoogleConfig.RedirectURL = hostname + "/oauth_callback/" + constants.AuthRecipeMethodGoogle
url := oauth.OAuthProviders.GoogleConfig.AuthCodeURL(oauthStateString)
c.Redirect(http.StatusTemporaryRedirect, url)
case constants.SignupMethodGithub:
case constants.AuthRecipeMethodGithub:
if oauth.OAuthProviders.GithubConfig == nil {
log.Debug("Github OAuth provider is not configured")
isProviderConfigured = false
break
}
err := memorystore.Provider.SetState(oauthStateString, constants.SignupMethodGithub)
err := memorystore.Provider.SetState(oauthStateString, constants.AuthRecipeMethodGithub)
if err != nil {
log.Debug("Error setting state: ", err)
c.JSON(500, gin.H{
@@ -131,16 +132,16 @@ func OAuthLoginHandler() gin.HandlerFunc {
})
return
}
oauth.OAuthProviders.GithubConfig.RedirectURL = hostname + "/oauth_callback/github"
oauth.OAuthProviders.GithubConfig.RedirectURL = hostname + "/oauth_callback/" + constants.AuthRecipeMethodGithub
url := oauth.OAuthProviders.GithubConfig.AuthCodeURL(oauthStateString)
c.Redirect(http.StatusTemporaryRedirect, url)
case constants.SignupMethodFacebook:
case constants.AuthRecipeMethodFacebook:
if oauth.OAuthProviders.FacebookConfig == nil {
log.Debug("Facebook OAuth provider is not configured")
isProviderConfigured = false
break
}
err := memorystore.Provider.SetState(oauthStateString, constants.SignupMethodFacebook)
err := memorystore.Provider.SetState(oauthStateString, constants.AuthRecipeMethodFacebook)
if err != nil {
log.Debug("Error setting state: ", err)
c.JSON(500, gin.H{
@@ -148,9 +149,45 @@ func OAuthLoginHandler() gin.HandlerFunc {
})
return
}
oauth.OAuthProviders.FacebookConfig.RedirectURL = hostname + "/oauth_callback/facebook"
oauth.OAuthProviders.FacebookConfig.RedirectURL = hostname + "/oauth_callback/" + constants.AuthRecipeMethodFacebook
url := oauth.OAuthProviders.FacebookConfig.AuthCodeURL(oauthStateString)
c.Redirect(http.StatusTemporaryRedirect, url)
case constants.AuthRecipeMethodLinkedIn:
if oauth.OAuthProviders.LinkedInConfig == nil {
log.Debug("Linkedin OAuth provider is not configured")
isProviderConfigured = false
break
}
err := memorystore.Provider.SetState(oauthStateString, constants.AuthRecipeMethodLinkedIn)
if err != nil {
log.Debug("Error setting state: ", err)
c.JSON(500, gin.H{
"error": "internal server error",
})
return
}
oauth.OAuthProviders.LinkedInConfig.RedirectURL = hostname + "/oauth_callback/" + constants.AuthRecipeMethodLinkedIn
url := oauth.OAuthProviders.LinkedInConfig.AuthCodeURL(oauthStateString)
c.Redirect(http.StatusTemporaryRedirect, url)
case constants.AuthRecipeMethodApple:
if oauth.OAuthProviders.AppleConfig == nil {
log.Debug("Apple OAuth provider is not configured")
isProviderConfigured = false
break
}
err := memorystore.Provider.SetState(oauthStateString, constants.AuthRecipeMethodApple)
if err != nil {
log.Debug("Error setting state: ", err)
c.JSON(500, gin.H{
"error": "internal server error",
})
return
}
oauth.OAuthProviders.AppleConfig.RedirectURL = hostname + "/oauth_callback/" + constants.AuthRecipeMethodApple
// there is scope encoding issue with oauth2 and how apple expects, hence added scope manually
// check: https://github.com/golang/oauth2/issues/449
url := oauth.OAuthProviders.AppleConfig.AuthCodeURL(oauthStateString, oauth2.SetAuthURLParam("response_mode", "form_post")) + "&scope=name email"
c.Redirect(http.StatusTemporaryRedirect, url)
default:
log.Debug("Invalid oauth provider: ", provider)
c.JSON(422, gin.H{

View File

@@ -9,10 +9,11 @@ import (
"github.com/authorizerdev/authorizer/server/constants"
"github.com/authorizerdev/authorizer/server/memorystore"
"github.com/authorizerdev/authorizer/server/token"
)
// Revoke handler to revoke refresh token
func RevokeHandler() gin.HandlerFunc {
// RevokeRefreshTokenHandler handler to revoke refresh token
func RevokeRefreshTokenHandler() gin.HandlerFunc {
return func(gc *gin.Context) {
var reqBody map[string]string
if err := gc.BindJSON(&reqBody); err != nil {
@@ -45,7 +46,24 @@ func RevokeHandler() gin.HandlerFunc {
return
}
memorystore.Provider.RemoveState(refreshToken)
claims, err := token.ParseJWTToken(refreshToken)
if err != nil {
log.Debug("Client ID is invalid: ", clientID)
gc.JSON(http.StatusBadRequest, gin.H{
"error": err.Error(),
"error_description": "Failed to parse jwt",
})
return
}
userID := claims["sub"].(string)
loginMethod := claims["login_method"]
sessionToken := userID
if loginMethod != nil && loginMethod != "" {
sessionToken = loginMethod.(string) + ":" + userID
}
memorystore.Provider.DeleteUserSession(sessionToken, claims["nonce"].(string))
gc.JSON(http.StatusOK, gin.H{
"message": "Token revoked successfully",

View File

@@ -72,6 +72,9 @@ func TokenHandler() gin.HandlerFunc {
var userID string
var roles, scope []string
loginMethod := ""
sessionKey := ""
if isAuthorizationCodeGrant {
if codeVerifier == "" {
@@ -107,6 +110,7 @@ func TokenHandler() gin.HandlerFunc {
return
}
go memorystore.Provider.RemoveState(encryptedCode)
// split session data
// it contains code@sessiontoken
sessionDataSplit := strings.Split(sessionData, "@")
@@ -130,11 +134,16 @@ func TokenHandler() gin.HandlerFunc {
})
return
}
// rollover the session for security
memorystore.Provider.RemoveState(sessionDataSplit[1])
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
if refreshToken == "" {
@@ -154,6 +163,7 @@ func TokenHandler() gin.HandlerFunc {
})
}
userID = claims["sub"].(string)
loginMethod := claims["login_method"]
rolesInterface := claims["roles"].([]interface{})
scopeInterface := claims["scope"].([]interface{})
for _, v := range rolesInterface {
@@ -162,11 +172,25 @@ func TokenHandler() gin.HandlerFunc {
for _, v := range scopeInterface {
scope = append(scope, v.(string))
}
sessionKey = userID
if loginMethod != nil && loginMethod != "" {
sessionKey = loginMethod.(string) + ":" + sessionKey
}
// remove older refresh token and rotate it for security
memorystore.Provider.RemoveState(refreshToken)
go memorystore.Provider.DeleteUserSession(sessionKey, claims["nonce"].(string))
}
user, err := db.Provider.GetUserByID(userID)
if sessionKey == "" {
log.Debug("Error getting sessionKey: ", sessionKey, loginMethod)
gc.JSON(http.StatusUnauthorized, gin.H{
"error": "unauthorized",
"error_description": "User not found",
})
return
}
user, err := db.Provider.GetUserByID(gc, userID)
if err != nil {
log.Debug("Error getting user: ", err)
gc.JSON(http.StatusUnauthorized, gin.H{
@@ -176,7 +200,7 @@ func TokenHandler() gin.HandlerFunc {
return
}
authToken, err := token.CreateAuthToken(gc, user, roles, scope)
authToken, err := token.CreateAuthToken(gc, user, roles, scope, loginMethod)
if err != nil {
log.Debug("Error creating auth token: ", err)
gc.JSON(http.StatusUnauthorized, gin.H{
@@ -185,8 +209,8 @@ func TokenHandler() gin.HandlerFunc {
})
return
}
memorystore.Provider.SetState(authToken.FingerPrintHash, authToken.FingerPrint+"@"+user.ID)
memorystore.Provider.SetState(authToken.AccessToken.Token, authToken.FingerPrint+"@"+user.ID)
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)
expiresIn := authToken.AccessToken.ExpiresAt - time.Now().Unix()
@@ -204,7 +228,7 @@ func TokenHandler() gin.HandlerFunc {
if authToken.RefreshToken != nil {
res["refresh_token"] = authToken.RefreshToken.Token
memorystore.Provider.SetState(authToken.RefreshToken.Token, authToken.FingerPrint+"@"+user.ID)
memorystore.Provider.SetUserSession(sessionKey, constants.TokenTypeRefreshToken+"_"+authToken.FingerPrint, authToken.RefreshToken.Token)
}
gc.JSON(http.StatusOK, res)

View File

@@ -31,7 +31,7 @@ func UserInfoHandler() gin.HandlerFunc {
}
userID := claims["sub"].(string)
user, err := db.Provider.GetUserByID(userID)
user, err := db.Provider.GetUserByID(gc, userID)
if err != nil {
log.Debug("Error getting user: ", err)
gc.JSON(http.StatusUnauthorized, gin.H{

View File

@@ -9,6 +9,7 @@ import (
"github.com/gin-gonic/gin"
log "github.com/sirupsen/logrus"
"github.com/authorizerdev/authorizer/server/constants"
"github.com/authorizerdev/authorizer/server/cookie"
"github.com/authorizerdev/authorizer/server/db"
"github.com/authorizerdev/authorizer/server/db/models"
@@ -32,7 +33,7 @@ func VerifyEmailHandler() gin.HandlerFunc {
return
}
verificationRequest, err := db.Provider.GetVerificationRequestByToken(tokenInQuery)
verificationRequest, err := db.Provider.GetVerificationRequestByToken(c, tokenInQuery)
if err != nil {
log.Debug("Error getting verification request: ", err)
errorRes["error_description"] = err.Error()
@@ -42,7 +43,7 @@ func VerifyEmailHandler() gin.HandlerFunc {
// verify if token exists in db
hostname := parsers.GetHost(c)
claim, err := token.ParseJWTToken(tokenInQuery, hostname, verificationRequest.Nonce, verificationRequest.Email)
claim, err := token.ParseJWTToken(tokenInQuery)
if err != nil {
log.Debug("Error parsing token: ", err)
errorRes["error_description"] = err.Error()
@@ -50,7 +51,14 @@ func VerifyEmailHandler() gin.HandlerFunc {
return
}
user, err := db.Provider.GetUserByEmail(claim["sub"].(string))
if ok, err := token.ValidateJWTClaims(claim, hostname, verificationRequest.Nonce, verificationRequest.Email); !ok || err != nil {
log.Debug("Error validating jwt claims: ", err)
errorRes["error_description"] = err.Error()
c.JSON(400, errorRes)
return
}
user, err := db.Provider.GetUserByEmail(c, verificationRequest.Email)
if err != nil {
log.Debug("Error getting user: ", err)
errorRes["error_description"] = err.Error()
@@ -58,14 +66,16 @@ func VerifyEmailHandler() gin.HandlerFunc {
return
}
isSignUp := false
// update email_verified_at in users table
if user.EmailVerifiedAt == nil {
now := time.Now().Unix()
user.EmailVerifiedAt = &now
db.Provider.UpdateUser(user)
isSignUp = true
db.Provider.UpdateUser(c, user)
}
// delete from verification table
db.Provider.DeleteVerificationRequest(verificationRequest)
db.Provider.DeleteVerificationRequest(c, verificationRequest)
state := strings.TrimSpace(c.Query("state"))
redirectURL := strings.TrimSpace(c.Query("redirect_uri"))
@@ -84,7 +94,11 @@ func VerifyEmailHandler() gin.HandlerFunc {
} else {
scope = strings.Split(scopeString, " ")
}
authToken, err := token.CreateAuthToken(c, user, roles, scope)
loginMethod := constants.AuthRecipeMethodBasicAuth
if verificationRequest.Identifier == constants.VerificationTypeMagicLinkLogin {
loginMethod = constants.AuthRecipeMethodMagicLinkLogin
}
authToken, err := token.CreateAuthToken(c, user, roles, scope, loginMethod)
if err != nil {
log.Debug("Error creating auth token: ", err)
errorRes["error_description"] = err.Error()
@@ -99,13 +113,14 @@ func VerifyEmailHandler() gin.HandlerFunc {
params := "access_token=" + authToken.AccessToken.Token + "&token_type=bearer&expires_in=" + strconv.FormatInt(expiresIn, 10) + "&state=" + state + "&id_token=" + authToken.IDToken.Token
sessionKey := loginMethod + ":" + user.ID
cookie.SetSession(c, authToken.FingerPrintHash)
memorystore.Provider.SetState(authToken.FingerPrintHash, authToken.FingerPrint+"@"+user.ID)
memorystore.Provider.SetState(authToken.AccessToken.Token, authToken.FingerPrint+"@"+user.ID)
memorystore.Provider.SetUserSession(sessionKey, constants.TokenTypeSessionToken+"_"+authToken.FingerPrint, authToken.FingerPrintHash)
memorystore.Provider.SetUserSession(sessionKey, constants.TokenTypeAccessToken+"_"+authToken.FingerPrint, authToken.AccessToken.Token)
if authToken.RefreshToken != nil {
params = params + `&refresh_token=${refresh_token}`
memorystore.Provider.SetState(authToken.RefreshToken.Token, authToken.FingerPrint+"@"+user.ID)
params = params + `&refresh_token=` + authToken.RefreshToken.Token
memorystore.Provider.SetUserSession(sessionKey, constants.TokenTypeRefreshToken+"_"+authToken.FingerPrint, authToken.RefreshToken.Token)
}
if redirectURL == "" {
@@ -115,14 +130,22 @@ func VerifyEmailHandler() gin.HandlerFunc {
if strings.Contains(redirectURL, "?") {
redirectURL = redirectURL + "&" + params
} else {
redirectURL = redirectURL + "?" + params
redirectURL = redirectURL + "?" + strings.TrimPrefix(params, "&")
}
go db.Provider.AddSession(models.Session{
UserID: user.ID,
UserAgent: utils.GetUserAgent(c.Request),
IP: utils.GetIP(c.Request),
})
go func() {
if isSignUp {
utils.RegisterEvent(c, constants.UserSignUpWebhookEvent, loginMethod, user)
} else {
utils.RegisterEvent(c, constants.UserLoginWebhookEvent, loginMethod, user)
}
db.Provider.AddSession(c, models.Session{
UserID: user.ID,
UserAgent: utils.GetUserAgent(c.Request),
IP: utils.GetIP(c.Request),
})
}()
c.Redirect(http.StatusTemporaryRedirect, redirectURL)
}

View File

@@ -30,6 +30,7 @@ func InitMemStore() error {
constants.EnvKeyDisableEmailVerification: false,
constants.EnvKeyDisableLoginPage: false,
constants.EnvKeyDisableSignUp: false,
constants.EnvKeyDisableStrongPassword: false,
}
requiredEnvs := RequiredEnvStoreObj.GetRequiredEnv()

View File

@@ -2,24 +2,23 @@ package inmemory
import (
"sync"
"github.com/authorizerdev/authorizer/server/memorystore/providers/inmemory/stores"
)
type provider struct {
mutex sync.Mutex
sessionStore map[string]map[string]string
stateStore map[string]string
envStore *EnvStore
sessionStore *stores.SessionStore
stateStore *stores.StateStore
envStore *stores.EnvStore
}
// NewInMemoryStore returns a new in-memory store.
func NewInMemoryProvider() (*provider, error) {
return &provider{
mutex: sync.Mutex{},
sessionStore: map[string]map[string]string{},
stateStore: map[string]string{},
envStore: &EnvStore{
mutex: sync.Mutex{},
store: map[string]interface{}{},
},
envStore: stores.NewEnvStore(),
sessionStore: stores.NewSessionStore(),
stateStore: stores.NewStateStore(),
}, nil
}

View File

@@ -3,85 +3,76 @@ package inmemory
import (
"fmt"
"os"
"strings"
"github.com/authorizerdev/authorizer/server/constants"
)
// ClearStore clears the in-memory store.
func (c *provider) ClearStore() error {
if os.Getenv("ENV") != "test" {
c.mutex.Lock()
defer c.mutex.Unlock()
}
c.sessionStore = map[string]map[string]string{}
// SetUserSession sets the user session
func (c *provider) SetUserSession(userId, key, token string) error {
c.sessionStore.Set(userId, key, token)
return nil
}
// GetUserSessions returns all the user session token from the in-memory store.
func (c *provider) GetUserSessions(userId string) map[string]string {
if os.Getenv("ENV") != "test" {
c.mutex.Lock()
defer c.mutex.Unlock()
}
res := map[string]string{}
for k, v := range c.stateStore {
split := strings.Split(v, "@")
if split[1] == userId {
res[k] = split[0]
}
}
return res
// GetAllUserSessions returns all the user sessions token from the in-memory store.
func (c *provider) GetAllUserSessions(userId string) (map[string]string, error) {
data := c.sessionStore.GetAll(userId)
return data, nil
}
// DeleteAllUserSession deletes all the user sessions from in-memory store.
func (c *provider) DeleteAllUserSession(userId string) error {
if os.Getenv("ENV") != "test" {
c.mutex.Lock()
defer c.mutex.Unlock()
}
sessions := c.GetUserSessions(userId)
for k := range sessions {
c.RemoveState(k)
// GetUserSession returns value for given session token
func (c *provider) GetUserSession(userId, sessionToken string) (string, error) {
return c.sessionStore.Get(userId, sessionToken), nil
}
// DeleteAllUserSessions deletes all the user sessions from in-memory store.
func (c *provider) DeleteAllUserSessions(userId string) error {
namespaces := []string{
constants.AuthRecipeMethodBasicAuth,
constants.AuthRecipeMethodMagicLinkLogin,
constants.AuthRecipeMethodApple,
constants.AuthRecipeMethodFacebook,
constants.AuthRecipeMethodGithub,
constants.AuthRecipeMethodGoogle,
constants.AuthRecipeMethodLinkedIn,
}
for _, namespace := range namespaces {
c.sessionStore.RemoveAll(namespace + ":" + userId)
}
return nil
}
// DeleteUserSession deletes the user session from the in-memory store.
func (c *provider) DeleteUserSession(userId, sessionToken string) error {
c.sessionStore.Remove(userId, sessionToken)
return nil
}
// DeleteSessionForNamespace to delete session for a given namespace example google,github
func (c *provider) DeleteSessionForNamespace(namespace string) error {
c.sessionStore.RemoveByNamespace(namespace)
return nil
}
// SetState sets the state in the in-memory store.
func (c *provider) SetState(key, state string) error {
if os.Getenv("ENV") != "test" {
if os.Getenv("ENV") != constants.TestEnv {
c.mutex.Lock()
defer c.mutex.Unlock()
}
c.stateStore[key] = state
c.stateStore.Set(key, state)
return nil
}
// GetState gets the state from the in-memory store.
func (c *provider) GetState(key string) (string, error) {
if os.Getenv("ENV") != "test" {
c.mutex.Lock()
defer c.mutex.Unlock()
}
state := ""
if stateVal, ok := c.stateStore[key]; ok {
state = stateVal
}
return state, nil
return c.stateStore.Get(key), nil
}
// RemoveState removes the state from the in-memory store.
func (c *provider) RemoveState(key string) error {
if os.Getenv("ENV") != "test" {
c.mutex.Lock()
defer c.mutex.Unlock()
}
delete(c.stateStore, key)
c.stateStore.Remove(key)
return nil
}

View File

@@ -1,7 +1,6 @@
package inmemory
package stores
import (
"os"
"sync"
)
@@ -11,14 +10,20 @@ type EnvStore struct {
store map[string]interface{}
}
// NewEnvStore create a new env store
func NewEnvStore() *EnvStore {
return &EnvStore{
mutex: sync.Mutex{},
store: make(map[string]interface{}),
}
}
// UpdateEnvStore to update the whole env store object
func (e *EnvStore) UpdateStore(store map[string]interface{}) {
if os.Getenv("ENV") != "test" {
e.mutex.Lock()
defer e.mutex.Unlock()
}
// just override the keys + new keys
e.mutex.Lock()
defer e.mutex.Unlock()
// just override the keys + new keys
for key, value := range store {
e.store[key] = value
}
@@ -26,28 +31,18 @@ func (e *EnvStore) UpdateStore(store map[string]interface{}) {
// GetStore returns the env store
func (e *EnvStore) GetStore() map[string]interface{} {
if os.Getenv("ENV") != "test" {
e.mutex.Lock()
defer e.mutex.Unlock()
}
return e.store
}
// Get returns the value of the key in evn store
func (e *EnvStore) Get(key string) interface{} {
if os.Getenv("ENV") != "test" {
e.mutex.Lock()
defer e.mutex.Unlock()
}
return e.store[key]
}
// Set sets the value of the key in env store
func (e *EnvStore) Set(key string, value interface{}) {
if os.Getenv("ENV") != "test" {
e.mutex.Lock()
defer e.mutex.Unlock()
}
e.mutex.Lock()
defer e.mutex.Unlock()
e.store[key] = value
}

View File

@@ -0,0 +1,72 @@
package stores
import (
"strings"
"sync"
)
// SessionStore struct to store the env variables
type SessionStore struct {
mutex sync.Mutex
store map[string]map[string]string
}
// NewSessionStore create a new session store
func NewSessionStore() *SessionStore {
return &SessionStore{
mutex: sync.Mutex{},
store: make(map[string]map[string]string),
}
}
// Get returns the value of the key in state store
func (s *SessionStore) Get(key, subKey string) string {
return s.store[key][subKey]
}
// Set sets the value of the key in state store
func (s *SessionStore) Set(key string, subKey, value string) {
s.mutex.Lock()
defer s.mutex.Unlock()
if _, ok := s.store[key]; !ok {
s.store[key] = make(map[string]string)
}
s.store[key][subKey] = value
}
// RemoveAll all values for given key
func (s *SessionStore) RemoveAll(key string) {
s.mutex.Lock()
defer s.mutex.Unlock()
delete(s.store, key)
}
// Remove value for given key and subkey
func (s *SessionStore) Remove(key, subKey string) {
s.mutex.Lock()
defer s.mutex.Unlock()
if _, ok := s.store[key]; ok {
delete(s.store[key], subKey)
}
}
// Get all the values for given key
func (s *SessionStore) GetAll(key string) map[string]string {
if _, ok := s.store[key]; !ok {
s.store[key] = make(map[string]string)
}
return s.store[key]
}
// RemoveByNamespace to delete session for a given namespace example google,github
func (s *SessionStore) RemoveByNamespace(namespace string) error {
s.mutex.Lock()
defer s.mutex.Unlock()
for key := range s.store {
if strings.Contains(key, namespace+":") {
delete(s.store, key)
}
}
return nil
}

View File

@@ -0,0 +1,40 @@
package stores
import (
"sync"
)
// StateStore struct to store the env variables
type StateStore struct {
mutex sync.Mutex
store map[string]string
}
// NewStateStore create a new state store
func NewStateStore() *StateStore {
return &StateStore{
mutex: sync.Mutex{},
store: make(map[string]string),
}
}
// Get returns the value of the key in state store
func (s *StateStore) Get(key string) string {
return s.store[key]
}
// Set sets the value of the key in state store
func (s *StateStore) Set(key string, value string) {
s.mutex.Lock()
defer s.mutex.Unlock()
s.store[key] = value
}
// Remove removes the key from state store
func (s *StateStore) Remove(key string) {
s.mutex.Lock()
defer s.mutex.Unlock()
delete(s.store, key)
}

View File

@@ -2,12 +2,19 @@ package providers
// Provider defines current memory store provider
type Provider interface {
// SetUserSession sets the user session
SetUserSession(userId, key, token string) error
// GetAllUserSessions returns all the user sessions from the session store
GetAllUserSessions(userId string) (map[string]string, error)
// GetUserSession returns the session token for given token
GetUserSession(userId, key string) (string, error)
// DeleteUserSession deletes the user session
DeleteUserSession(userId, key string) error
// DeleteAllSessions deletes all the sessions from the session store
DeleteAllUserSession(userId string) error
// GetUserSessions returns all the user sessions from the session store
GetUserSessions(userId string) map[string]string
// ClearStore clears the session store for authorizer tokens
ClearStore() error
DeleteAllUserSessions(userId string) error
// DeleteSessionForNamespace deletes the session for a given namespace
DeleteSessionForNamespace(namespace string) error
// SetState sets the login state (key, value form) in the session store
SetState(key, state string) error
// GetState returns the state from the session store

View File

@@ -2,59 +2,107 @@ package redis
import (
"strconv"
"strings"
"github.com/authorizerdev/authorizer/server/constants"
log "github.com/sirupsen/logrus"
)
var (
// session store prefix
sessionStorePrefix = "authorizer_session:"
// state store prefix
stateStorePrefix = "authorizer_state:"
// env store prefix
envStorePrefix = "authorizer_env"
)
// ClearStore clears the redis store for authorizer related tokens
func (c *provider) ClearStore() error {
err := c.store.Del(c.ctx, sessionStorePrefix+"*").Err()
// SetUserSession sets the user session in redis store.
func (c *provider) SetUserSession(userId, key, token string) error {
err := c.store.HSet(c.ctx, userId, key, token).Err()
if err != nil {
log.Debug("Error clearing redis store: ", err)
log.Debug("Error saving to redis: ", err)
return err
}
return nil
}
// GetUserSessions returns all the user session token from the redis store.
func (c *provider) GetUserSessions(userID string) map[string]string {
data, err := c.store.HGetAll(c.ctx, "*").Result()
// GetAllUserSessions returns all the user session token from the redis store.
func (c *provider) GetAllUserSessions(userID string) (map[string]string, error) {
data, err := c.store.HGetAll(c.ctx, userID).Result()
if err != nil {
log.Debug("error getting token from redis store: ", err)
log.Debug("error getting all user sessions from redis store: ", err)
return nil, err
}
res := map[string]string{}
for k, v := range data {
split := strings.Split(v, "@")
if split[1] == userID {
res[k] = split[0]
}
}
return res
return data, nil
}
// DeleteAllUserSession deletes all the user session from redis
func (c *provider) DeleteAllUserSession(userId string) error {
sessions := c.GetUserSessions(userId)
for k, v := range sessions {
if k == "token" {
err := c.store.Del(c.ctx, v).Err()
// GetUserSession returns the user session from redis store.
func (c *provider) GetUserSession(userId, key string) (string, error) {
data, err := c.store.HGet(c.ctx, userId, key).Result()
if err != nil {
return "", err
}
return data, nil
}
// DeleteUserSession deletes the user session from redis store.
func (c *provider) DeleteUserSession(userId, key string) error {
if err := c.store.HDel(c.ctx, userId, constants.TokenTypeSessionToken+"_"+key).Err(); err != nil {
log.Debug("Error deleting user session from redis: ", err)
return err
}
if err := c.store.HDel(c.ctx, userId, constants.TokenTypeAccessToken+"_"+key).Err(); err != nil {
log.Debug("Error deleting user session from redis: ", err)
return err
}
if err := c.store.HDel(c.ctx, userId, constants.TokenTypeRefreshToken+"_"+key).Err(); err != nil {
log.Debug("Error deleting user session from redis: ", err)
return err
}
return nil
}
// DeleteAllUserSessions deletes all the user session from redis
func (c *provider) DeleteAllUserSessions(userID string) error {
namespaces := []string{
constants.AuthRecipeMethodBasicAuth,
constants.AuthRecipeMethodMagicLinkLogin,
constants.AuthRecipeMethodApple,
constants.AuthRecipeMethodFacebook,
constants.AuthRecipeMethodGithub,
constants.AuthRecipeMethodGoogle,
constants.AuthRecipeMethodLinkedIn,
}
for _, namespace := range namespaces {
err := c.store.Del(c.ctx, namespace+":"+userID).Err()
if err != nil {
log.Debug("Error deleting all user sessions from redis: ", err)
return err
}
}
return nil
}
// DeleteSessionForNamespace to delete session for a given namespace example google,github
func (c *provider) DeleteSessionForNamespace(namespace string) error {
var cursor uint64
for {
keys := []string{}
keys, cursor, err := c.store.Scan(c.ctx, cursor, namespace+":*", 0).Result()
if err != nil {
log.Debugf("Error scanning keys for %s namespace: %s", namespace, err.Error())
return err
}
for _, key := range keys {
err := c.store.Del(c.ctx, key).Err()
if err != nil {
log.Debug("Error deleting redis token: ", err)
log.Debugf("Error deleting sessions for %s namespace: %s", namespace, err.Error())
return err
}
}
if cursor == 0 { // no more keys
break
}
}
return nil
@@ -62,7 +110,7 @@ func (c *provider) DeleteAllUserSession(userId string) error {
// SetState sets the state in redis store.
func (c *provider) SetState(key, value string) error {
err := c.store.Set(c.ctx, sessionStorePrefix+key, value, 0).Err()
err := c.store.Set(c.ctx, stateStorePrefix+key, value, 0).Err()
if err != nil {
log.Debug("Error saving redis token: ", err)
return err
@@ -73,18 +121,18 @@ func (c *provider) SetState(key, value string) error {
// GetState gets the state from redis store.
func (c *provider) GetState(key string) (string, error) {
var res string
err := c.store.Get(c.ctx, sessionStorePrefix+key).Scan(&res)
data, err := c.store.Get(c.ctx, stateStorePrefix+key).Result()
if err != nil {
log.Debug("error getting token from redis store: ", err)
return "", err
}
return res, err
return data, err
}
// RemoveState removes the state from redis store.
func (c *provider) RemoveState(key string) error {
err := c.store.Del(c.ctx, sessionStorePrefix+key).Err()
err := c.store.Del(c.ctx, stateStorePrefix+key).Err()
if err != nil {
log.Fatalln("Error deleting redis token: ", err)
return err
@@ -112,7 +160,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 {
if key == constants.EnvKeyDisableBasicAuthentication || key == constants.EnvKeyDisableEmailVerification || key == constants.EnvKeyDisableLoginPage || key == constants.EnvKeyDisableMagicLinkLogin || key == constants.EnvKeyDisableRedisForEnv || key == constants.EnvKeyDisableSignUp || key == constants.EnvKeyDisableStrongPassword {
boolValue, err := strconv.ParseBool(value)
if err != nil {
return res, err
@@ -137,22 +185,20 @@ func (c *provider) UpdateEnvVariable(key string, value interface{}) error {
// GetStringStoreEnvVariable to get the string env variable from env store
func (c *provider) GetStringStoreEnvVariable(key string) (string, error) {
var res string
err := c.store.HGet(c.ctx, envStorePrefix, key).Scan(&res)
data, err := c.store.HGet(c.ctx, envStorePrefix, key).Result()
if err != nil {
return "", nil
}
return res, nil
return data, nil
}
// GetBoolStoreEnvVariable to get the bool env variable from env store
func (c *provider) GetBoolStoreEnvVariable(key string) (bool, error) {
var res bool
err := c.store.HGet(c.ctx, envStorePrefix, key).Scan(res)
data, err := c.store.HGet(c.ctx, envStorePrefix, key).Result()
if err != nil {
return false, nil
}
return res, nil
return data == "1", nil
}

View File

@@ -7,6 +7,7 @@ import (
"golang.org/x/oauth2"
facebookOAuth2 "golang.org/x/oauth2/facebook"
githubOAuth2 "golang.org/x/oauth2/github"
linkedInOAuth2 "golang.org/x/oauth2/linkedin"
"github.com/authorizerdev/authorizer/server/constants"
"github.com/authorizerdev/authorizer/server/memorystore"
@@ -17,6 +18,8 @@ type OAuthProvider struct {
GoogleConfig *oauth2.Config
GithubConfig *oauth2.Config
FacebookConfig *oauth2.Config
LinkedInConfig *oauth2.Config
AppleConfig *oauth2.Config
}
// OIDCProviders is a struct that contains reference all the OpenID providers
@@ -92,5 +95,43 @@ func InitOAuth() error {
}
}
linkedInClientID, err := memorystore.Provider.GetStringStoreEnvVariable(constants.EnvKeyLinkedInClientID)
if err != nil {
linkedInClientID = ""
}
linkedInClientSecret, err := memorystore.Provider.GetStringStoreEnvVariable(constants.EnvKeyLinkedInClientSecret)
if err != nil {
linkedInClientSecret = ""
}
if linkedInClientID != "" && linkedInClientSecret != "" {
OAuthProviders.LinkedInConfig = &oauth2.Config{
ClientID: linkedInClientID,
ClientSecret: linkedInClientSecret,
RedirectURL: "/oauth_callback/linkedin",
Endpoint: linkedInOAuth2.Endpoint,
Scopes: []string{"r_liteprofile", "r_emailaddress"},
}
}
appleClientID, err := memorystore.Provider.GetStringStoreEnvVariable(constants.EnvKeyAppleClientID)
if err != nil {
appleClientID = ""
}
appleClientSecret, err := memorystore.Provider.GetStringStoreEnvVariable(constants.EnvKeyAppleClientSecret)
if err != nil {
appleClientSecret = ""
}
if appleClientID != "" && appleClientSecret != "" {
OAuthProviders.AppleConfig = &oauth2.Config{
ClientID: appleClientID,
ClientSecret: appleClientSecret,
RedirectURL: "/oauth_callback/apple",
Endpoint: oauth2.Endpoint{
AuthURL: "https://appleid.apple.com/auth/authorize",
TokenURL: "https://appleid.apple.com/auth/token",
},
}
}
return nil
}

View File

@@ -4,9 +4,10 @@ import (
"net/url"
"strings"
"github.com/gin-gonic/gin"
"github.com/authorizerdev/authorizer/server/constants"
"github.com/authorizerdev/authorizer/server/memorystore"
"github.com/gin-gonic/gin"
)
// GetHost returns hostname from request context
@@ -14,15 +15,15 @@ import (
// if EnvKeyAuthorizerURL 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 := c.Request.Header.Get("X-Authorizer-URL")
authorizerURL, err := memorystore.Provider.GetStringStoreEnvVariable(constants.EnvKeyAuthorizerURL)
if err != nil {
authorizerURL = ""
}
if authorizerURL != "" {
return authorizerURL
}
authorizerURL, err := memorystore.Provider.GetStringStoreEnvVariable(constants.EnvKeyAuthorizerURL)
if err == nil {
authorizerURL = ""
}
authorizerURL = c.Request.Header.Get("X-Authorizer-URL")
if authorizerURL != "" {
return authorizerURL
}

View File

@@ -0,0 +1,54 @@
package resolvers
import (
"context"
"encoding/json"
"fmt"
"github.com/authorizerdev/authorizer/server/db"
"github.com/authorizerdev/authorizer/server/db/models"
"github.com/authorizerdev/authorizer/server/graph/model"
"github.com/authorizerdev/authorizer/server/token"
"github.com/authorizerdev/authorizer/server/utils"
"github.com/authorizerdev/authorizer/server/validators"
log "github.com/sirupsen/logrus"
)
// AddWebhookResolver resolver for add webhook mutation
func AddWebhookResolver(ctx context.Context, params model.AddWebhookRequest) (*model.Response, error) {
gc, err := utils.GinContextFromContext(ctx)
if err != nil {
log.Debug("Failed to get GinContext: ", err)
return nil, err
}
if !token.IsSuperAdmin(gc) {
log.Debug("Not logged in as super admin")
return nil, fmt.Errorf("unauthorized")
}
if !validators.IsValidWebhookEventName(params.EventName) {
log.Debug("Invalid Event Name: ", params.EventName)
return nil, fmt.Errorf("invalid event name %s", params.EventName)
}
headerBytes, err := json.Marshal(params.Headers)
if err != nil {
return nil, err
}
_, err = db.Provider.AddWebhook(ctx, models.Webhook{
EventName: params.EventName,
EndPoint: params.Endpoint,
Enabled: params.Enabled,
Headers: string(headerBytes),
})
if err != nil {
log.Debug("Failed to add webhook: ", err)
return nil, err
}
return &model.Response{
Message: `Webhook added successfully`,
}, nil
}

View File

@@ -58,7 +58,7 @@ func AdminSignupResolver(ctx context.Context, params model.AdminSignupInput) (*m
return res, err
}
env, err := db.Provider.GetEnv()
env, err := db.Provider.GetEnv(ctx)
if err != nil {
log.Debug("Failed to get env: ", err)
return res, err
@@ -71,7 +71,7 @@ func AdminSignupResolver(ctx context.Context, params model.AdminSignupInput) (*m
}
env.EnvData = envData
if _, err := db.Provider.UpdateEnv(env); err != nil {
if _, err := db.Provider.UpdateEnv(ctx, env); err != nil {
log.Debug("Failed to update env: ", err)
return res, err
}

View File

@@ -6,6 +6,7 @@ import (
log "github.com/sirupsen/logrus"
"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"
@@ -32,15 +33,13 @@ func DeleteUserResolver(ctx context.Context, params model.DeleteUserInput) (*mod
"email": params.Email,
})
user, err := db.Provider.GetUserByEmail(params.Email)
user, err := db.Provider.GetUserByEmail(ctx, params.Email)
if err != nil {
log.Debug("Failed to get user from DB: ", err)
return res, err
}
go memorystore.Provider.DeleteAllUserSession(fmt.Sprintf("%x", user.ID))
err = db.Provider.DeleteUser(user)
err = db.Provider.DeleteUser(ctx, user)
if err != nil {
log.Debug("Failed to delete user: ", err)
return res, err
@@ -50,5 +49,10 @@ func DeleteUserResolver(ctx context.Context, params model.DeleteUserInput) (*mod
Message: `user deleted successfully`,
}
go func() {
memorystore.Provider.DeleteAllUserSessions(user.ID)
utils.RegisterEvent(ctx, constants.UserDeletedWebhookEvent, "", user)
}()
return res, nil
}

View File

@@ -0,0 +1,49 @@
package resolvers
import (
"context"
"fmt"
"github.com/authorizerdev/authorizer/server/db"
"github.com/authorizerdev/authorizer/server/graph/model"
"github.com/authorizerdev/authorizer/server/token"
"github.com/authorizerdev/authorizer/server/utils"
log "github.com/sirupsen/logrus"
)
// DeleteWebhookResolver resolver to delete webhook and its relevant logs
func DeleteWebhookResolver(ctx context.Context, params model.WebhookRequest) (*model.Response, error) {
gc, err := utils.GinContextFromContext(ctx)
if err != nil {
log.Debug("Failed to get GinContext: ", err)
return nil, err
}
if !token.IsSuperAdmin(gc) {
log.Debug("Not logged in as super admin")
return nil, fmt.Errorf("unauthorized")
}
if params.ID == "" {
log.Debug("webhookID is required")
return nil, fmt.Errorf("webhook ID required")
}
log := log.WithField("webhook_id", params.ID)
webhook, err := db.Provider.GetWebhookByID(ctx, params.ID)
if err != nil {
log.Debug("failed to get webhook: ", err)
return nil, err
}
err = db.Provider.DeleteWebhook(ctx, webhook)
if err != nil {
log.Debug("failed to delete webhook: ", err)
return nil, err
}
return &model.Response{
Message: "Webhook deleted successfully",
}, nil
}

View File

@@ -6,6 +6,7 @@ import (
log "github.com/sirupsen/logrus"
"github.com/authorizerdev/authorizer/server/constants"
"github.com/authorizerdev/authorizer/server/db"
"github.com/authorizerdev/authorizer/server/graph/model"
"github.com/authorizerdev/authorizer/server/token"
@@ -31,7 +32,7 @@ func EnableAccessResolver(ctx context.Context, params model.UpdateAccessInput) (
"user_id": params.UserID,
})
user, err := db.Provider.GetUserByID(params.UserID)
user, err := db.Provider.GetUserByID(ctx, params.UserID)
if err != nil {
log.Debug("Failed to get user from DB: ", err)
return res, err
@@ -39,7 +40,7 @@ func EnableAccessResolver(ctx context.Context, params model.UpdateAccessInput) (
user.RevokedTimestamp = nil
user, err = db.Provider.UpdateUser(user)
user, err = db.Provider.UpdateUser(ctx, user)
if err != nil {
log.Debug("Failed to update user: ", err)
return res, err
@@ -49,5 +50,7 @@ func EnableAccessResolver(ctx context.Context, params model.UpdateAccessInput) (
Message: `user access enabled successfully`,
}
go utils.RegisterEvent(ctx, constants.UserAccessEnabledWebhookEvent, "", user)
return res, nil
}

View File

@@ -130,6 +130,18 @@ func EnvResolver(ctx context.Context) (*model.Env, error) {
if val, ok := store[constants.EnvKeyGithubClientSecret]; ok {
res.GithubClientSecret = utils.NewStringRef(val.(string))
}
if val, ok := store[constants.EnvKeyLinkedInClientID]; ok {
res.LinkedinClientID = utils.NewStringRef(val.(string))
}
if val, ok := store[constants.EnvKeyLinkedInClientSecret]; ok {
res.LinkedinClientSecret = utils.NewStringRef(val.(string))
}
if val, ok := store[constants.EnvKeyAppleClientID]; ok {
res.AppleClientID = utils.NewStringRef(val.(string))
}
if val, ok := store[constants.EnvKeyAppleClientSecret]; ok {
res.AppleClientSecret = utils.NewStringRef(val.(string))
}
if val, ok := store[constants.EnvKeyOrganizationName]; ok {
res.OrganizationName = utils.NewStringRef(val.(string))
}
@@ -141,7 +153,14 @@ func EnvResolver(ctx context.Context) (*model.Env, error) {
res.AllowedOrigins = strings.Split(store[constants.EnvKeyAllowedOrigins].(string), ",")
res.Roles = strings.Split(store[constants.EnvKeyRoles].(string), ",")
res.DefaultRoles = strings.Split(store[constants.EnvKeyDefaultRoles].(string), ",")
res.ProtectedRoles = strings.Split(store[constants.EnvKeyProtectedRoles].(string), ",")
// since protected role is optional default split gives array with empty string
protectedRoles := strings.Split(store[constants.EnvKeyProtectedRoles].(string), ",")
res.ProtectedRoles = []string{}
for _, role := range protectedRoles {
if strings.Trim(role, " ") != "" {
res.ProtectedRoles = append(res.ProtectedRoles, strings.Trim(role, " "))
}
}
// bool vars
res.DisableEmailVerification = store[constants.EnvKeyDisableEmailVerification].(bool)
@@ -149,6 +168,7 @@ func EnvResolver(ctx context.Context) (*model.Env, error) {
res.DisableMagicLinkLogin = store[constants.EnvKeyDisableMagicLinkLogin].(bool)
res.DisableLoginPage = store[constants.EnvKeyDisableLoginPage].(bool)
res.DisableSignUp = store[constants.EnvKeyDisableSignUp].(bool)
res.DisableStrongPassword = store[constants.EnvKeyDisableStrongPassword].(bool)
return res, nil
}

View File

@@ -49,7 +49,7 @@ func ForgotPasswordResolver(ctx context.Context, params model.ForgotPasswordInpu
log := log.WithFields(log.Fields{
"email": params.Email,
})
_, err = db.Provider.GetUserByEmail(params.Email)
_, 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`)
@@ -71,7 +71,7 @@ func ForgotPasswordResolver(ctx context.Context, params model.ForgotPasswordInpu
log.Debug("Failed to create verification token", err)
return res, err
}
_, err = db.Provider.AddVerificationRequest(models.VerificationRequest{
_, err = db.Provider.AddVerificationRequest(ctx, models.VerificationRequest{
Token: verificationToken,
Identifier: constants.VerificationTypeForgotPassword,
ExpiresAt: time.Now().Add(time.Minute * 30).Unix(),

View File

@@ -70,7 +70,7 @@ func InviteMembersResolver(ctx context.Context, params model.InviteMemberInput)
// for each emails check if emails exists in db
newEmails := []string{}
for _, email := range emails {
_, err := db.Provider.GetUserByEmail(email)
_, err := db.Provider.GetUserByEmail(ctx, email)
if err != nil {
log.Debugf("User with %s email not found, so inviting user", email)
newEmails = append(newEmails, email)
@@ -129,24 +129,24 @@ func InviteMembersResolver(ctx context.Context, params model.InviteMemberInput)
// use magic link login if that option is on
if !isMagicLinkLoginDisabled {
user.SignupMethods = constants.SignupMethodMagicLinkLogin
user.SignupMethods = constants.AuthRecipeMethodMagicLinkLogin
verificationRequest.Identifier = constants.VerificationTypeMagicLinkLogin
} else {
// use basic authentication if that option is on
user.SignupMethods = constants.SignupMethodBasicAuth
user.SignupMethods = constants.AuthRecipeMethodBasicAuth
verificationRequest.Identifier = constants.VerificationTypeForgotPassword
verifyEmailURL = appURL + "/setup-password"
}
user, err = db.Provider.AddUser(user)
user, err = db.Provider.AddUser(ctx, user)
if err != nil {
log.Debugf("Error adding user: %s, err: %v", email, err)
return nil, err
}
_, err = db.Provider.AddVerificationRequest(verificationRequest)
_, err = db.Provider.AddVerificationRequest(ctx, verificationRequest)
if err != nil {
log.Debugf("Error adding verification request: %s, err: %v", email, err)
return nil, err

View File

@@ -45,7 +45,7 @@ func LoginResolver(ctx context.Context, params model.LoginInput) (*model.AuthRes
"email": params.Email,
})
params.Email = strings.ToLower(params.Email)
user, err := db.Provider.GetUserByEmail(params.Email)
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`)
@@ -56,7 +56,7 @@ func LoginResolver(ctx context.Context, params model.LoginInput) (*model.AuthRes
return res, fmt.Errorf(`user access has been revoked`)
}
if !strings.Contains(user.SignupMethods, constants.SignupMethodBasicAuth) {
if !strings.Contains(user.SignupMethods, constants.AuthRecipeMethodBasicAuth) {
log.Debug("User signup method is not basic auth")
return res, fmt.Errorf(`user has not signed up email & password`)
}
@@ -97,7 +97,7 @@ func LoginResolver(ctx context.Context, params model.LoginInput) (*model.AuthRes
scope = params.Scope
}
authToken, err := token.CreateAuthToken(gc, user, roles, scope)
authToken, err := token.CreateAuthToken(gc, user, roles, scope, constants.AuthRecipeMethodBasicAuth)
if err != nil {
log.Debug("Failed to create auth token", err)
return res, err
@@ -117,19 +117,23 @@ func LoginResolver(ctx context.Context, params model.LoginInput) (*model.AuthRes
}
cookie.SetSession(gc, authToken.FingerPrintHash)
memorystore.Provider.SetState(authToken.FingerPrintHash, authToken.FingerPrint+"@"+user.ID)
memorystore.Provider.SetState(authToken.AccessToken.Token, authToken.FingerPrint+"@"+user.ID)
sessionStoreKey := constants.AuthRecipeMethodBasicAuth + ":" + user.ID
memorystore.Provider.SetUserSession(sessionStoreKey, constants.TokenTypeSessionToken+"_"+authToken.FingerPrint, authToken.FingerPrintHash)
memorystore.Provider.SetUserSession(sessionStoreKey, constants.TokenTypeAccessToken+"_"+authToken.FingerPrint, authToken.AccessToken.Token)
if authToken.RefreshToken != nil {
res.RefreshToken = &authToken.RefreshToken.Token
memorystore.Provider.SetState(authToken.RefreshToken.Token, authToken.FingerPrint+"@"+user.ID)
memorystore.Provider.SetUserSession(sessionStoreKey, constants.TokenTypeRefreshToken+"_"+authToken.FingerPrint, authToken.RefreshToken.Token)
}
go db.Provider.AddSession(models.Session{
UserID: user.ID,
UserAgent: utils.GetUserAgent(gc.Request),
IP: utils.GetIP(gc.Request),
})
go func() {
utils.RegisterEvent(ctx, constants.UserLoginWebhookEvent, constants.AuthRecipeMethodBasicAuth, user)
db.Provider.AddSession(ctx, models.Session{
UserID: user.ID,
UserAgent: utils.GetUserAgent(gc.Request),
IP: utils.GetIP(gc.Request),
})
}()
return res, nil
}

View File

@@ -2,6 +2,7 @@ package resolvers
import (
"context"
"encoding/json"
log "github.com/sirupsen/logrus"
@@ -9,38 +10,46 @@ import (
"github.com/authorizerdev/authorizer/server/crypto"
"github.com/authorizerdev/authorizer/server/graph/model"
"github.com/authorizerdev/authorizer/server/memorystore"
"github.com/authorizerdev/authorizer/server/token"
"github.com/authorizerdev/authorizer/server/utils"
)
// LogoutResolver is a resolver for logout mutation
func LogoutResolver(ctx context.Context) (*model.Response, error) {
var res *model.Response
gc, err := utils.GinContextFromContext(ctx)
if err != nil {
log.Debug("Failed to get GinContext: ", err)
return res, err
return nil, err
}
// get fingerprint hash
fingerprintHash, err := cookie.GetSession(gc)
if err != nil {
log.Debug("Failed to get fingerprint hash: ", err)
return res, err
return nil, err
}
decryptedFingerPrint, err := crypto.DecryptAES(fingerprintHash)
if err != nil {
log.Debug("Failed to decrypt fingerprint hash: ", err)
return res, err
return nil, err
}
fingerPrint := string(decryptedFingerPrint)
var sessionData token.SessionData
err = json.Unmarshal([]byte(decryptedFingerPrint), &sessionData)
if err != nil {
return nil, err
}
memorystore.Provider.RemoveState(fingerPrint)
sessionKey := sessionData.Subject
if sessionData.LoginMethod != "" {
sessionKey = sessionData.LoginMethod + ":" + sessionData.Subject
}
memorystore.Provider.DeleteUserSession(sessionKey, sessionData.Nonce)
cookie.DeleteSession(gc)
res = &model.Response{
res := &model.Response{
Message: "Logged out successfully",
}

View File

@@ -59,7 +59,7 @@ func MagicLinkLoginResolver(ctx context.Context, params model.MagicLinkLoginInpu
}
// find user with email
existingUser, err := db.Provider.GetUserByEmail(params.Email)
existingUser, err := db.Provider.GetUserByEmail(ctx, params.Email)
if err != nil {
isSignupDisabled, err := memorystore.Provider.GetBoolStoreEnvVariable(constants.EnvKeyDisableSignUp)
if err != nil {
@@ -70,7 +70,7 @@ func MagicLinkLoginResolver(ctx context.Context, params model.MagicLinkLoginInpu
return res, fmt.Errorf(`signup is disabled for this instance`)
}
user.SignupMethods = constants.SignupMethodMagicLinkLogin
user.SignupMethods = constants.AuthRecipeMethodMagicLinkLogin
// define roles for new user
if len(params.Roles) > 0 {
// check if roles exists
@@ -99,7 +99,8 @@ func MagicLinkLoginResolver(ctx context.Context, params model.MagicLinkLoginInpu
}
user.Roles = strings.Join(inputRoles, ",")
user, _ = db.Provider.AddUser(user)
user, _ = db.Provider.AddUser(ctx, user)
go utils.RegisterEvent(ctx, constants.UserCreatedWebhookEvent, constants.AuthRecipeMethodMagicLinkLogin, user)
} else {
user = existingUser
// There multiple scenarios with roles here in magic link login
@@ -158,12 +159,12 @@ func MagicLinkLoginResolver(ctx context.Context, params model.MagicLinkLoginInpu
}
signupMethod := existingUser.SignupMethods
if !strings.Contains(signupMethod, constants.SignupMethodMagicLinkLogin) {
signupMethod = signupMethod + "," + constants.SignupMethodMagicLinkLogin
if !strings.Contains(signupMethod, constants.AuthRecipeMethodMagicLinkLogin) {
signupMethod = signupMethod + "," + constants.AuthRecipeMethodMagicLinkLogin
}
user.SignupMethods = signupMethod
user, _ = db.Provider.UpdateUser(user)
user, _ = db.Provider.UpdateUser(ctx, user)
if err != nil {
log.Debug("Failed to update user: ", err)
}
@@ -197,7 +198,7 @@ func MagicLinkLoginResolver(ctx context.Context, params model.MagicLinkLoginInpu
if strings.Contains(redirectURL, "?") {
redirectURL = redirectURL + "&" + redirectURLParams
} else {
redirectURL = redirectURL + "?" + redirectURLParams
redirectURL = redirectURL + "?" + strings.TrimPrefix(redirectURLParams, "&")
}
verificationType := constants.VerificationTypeMagicLinkLogin
@@ -205,7 +206,7 @@ func MagicLinkLoginResolver(ctx context.Context, params model.MagicLinkLoginInpu
if err != nil {
log.Debug("Failed to create verification token: ", err)
}
_, err = db.Provider.AddVerificationRequest(models.VerificationRequest{
_, err = db.Provider.AddVerificationRequest(ctx, models.VerificationRequest{
Token: verificationToken,
Identifier: verificationType,
ExpiresAt: time.Now().Add(time.Minute * 30).Unix(),

View File

@@ -41,6 +41,30 @@ func MetaResolver(ctx context.Context) (*model.Meta, error) {
facebookClientSecret = ""
}
linkedClientID, err := memorystore.Provider.GetStringStoreEnvVariable(constants.EnvKeyLinkedInClientID)
if err != nil {
log.Debug("Failed to get LinkedIn Client ID from environment variable", err)
linkedClientID = ""
}
linkedInClientSecret, err := memorystore.Provider.GetStringStoreEnvVariable(constants.EnvKeyLinkedInClientSecret)
if err != nil {
log.Debug("Failed to get LinkedIn Client Secret from environment variable", err)
linkedInClientSecret = ""
}
appleClientID, err := memorystore.Provider.GetStringStoreEnvVariable(constants.EnvKeyAppleClientID)
if err != nil {
log.Debug("Failed to get Apple Client ID from environment variable", err)
appleClientID = ""
}
appleClientSecret, err := memorystore.Provider.GetStringStoreEnvVariable(constants.EnvKeyAppleClientSecret)
if err != nil {
log.Debug("Failed to get Apple Client Secret from environment variable", err)
appleClientSecret = ""
}
githubClientID, err := memorystore.Provider.GetStringStoreEnvVariable(constants.EnvKeyGithubClientID)
if err != nil {
log.Debug("Failed to get Github Client ID from environment variable", err)
@@ -77,16 +101,25 @@ func MetaResolver(ctx context.Context) (*model.Meta, error) {
isSignUpDisabled = true
}
isStrongPasswordDisabled, err := memorystore.Provider.GetBoolStoreEnvVariable(constants.EnvKeyDisableStrongPassword)
if err != nil {
log.Debug("Failed to get Disable Signup from environment variable", err)
isSignUpDisabled = true
}
metaInfo := model.Meta{
Version: constants.VERSION,
ClientID: clientID,
IsGoogleLoginEnabled: googleClientID != "" && googleClientSecret != "",
IsGithubLoginEnabled: githubClientID != "" && githubClientSecret != "",
IsFacebookLoginEnabled: facebookClientID != "" && facebookClientSecret != "",
IsLinkedinLoginEnabled: linkedClientID != "" && linkedInClientSecret != "",
IsAppleLoginEnabled: appleClientID != "" && appleClientSecret != "",
IsBasicAuthenticationEnabled: !isBasicAuthDisabled,
IsEmailVerificationEnabled: !isEmailVerificationDisabled,
IsMagicLinkLoginEnabled: !isMagicLinkLoginDisabled,
IsSignUpEnabled: !isSignUpDisabled,
IsStrongPasswordEnabled: !isStrongPasswordDisabled,
}
return &metaInfo, nil
}

View File

@@ -38,7 +38,7 @@ func ProfileResolver(ctx context.Context) (*model.User, error) {
log := log.WithFields(log.Fields{
"user_id": userID,
})
user, err := db.Provider.GetUserByID(userID)
user, err := db.Provider.GetUserByID(ctx, userID)
if err != nil {
log.Debug("Failed to get user: ", err)
return res, err

View File

@@ -39,14 +39,14 @@ func ResendVerifyEmailResolver(ctx context.Context, params model.ResendVerifyEma
return res, fmt.Errorf("invalid identifier")
}
verificationRequest, err := db.Provider.GetVerificationRequestByEmail(params.Email, params.Identifier)
verificationRequest, err := db.Provider.GetVerificationRequestByEmail(ctx, params.Email, params.Identifier)
if err != nil {
log.Debug("Failed to get verification request: ", err)
return res, fmt.Errorf(`verification request not found`)
}
// delete current verification and create new one
err = db.Provider.DeleteVerificationRequest(verificationRequest)
err = db.Provider.DeleteVerificationRequest(ctx, verificationRequest)
if err != nil {
log.Debug("Failed to delete verification request: ", err)
}
@@ -62,7 +62,7 @@ func ResendVerifyEmailResolver(ctx context.Context, params model.ResendVerifyEma
if err != nil {
log.Debug("Failed to create verification token: ", err)
}
_, err = db.Provider.AddVerificationRequest(models.VerificationRequest{
_, err = db.Provider.AddVerificationRequest(ctx, models.VerificationRequest{
Token: verificationToken,
Identifier: params.Identifier,
ExpiresAt: time.Now().Add(time.Minute * 30).Unix(),

View File

@@ -39,7 +39,7 @@ func ResetPasswordResolver(ctx context.Context, params model.ResetPasswordInput)
return res, fmt.Errorf(`basic authentication is disabled for this instance`)
}
verificationRequest, err := db.Provider.GetVerificationRequestByToken(params.Token)
verificationRequest, err := db.Provider.GetVerificationRequestByToken(ctx, params.Token)
if err != nil {
log.Debug("Failed to get verification request: ", err)
return res, fmt.Errorf(`invalid token`)
@@ -50,24 +50,29 @@ func ResetPasswordResolver(ctx context.Context, params model.ResetPasswordInput)
return res, fmt.Errorf(`passwords don't match`)
}
if !validators.IsValidPassword(params.Password) {
if err := validators.IsValidPassword(params.Password); err != nil {
log.Debug("Invalid password")
return res, fmt.Errorf(`password is not valid. It needs to be at least 6 characters long and contain at least one number, one uppercase letter, one lowercase letter and one special character`)
return res, err
}
// verify if token exists in db
hostname := parsers.GetHost(gc)
claim, err := token.ParseJWTToken(params.Token, hostname, verificationRequest.Nonce, verificationRequest.Email)
claim, err := token.ParseJWTToken(params.Token)
if err != nil {
log.Debug("Failed to parse token: ", err)
return res, fmt.Errorf(`invalid token`)
}
if ok, err := token.ValidateJWTClaims(claim, hostname, verificationRequest.Nonce, verificationRequest.Email); !ok || err != nil {
log.Debug("Failed to validate jwt claims: ", err)
return res, fmt.Errorf(`invalid token`)
}
email := claim["sub"].(string)
log := log.WithFields(log.Fields{
"email": email,
})
user, err := db.Provider.GetUserByEmail(email)
user, err := db.Provider.GetUserByEmail(ctx, email)
if err != nil {
log.Debug("Failed to get user: ", err)
return res, err
@@ -77,8 +82,8 @@ func ResetPasswordResolver(ctx context.Context, params model.ResetPasswordInput)
user.Password = &password
signupMethod := user.SignupMethods
if !strings.Contains(signupMethod, constants.SignupMethodBasicAuth) {
signupMethod = signupMethod + "," + constants.SignupMethodBasicAuth
if !strings.Contains(signupMethod, constants.AuthRecipeMethodBasicAuth) {
signupMethod = signupMethod + "," + constants.AuthRecipeMethodBasicAuth
}
user.SignupMethods = signupMethod
@@ -89,13 +94,13 @@ func ResetPasswordResolver(ctx context.Context, params model.ResetPasswordInput)
}
// delete from verification table
err = db.Provider.DeleteVerificationRequest(verificationRequest)
err = db.Provider.DeleteVerificationRequest(ctx, verificationRequest)
if err != nil {
log.Debug("Failed to delete verification request: ", err)
return res, err
}
_, err = db.Provider.UpdateUser(user)
_, err = db.Provider.UpdateUser(ctx, user)
if err != nil {
log.Debug("Failed to update user: ", err)
return res, err

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