Compare commits

...

38 Commits

Author SHA1 Message Date
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
67 changed files with 1483 additions and 566 deletions

42
app/package-lock.json generated
View File

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

View File

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

View File

@@ -2529,7 +2529,8 @@
"@chakra-ui/css-reset": { "@chakra-ui/css-reset": {
"version": "1.1.1", "version": "1.1.1",
"resolved": "https://registry.npmjs.org/@chakra-ui/css-reset/-/css-reset-1.1.1.tgz", "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": { "@chakra-ui/descendant": {
"version": "2.1.1", "version": "2.1.1",
@@ -3133,7 +3134,8 @@
"@graphql-typed-document-node/core": { "@graphql-typed-document-node/core": {
"version": "3.1.1", "version": "3.1.1",
"resolved": "https://registry.npmjs.org/@graphql-typed-document-node/core/-/core-3.1.1.tgz", "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": { "@popperjs/core": {
"version": "2.11.0", "version": "2.11.0",
@@ -3843,7 +3845,8 @@
"react-icons": { "react-icons": {
"version": "4.3.1", "version": "4.3.1",
"resolved": "https://registry.npmjs.org/react-icons/-/react-icons-4.3.1.tgz", "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": { "react-is": {
"version": "16.13.1", "version": "16.13.1",
@@ -4029,7 +4032,8 @@
"use-callback-ref": { "use-callback-ref": {
"version": "1.2.5", "version": "1.2.5",
"resolved": "https://registry.npmjs.org/use-callback-ref/-/use-callback-ref-1.2.5.tgz", "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": { "use-sidecar": {
"version": "1.0.5", "version": "1.0.5",

View File

@@ -71,6 +71,18 @@ const Features = ({ variables, setVariables }: any) => {
/> />
</Flex> </Flex>
</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> </Stack>
</div> </div>
); );

View File

@@ -9,7 +9,13 @@ import {
Divider, Divider,
useMediaQuery, useMediaQuery,
} from '@chakra-ui/react'; } from '@chakra-ui/react';
import { FaGoogle, FaGithub, FaFacebookF, FaLinkedin } from 'react-icons/fa'; import {
FaGoogle,
FaGithub,
FaFacebookF,
FaLinkedin,
FaApple,
} from 'react-icons/fa';
import { TextInputType, HiddenInputType } from '../../constants'; import { TextInputType, HiddenInputType } from '../../constants';
const OAuthConfig = ({ const OAuthConfig = ({
@@ -216,7 +222,45 @@ const OAuthConfig = ({
fieldVisibility={fieldVisibility} fieldVisibility={fieldVisibility}
setFieldVisibility={setFieldVisibility} setFieldVisibility={setFieldVisibility}
inputType={HiddenInputType.LINKEDIN_CLIENT_SECRET} inputType={HiddenInputType.LINKEDIN_CLIENT_SECRET}
placeholder="LinkedIn 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> </Center>
</Flex> </Flex>

View File

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

View File

@@ -8,6 +8,7 @@ export const TextInputType = {
GITHUB_CLIENT_ID: 'GITHUB_CLIENT_ID', GITHUB_CLIENT_ID: 'GITHUB_CLIENT_ID',
FACEBOOK_CLIENT_ID: 'FACEBOOK_CLIENT_ID', FACEBOOK_CLIENT_ID: 'FACEBOOK_CLIENT_ID',
LINKEDIN_CLIENT_ID: 'LINKEDIN_CLIENT_ID', LINKEDIN_CLIENT_ID: 'LINKEDIN_CLIENT_ID',
APPLE_CLIENT_ID: 'APPLE_CLIENT_ID',
JWT_ROLE_CLAIM: 'JWT_ROLE_CLAIM', JWT_ROLE_CLAIM: 'JWT_ROLE_CLAIM',
REDIS_URL: 'REDIS_URL', REDIS_URL: 'REDIS_URL',
SMTP_HOST: 'SMTP_HOST', SMTP_HOST: 'SMTP_HOST',
@@ -33,6 +34,7 @@ export const HiddenInputType = {
GITHUB_CLIENT_SECRET: 'GITHUB_CLIENT_SECRET', GITHUB_CLIENT_SECRET: 'GITHUB_CLIENT_SECRET',
FACEBOOK_CLIENT_SECRET: 'FACEBOOK_CLIENT_SECRET', FACEBOOK_CLIENT_SECRET: 'FACEBOOK_CLIENT_SECRET',
LINKEDIN_CLIENT_SECRET: 'LINKEDIN_CLIENT_SECRET', LINKEDIN_CLIENT_SECRET: 'LINKEDIN_CLIENT_SECRET',
APPLE_CLIENT_SECRET: 'APPLE_CLIENT_SECRET',
JWT_SECRET: 'JWT_SECRET', JWT_SECRET: 'JWT_SECRET',
SMTP_PASSWORD: 'SMTP_PASSWORD', SMTP_PASSWORD: 'SMTP_PASSWORD',
ADMIN_SECRET: 'ADMIN_SECRET', ADMIN_SECRET: 'ADMIN_SECRET',
@@ -65,6 +67,7 @@ export const SwitchInputType = {
DISABLE_BASIC_AUTHENTICATION: 'DISABLE_BASIC_AUTHENTICATION', DISABLE_BASIC_AUTHENTICATION: 'DISABLE_BASIC_AUTHENTICATION',
DISABLE_SIGN_UP: 'DISABLE_SIGN_UP', DISABLE_SIGN_UP: 'DISABLE_SIGN_UP',
DISABLE_REDIS_FOR_ENV: 'DISABLE_REDIS_FOR_ENV', DISABLE_REDIS_FOR_ENV: 'DISABLE_REDIS_FOR_ENV',
DISABLE_STRONG_PASSWORD: 'DISABLE_STRONG_PASSWORD',
}; };
export const DateInputType = { export const DateInputType = {
@@ -103,6 +106,8 @@ export interface envVarTypes {
FACEBOOK_CLIENT_SECRET: string; FACEBOOK_CLIENT_SECRET: string;
LINKEDIN_CLIENT_ID: string; LINKEDIN_CLIENT_ID: string;
LINKEDIN_CLIENT_SECRET: string; LINKEDIN_CLIENT_SECRET: string;
APPLE_CLIENT_ID: string;
APPLE_CLIENT_SECRET: string;
ROLES: [string] | []; ROLES: [string] | [];
DEFAULT_ROLES: [string] | []; DEFAULT_ROLES: [string] | [];
PROTECTED_ROLES: [string] | []; PROTECTED_ROLES: [string] | [];
@@ -127,6 +132,7 @@ export interface envVarTypes {
DISABLE_EMAIL_VERIFICATION: boolean; DISABLE_EMAIL_VERIFICATION: boolean;
DISABLE_BASIC_AUTHENTICATION: boolean; DISABLE_BASIC_AUTHENTICATION: boolean;
DISABLE_SIGN_UP: boolean; DISABLE_SIGN_UP: boolean;
DISABLE_STRONG_PASSWORD: boolean;
OLD_ADMIN_SECRET: string; OLD_ADMIN_SECRET: string;
DATABASE_NAME: string; DATABASE_NAME: string;
DATABASE_TYPE: string; DATABASE_TYPE: string;

View File

@@ -28,8 +28,11 @@ export const EnvVariablesQuery = `
FACEBOOK_CLIENT_SECRET, FACEBOOK_CLIENT_SECRET,
LINKEDIN_CLIENT_ID, LINKEDIN_CLIENT_ID,
LINKEDIN_CLIENT_SECRET, LINKEDIN_CLIENT_SECRET,
APPLE_CLIENT_ID,
APPLE_CLIENT_SECRET,
DEFAULT_ROLES, DEFAULT_ROLES,
PROTECTED_ROLES, PROTECTED_ROLES,
ROLES,
JWT_TYPE, JWT_TYPE,
JWT_SECRET, JWT_SECRET,
JWT_ROLE_CLAIM, JWT_ROLE_CLAIM,
@@ -50,6 +53,7 @@ export const EnvVariablesQuery = `
DISABLE_EMAIL_VERIFICATION, DISABLE_EMAIL_VERIFICATION,
DISABLE_BASIC_AUTHENTICATION, DISABLE_BASIC_AUTHENTICATION,
DISABLE_SIGN_UP, DISABLE_SIGN_UP,
DISABLE_STRONG_PASSWORD,
DISABLE_REDIS_FOR_ENV, DISABLE_REDIS_FOR_ENV,
CUSTOM_ACCESS_TOKEN_SCRIPT, CUSTOM_ACCESS_TOKEN_SCRIPT,
DATABASE_NAME, DATABASE_NAME,

View File

@@ -48,6 +48,8 @@ const Environment = () => {
FACEBOOK_CLIENT_SECRET: '', FACEBOOK_CLIENT_SECRET: '',
LINKEDIN_CLIENT_ID: '', LINKEDIN_CLIENT_ID: '',
LINKEDIN_CLIENT_SECRET: '', LINKEDIN_CLIENT_SECRET: '',
APPLE_CLIENT_ID: '',
APPLE_CLIENT_SECRET: '',
ROLES: [], ROLES: [],
DEFAULT_ROLES: [], DEFAULT_ROLES: [],
PROTECTED_ROLES: [], PROTECTED_ROLES: [],
@@ -72,6 +74,7 @@ const Environment = () => {
DISABLE_EMAIL_VERIFICATION: false, DISABLE_EMAIL_VERIFICATION: false,
DISABLE_BASIC_AUTHENTICATION: false, DISABLE_BASIC_AUTHENTICATION: false,
DISABLE_SIGN_UP: false, DISABLE_SIGN_UP: false,
DISABLE_STRONG_PASSWORD: false,
OLD_ADMIN_SECRET: '', OLD_ADMIN_SECRET: '',
DATABASE_NAME: '', DATABASE_NAME: '',
DATABASE_TYPE: '', DATABASE_TYPE: '',
@@ -86,6 +89,7 @@ const Environment = () => {
GITHUB_CLIENT_SECRET: false, GITHUB_CLIENT_SECRET: false,
FACEBOOK_CLIENT_SECRET: false, FACEBOOK_CLIENT_SECRET: false,
LINKEDIN_CLIENT_SECRET: false, LINKEDIN_CLIENT_SECRET: false,
APPLE_CLIENT_SECRET: false,
JWT_SECRET: false, JWT_SECRET: false,
SMTP_PASSWORD: false, SMTP_PASSWORD: false,
ADMIN_SECRET: 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

@@ -21,4 +21,6 @@ const (
DbTypeCassandraDB = "cassandradb" DbTypeCassandraDB = "cassandradb"
// DbTypeScyllaDB is the scylla database type // DbTypeScyllaDB is the scylla database type
DbTypeScyllaDB = "scylladb" DbTypeScyllaDB = "scylladb"
// DbTypeCockroachDB is the cockroach database type
DbTypeCockroachDB = "cockroachdb"
) )

View File

@@ -3,6 +3,8 @@ package constants
var VERSION = "0.0.1" var VERSION = "0.0.1"
const ( const (
// TestEnv is used for testing
TestEnv = "test"
// EnvKeyEnv key for env variable ENV // EnvKeyEnv key for env variable ENV
EnvKeyEnv = "ENV" EnvKeyEnv = "ENV"
// EnvKeyEnvPath key for cli arg variable ENV_PATH // EnvKeyEnvPath key for cli arg variable ENV_PATH
@@ -77,6 +79,10 @@ const (
EnvKeyLinkedInClientID = "LINKEDIN_CLIENT_ID" EnvKeyLinkedInClientID = "LINKEDIN_CLIENT_ID"
// EnvKeyLinkedinClientSecret key for env variable LINKEDIN_CLIENT_SECRET // EnvKeyLinkedinClientSecret key for env variable LINKEDIN_CLIENT_SECRET
EnvKeyLinkedInClientSecret = "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 key for env variable ORGANIZATION_NAME
EnvKeyOrganizationName = "ORGANIZATION_NAME" EnvKeyOrganizationName = "ORGANIZATION_NAME"
// EnvKeyOrganizationLogo key for env variable ORGANIZATION_LOGO // EnvKeyOrganizationLogo key for env variable ORGANIZATION_LOGO
@@ -109,6 +115,8 @@ const (
EnvKeyDisableSignUp = "DISABLE_SIGN_UP" EnvKeyDisableSignUp = "DISABLE_SIGN_UP"
// EnvKeyDisableRedisForEnv key for env variable DISABLE_REDIS_FOR_ENV // EnvKeyDisableRedisForEnv key for env variable DISABLE_REDIS_FOR_ENV
EnvKeyDisableRedisForEnv = "DISABLE_REDIS_FOR_ENV" EnvKeyDisableRedisForEnv = "DISABLE_REDIS_FOR_ENV"
// EnvKeyDisableStrongPassword key for env variable DISABLE_STRONG_PASSWORD
EnvKeyDisableStrongPassword = "DISABLE_STRONG_PASSWORD"
// Slice variables // Slice variables
// EnvKeyRoles key for env variable ROLES // EnvKeyRoles key for env variable ROLES

View File

@@ -1,16 +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"
// SignupMethodLinkedin is the linkedin signup method
SignupMethodLinkedIn = "linkedin"
)

View File

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

View File

@@ -38,7 +38,6 @@ func (user *User) AsAPIUser() *model.User {
email := user.Email email := user.Email
createdAt := user.CreatedAt createdAt := user.CreatedAt
updatedAt := user.UpdatedAt updatedAt := user.UpdatedAt
revokedTimestamp := user.RevokedTimestamp
return &model.User{ return &model.User{
ID: user.ID, ID: user.ID,
Email: user.Email, Email: user.Email,
@@ -55,7 +54,7 @@ func (user *User) AsAPIUser() *model.User {
PhoneNumberVerified: &isPhoneVerified, PhoneNumberVerified: &isPhoneVerified,
Picture: user.Picture, Picture: user.Picture,
Roles: strings.Split(user.Roles, ","), Roles: strings.Split(user.Roles, ","),
RevokedTimestamp: revokedTimestamp, RevokedTimestamp: user.RevokedTimestamp,
CreatedAt: &createdAt, CreatedAt: &createdAt,
UpdatedAt: &updatedAt, UpdatedAt: &updatedAt,
} }

View File

@@ -68,7 +68,6 @@ func (p *provider) ListUsers(pagination model.Pagination) (*model.Users, error)
opts.SetSort(bson.M{"created_at": -1}) opts.SetSort(bson.M{"created_at": -1})
paginationClone := pagination paginationClone := pagination
// TODO add pagination total
userCollection := p.db.Collection(models.Collections.User, options.Collection()) userCollection := p.db.Collection(models.Collections.User, options.Collection())
count, err := userCollection.CountDocuments(nil, bson.M{}, options.Count()) count, err := userCollection.CountDocuments(nil, bson.M{}, options.Count())

View File

@@ -46,7 +46,7 @@ func NewProvider() (*provider, error) {
dbURL := memorystore.RequiredEnvStoreObj.GetRequiredEnv().DatabaseURL dbURL := memorystore.RequiredEnvStoreObj.GetRequiredEnv().DatabaseURL
switch dbType { switch dbType {
case constants.DbTypePostgres, constants.DbTypeYugabyte: case constants.DbTypePostgres, constants.DbTypeYugabyte, constants.DbTypeCockroachDB:
sqlDB, err = gorm.Open(postgres.Open(dbURL), ormConfig) sqlDB, err = gorm.Open(postgres.Open(dbURL), ormConfig)
case constants.DbTypeSqlite: case constants.DbTypeSqlite:
sqlDB, err = gorm.Open(sqlite.Open(dbURL), ormConfig) sqlDB, err = gorm.Open(sqlite.Open(dbURL), ormConfig)

View File

@@ -97,7 +97,6 @@ func (p *provider) ListUsers(pagination model.Pagination) (*model.Users, error)
func (p *provider) GetUserByEmail(email string) (models.User, error) { func (p *provider) GetUserByEmail(email string) (models.User, error) {
var user models.User var user models.User
result := p.db.Where("email = ?", email).First(&user) result := p.db.Where("email = ?", email).First(&user)
if result.Error != nil { if result.Error != nil {
return user, result.Error return user, result.Error
} }
@@ -110,7 +109,6 @@ func (p *provider) GetUserByID(id string) (models.User, error) {
var user models.User var user models.User
result := p.db.Where("id = ?", id).First(&user) result := p.db.Where("id = ?", id).First(&user)
if result.Error != nil { if result.Error != nil {
return user, result.Error return user, result.Error
} }

View File

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

30
server/env/env.go vendored
View File

@@ -70,6 +70,8 @@ func InitAllEnv() error {
osFacebookClientSecret := os.Getenv(constants.EnvKeyFacebookClientSecret) osFacebookClientSecret := os.Getenv(constants.EnvKeyFacebookClientSecret)
osLinkedInClientID := os.Getenv(constants.EnvKeyLinkedInClientID) osLinkedInClientID := os.Getenv(constants.EnvKeyLinkedInClientID)
osLinkedInClientSecret := os.Getenv(constants.EnvKeyLinkedInClientSecret) osLinkedInClientSecret := os.Getenv(constants.EnvKeyLinkedInClientSecret)
osAppleClientID := os.Getenv(constants.EnvKeyAppleClientID)
osAppleClientSecret := os.Getenv(constants.EnvKeyAppleClientSecret)
osResetPasswordURL := os.Getenv(constants.EnvKeyResetPasswordURL) osResetPasswordURL := os.Getenv(constants.EnvKeyResetPasswordURL)
osOrganizationName := os.Getenv(constants.EnvKeyOrganizationName) osOrganizationName := os.Getenv(constants.EnvKeyOrganizationName)
osOrganizationLogo := os.Getenv(constants.EnvKeyOrganizationLogo) osOrganizationLogo := os.Getenv(constants.EnvKeyOrganizationLogo)
@@ -81,6 +83,7 @@ func InitAllEnv() error {
osDisableLoginPage := os.Getenv(constants.EnvKeyDisableLoginPage) osDisableLoginPage := os.Getenv(constants.EnvKeyDisableLoginPage)
osDisableSignUp := os.Getenv(constants.EnvKeyDisableSignUp) osDisableSignUp := os.Getenv(constants.EnvKeyDisableSignUp)
osDisableRedisForEnv := os.Getenv(constants.EnvKeyDisableRedisForEnv) osDisableRedisForEnv := os.Getenv(constants.EnvKeyDisableRedisForEnv)
osDisableStrongPassword := os.Getenv(constants.EnvKeyDisableStrongPassword)
// os slice vars // os slice vars
osAllowedOrigins := os.Getenv(constants.EnvKeyAllowedOrigins) osAllowedOrigins := os.Getenv(constants.EnvKeyAllowedOrigins)
@@ -361,6 +364,20 @@ func InitAllEnv() error {
envData[constants.EnvKeyLinkedInClientSecret] = osLinkedInClientSecret envData[constants.EnvKeyLinkedInClientSecret] = osLinkedInClientSecret
} }
if val, ok := envData[constants.EnvKeyAppleClientID]; !ok || val == "" {
envData[constants.EnvKeyAppleClientID] = osAppleClientID
}
if osFacebookClientID != "" && envData[constants.EnvKeyAppleClientID] != osFacebookClientID {
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 == "" { if val, ok := envData[constants.EnvKeyResetPasswordURL]; !ok || val == "" {
envData[constants.EnvKeyResetPasswordURL] = strings.TrimPrefix(osResetPasswordURL, "/") envData[constants.EnvKeyResetPasswordURL] = strings.TrimPrefix(osResetPasswordURL, "/")
} }
@@ -460,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 // 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] == "" { if envData[constants.EnvKeySmtpHost] == "" || envData[constants.EnvKeySmtpUsername] == "" || envData[constants.EnvKeySmtpPassword] == "" || envData[constants.EnvKeySenderEmail] == "" && envData[constants.EnvKeySmtpPort] == "" {
envData[constants.EnvKeyDisableEmailVerification] = true envData[constants.EnvKeyDisableEmailVerification] = true

View File

@@ -198,7 +198,7 @@ func PersistEnv() error {
envValue := strings.TrimSpace(os.Getenv(key)) envValue := strings.TrimSpace(os.Getenv(key))
if envValue != "" { if envValue != "" {
switch key { 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 envValueBool, err := strconv.ParseBool(envValue); err == nil {
if value.(bool) != envValueBool { if value.(bool) != envValueBool {
storeData[key] = envValueBool storeData[key] = envValueBool

View File

@@ -57,6 +57,8 @@ type ComplexityRoot struct {
AdminSecret func(childComplexity int) int AdminSecret func(childComplexity int) int
AllowedOrigins func(childComplexity int) int AllowedOrigins func(childComplexity int) int
AppURL func(childComplexity int) int AppURL func(childComplexity int) int
AppleClientID func(childComplexity int) int
AppleClientSecret func(childComplexity int) int
ClientID func(childComplexity int) int ClientID func(childComplexity int) int
ClientSecret func(childComplexity int) int ClientSecret func(childComplexity int) int
CustomAccessTokenScript func(childComplexity int) int CustomAccessTokenScript func(childComplexity int) int
@@ -74,6 +76,7 @@ type ComplexityRoot struct {
DisableMagicLinkLogin func(childComplexity int) int DisableMagicLinkLogin func(childComplexity int) int
DisableRedisForEnv func(childComplexity int) int DisableRedisForEnv func(childComplexity int) int
DisableSignUp func(childComplexity int) int DisableSignUp func(childComplexity int) int
DisableStrongPassword func(childComplexity int) int
FacebookClientID func(childComplexity int) int FacebookClientID func(childComplexity int) int
FacebookClientSecret func(childComplexity int) int FacebookClientSecret func(childComplexity int) int
GithubClientID func(childComplexity int) int GithubClientID func(childComplexity int) int
@@ -113,6 +116,7 @@ type ComplexityRoot struct {
Meta struct { Meta struct {
ClientID func(childComplexity int) int ClientID func(childComplexity int) int
IsAppleLoginEnabled func(childComplexity int) int
IsBasicAuthenticationEnabled func(childComplexity int) int IsBasicAuthenticationEnabled func(childComplexity int) int
IsEmailVerificationEnabled func(childComplexity int) int IsEmailVerificationEnabled func(childComplexity int) int
IsFacebookLoginEnabled func(childComplexity int) int IsFacebookLoginEnabled func(childComplexity int) int
@@ -121,6 +125,7 @@ type ComplexityRoot struct {
IsLinkedinLoginEnabled func(childComplexity int) int IsLinkedinLoginEnabled func(childComplexity int) int
IsMagicLinkLoginEnabled func(childComplexity int) int IsMagicLinkLoginEnabled func(childComplexity int) int
IsSignUpEnabled func(childComplexity int) int IsSignUpEnabled func(childComplexity int) int
IsStrongPasswordEnabled func(childComplexity int) int
Version func(childComplexity int) int Version func(childComplexity int) int
} }
@@ -335,6 +340,20 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in
return e.complexity.Env.AppURL(childComplexity), true return e.complexity.Env.AppURL(childComplexity), true
case "Env.APPLE_CLIENT_ID":
if e.complexity.Env.AppleClientID == nil {
break
}
return e.complexity.Env.AppleClientID(childComplexity), true
case "Env.APPLE_CLIENT_SECRET":
if e.complexity.Env.AppleClientSecret == nil {
break
}
return e.complexity.Env.AppleClientSecret(childComplexity), true
case "Env.CLIENT_ID": case "Env.CLIENT_ID":
if e.complexity.Env.ClientID == nil { if e.complexity.Env.ClientID == nil {
break break
@@ -454,6 +473,13 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in
return e.complexity.Env.DisableSignUp(childComplexity), true return e.complexity.Env.DisableSignUp(childComplexity), true
case "Env.DISABLE_STRONG_PASSWORD":
if e.complexity.Env.DisableStrongPassword == nil {
break
}
return e.complexity.Env.DisableStrongPassword(childComplexity), true
case "Env.FACEBOOK_CLIENT_ID": case "Env.FACEBOOK_CLIENT_ID":
if e.complexity.Env.FacebookClientID == nil { if e.complexity.Env.FacebookClientID == nil {
break break
@@ -664,6 +690,13 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in
return e.complexity.Meta.ClientID(childComplexity), true return e.complexity.Meta.ClientID(childComplexity), true
case "Meta.is_apple_login_enabled":
if e.complexity.Meta.IsAppleLoginEnabled == nil {
break
}
return e.complexity.Meta.IsAppleLoginEnabled(childComplexity), true
case "Meta.is_basic_authentication_enabled": case "Meta.is_basic_authentication_enabled":
if e.complexity.Meta.IsBasicAuthenticationEnabled == nil { if e.complexity.Meta.IsBasicAuthenticationEnabled == nil {
break break
@@ -720,6 +753,13 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in
return e.complexity.Meta.IsSignUpEnabled(childComplexity), true return e.complexity.Meta.IsSignUpEnabled(childComplexity), true
case "Meta.is_strong_password_enabled":
if e.complexity.Meta.IsStrongPasswordEnabled == nil {
break
}
return e.complexity.Meta.IsStrongPasswordEnabled(childComplexity), true
case "Meta.version": case "Meta.version":
if e.complexity.Meta.Version == nil { if e.complexity.Meta.Version == nil {
break break
@@ -1377,10 +1417,12 @@ type Meta {
is_facebook_login_enabled: Boolean! is_facebook_login_enabled: Boolean!
is_github_login_enabled: Boolean! is_github_login_enabled: Boolean!
is_linkedin_login_enabled: Boolean! is_linkedin_login_enabled: Boolean!
is_apple_login_enabled: Boolean!
is_email_verification_enabled: Boolean! is_email_verification_enabled: Boolean!
is_basic_authentication_enabled: Boolean! is_basic_authentication_enabled: Boolean!
is_magic_link_login_enabled: Boolean! is_magic_link_login_enabled: Boolean!
is_sign_up_enabled: Boolean! is_sign_up_enabled: Boolean!
is_strong_password_enabled: Boolean!
} }
type User { type User {
@@ -1477,6 +1519,7 @@ type Env {
DISABLE_LOGIN_PAGE: Boolean! DISABLE_LOGIN_PAGE: Boolean!
DISABLE_SIGN_UP: Boolean! DISABLE_SIGN_UP: Boolean!
DISABLE_REDIS_FOR_ENV: Boolean! DISABLE_REDIS_FOR_ENV: Boolean!
DISABLE_STRONG_PASSWORD: Boolean!
ROLES: [String!] ROLES: [String!]
PROTECTED_ROLES: [String!] PROTECTED_ROLES: [String!]
DEFAULT_ROLES: [String!] DEFAULT_ROLES: [String!]
@@ -1489,6 +1532,8 @@ type Env {
FACEBOOK_CLIENT_SECRET: String FACEBOOK_CLIENT_SECRET: String
LINKEDIN_CLIENT_ID: String LINKEDIN_CLIENT_ID: String
LINKEDIN_CLIENT_SECRET: String LINKEDIN_CLIENT_SECRET: String
APPLE_CLIENT_ID: String
APPLE_CLIENT_SECRET: String
ORGANIZATION_NAME: String ORGANIZATION_NAME: String
ORGANIZATION_LOGO: String ORGANIZATION_LOGO: String
} }
@@ -1526,6 +1571,7 @@ input UpdateEnvInput {
DISABLE_LOGIN_PAGE: Boolean DISABLE_LOGIN_PAGE: Boolean
DISABLE_SIGN_UP: Boolean DISABLE_SIGN_UP: Boolean
DISABLE_REDIS_FOR_ENV: Boolean DISABLE_REDIS_FOR_ENV: Boolean
DISABLE_STRONG_PASSWORD: Boolean
ROLES: [String!] ROLES: [String!]
PROTECTED_ROLES: [String!] PROTECTED_ROLES: [String!]
DEFAULT_ROLES: [String!] DEFAULT_ROLES: [String!]
@@ -1538,6 +1584,8 @@ input UpdateEnvInput {
FACEBOOK_CLIENT_SECRET: String FACEBOOK_CLIENT_SECRET: String
LINKEDIN_CLIENT_ID: String LINKEDIN_CLIENT_ID: String
LINKEDIN_CLIENT_SECRET: String LINKEDIN_CLIENT_SECRET: String
APPLE_CLIENT_ID: String
APPLE_CLIENT_SECRET: String
ORGANIZATION_NAME: String ORGANIZATION_NAME: String
ORGANIZATION_LOGO: String ORGANIZATION_LOGO: String
} }
@@ -3311,6 +3359,41 @@ func (ec *executionContext) _Env_DISABLE_REDIS_FOR_ENV(ctx context.Context, fiel
return ec.marshalNBoolean2bool(ctx, field.Selections, res) return ec.marshalNBoolean2bool(ctx, field.Selections, res)
} }
func (ec *executionContext) _Env_DISABLE_STRONG_PASSWORD(ctx context.Context, field graphql.CollectedField, obj *model.Env) (ret graphql.Marshaler) {
defer func() {
if r := recover(); r != nil {
ec.Error(ctx, ec.Recover(ctx, r))
ret = graphql.Null
}
}()
fc := &graphql.FieldContext{
Object: "Env",
Field: field,
Args: nil,
IsMethod: false,
IsResolver: false,
}
ctx = graphql.WithFieldContext(ctx, fc)
resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) {
ctx = rctx // use context from middleware stack in children
return obj.DisableStrongPassword, nil
})
if err != nil {
ec.Error(ctx, err)
return graphql.Null
}
if resTmp == nil {
if !graphql.HasFieldError(ctx, fc) {
ec.Errorf(ctx, "must not be null")
}
return graphql.Null
}
res := resTmp.(bool)
fc.Result = res
return ec.marshalNBoolean2bool(ctx, field.Selections, res)
}
func (ec *executionContext) _Env_ROLES(ctx context.Context, field graphql.CollectedField, obj *model.Env) (ret graphql.Marshaler) { func (ec *executionContext) _Env_ROLES(ctx context.Context, field graphql.CollectedField, obj *model.Env) (ret graphql.Marshaler) {
defer func() { defer func() {
if r := recover(); r != nil { if r := recover(); r != nil {
@@ -3695,6 +3778,70 @@ func (ec *executionContext) _Env_LINKEDIN_CLIENT_SECRET(ctx context.Context, fie
return ec.marshalOString2ᚖstring(ctx, field.Selections, res) return ec.marshalOString2ᚖstring(ctx, field.Selections, res)
} }
func (ec *executionContext) _Env_APPLE_CLIENT_ID(ctx context.Context, field graphql.CollectedField, obj *model.Env) (ret graphql.Marshaler) {
defer func() {
if r := recover(); r != nil {
ec.Error(ctx, ec.Recover(ctx, r))
ret = graphql.Null
}
}()
fc := &graphql.FieldContext{
Object: "Env",
Field: field,
Args: nil,
IsMethod: false,
IsResolver: false,
}
ctx = graphql.WithFieldContext(ctx, fc)
resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) {
ctx = rctx // use context from middleware stack in children
return obj.AppleClientID, nil
})
if err != nil {
ec.Error(ctx, err)
return graphql.Null
}
if resTmp == nil {
return graphql.Null
}
res := resTmp.(*string)
fc.Result = res
return ec.marshalOString2ᚖstring(ctx, field.Selections, res)
}
func (ec *executionContext) _Env_APPLE_CLIENT_SECRET(ctx context.Context, field graphql.CollectedField, obj *model.Env) (ret graphql.Marshaler) {
defer func() {
if r := recover(); r != nil {
ec.Error(ctx, ec.Recover(ctx, r))
ret = graphql.Null
}
}()
fc := &graphql.FieldContext{
Object: "Env",
Field: field,
Args: nil,
IsMethod: false,
IsResolver: false,
}
ctx = graphql.WithFieldContext(ctx, fc)
resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) {
ctx = rctx // use context from middleware stack in children
return obj.AppleClientSecret, nil
})
if err != nil {
ec.Error(ctx, err)
return graphql.Null
}
if resTmp == nil {
return graphql.Null
}
res := resTmp.(*string)
fc.Result = res
return ec.marshalOString2ᚖstring(ctx, field.Selections, res)
}
func (ec *executionContext) _Env_ORGANIZATION_NAME(ctx context.Context, field graphql.CollectedField, obj *model.Env) (ret graphql.Marshaler) { func (ec *executionContext) _Env_ORGANIZATION_NAME(ctx context.Context, field graphql.CollectedField, obj *model.Env) (ret graphql.Marshaler) {
defer func() { defer func() {
if r := recover(); r != nil { if r := recover(); r != nil {
@@ -4135,6 +4282,41 @@ func (ec *executionContext) _Meta_is_linkedin_login_enabled(ctx context.Context,
return ec.marshalNBoolean2bool(ctx, field.Selections, res) return ec.marshalNBoolean2bool(ctx, field.Selections, res)
} }
func (ec *executionContext) _Meta_is_apple_login_enabled(ctx context.Context, field graphql.CollectedField, obj *model.Meta) (ret graphql.Marshaler) {
defer func() {
if r := recover(); r != nil {
ec.Error(ctx, ec.Recover(ctx, r))
ret = graphql.Null
}
}()
fc := &graphql.FieldContext{
Object: "Meta",
Field: field,
Args: nil,
IsMethod: false,
IsResolver: false,
}
ctx = graphql.WithFieldContext(ctx, fc)
resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) {
ctx = rctx // use context from middleware stack in children
return obj.IsAppleLoginEnabled, nil
})
if err != nil {
ec.Error(ctx, err)
return graphql.Null
}
if resTmp == nil {
if !graphql.HasFieldError(ctx, fc) {
ec.Errorf(ctx, "must not be null")
}
return graphql.Null
}
res := resTmp.(bool)
fc.Result = res
return ec.marshalNBoolean2bool(ctx, field.Selections, res)
}
func (ec *executionContext) _Meta_is_email_verification_enabled(ctx context.Context, field graphql.CollectedField, obj *model.Meta) (ret graphql.Marshaler) { func (ec *executionContext) _Meta_is_email_verification_enabled(ctx context.Context, field graphql.CollectedField, obj *model.Meta) (ret graphql.Marshaler) {
defer func() { defer func() {
if r := recover(); r != nil { if r := recover(); r != nil {
@@ -4275,6 +4457,41 @@ func (ec *executionContext) _Meta_is_sign_up_enabled(ctx context.Context, field
return ec.marshalNBoolean2bool(ctx, field.Selections, res) return ec.marshalNBoolean2bool(ctx, field.Selections, res)
} }
func (ec *executionContext) _Meta_is_strong_password_enabled(ctx context.Context, field graphql.CollectedField, obj *model.Meta) (ret graphql.Marshaler) {
defer func() {
if r := recover(); r != nil {
ec.Error(ctx, ec.Recover(ctx, r))
ret = graphql.Null
}
}()
fc := &graphql.FieldContext{
Object: "Meta",
Field: field,
Args: nil,
IsMethod: false,
IsResolver: false,
}
ctx = graphql.WithFieldContext(ctx, fc)
resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) {
ctx = rctx // use context from middleware stack in children
return obj.IsStrongPasswordEnabled, nil
})
if err != nil {
ec.Error(ctx, err)
return graphql.Null
}
if resTmp == nil {
if !graphql.HasFieldError(ctx, fc) {
ec.Errorf(ctx, "must not be null")
}
return graphql.Null
}
res := resTmp.(bool)
fc.Result = res
return ec.marshalNBoolean2bool(ctx, field.Selections, res)
}
func (ec *executionContext) _Mutation_signup(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) { func (ec *executionContext) _Mutation_signup(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) {
defer func() { defer func() {
if r := recover(); r != nil { if r := recover(); r != nil {
@@ -8611,6 +8828,14 @@ func (ec *executionContext) unmarshalInputUpdateEnvInput(ctx context.Context, ob
if err != nil { if err != nil {
return it, err return it, err
} }
case "DISABLE_STRONG_PASSWORD":
var err error
ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("DISABLE_STRONG_PASSWORD"))
it.DisableStrongPassword, err = ec.unmarshalOBoolean2ᚖbool(ctx, v)
if err != nil {
return it, err
}
case "ROLES": case "ROLES":
var err error var err error
@@ -8707,6 +8932,22 @@ func (ec *executionContext) unmarshalInputUpdateEnvInput(ctx context.Context, ob
if err != nil { if err != nil {
return it, err return it, err
} }
case "APPLE_CLIENT_ID":
var err error
ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("APPLE_CLIENT_ID"))
it.AppleClientID, err = ec.unmarshalOString2ᚖstring(ctx, v)
if err != nil {
return it, err
}
case "APPLE_CLIENT_SECRET":
var err error
ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("APPLE_CLIENT_SECRET"))
it.AppleClientSecret, err = ec.unmarshalOString2ᚖstring(ctx, v)
if err != nil {
return it, err
}
case "ORGANIZATION_NAME": case "ORGANIZATION_NAME":
var err error var err error
@@ -9155,6 +9396,11 @@ func (ec *executionContext) _Env(ctx context.Context, sel ast.SelectionSet, obj
if out.Values[i] == graphql.Null { if out.Values[i] == graphql.Null {
invalids++ invalids++
} }
case "DISABLE_STRONG_PASSWORD":
out.Values[i] = ec._Env_DISABLE_STRONG_PASSWORD(ctx, field, obj)
if out.Values[i] == graphql.Null {
invalids++
}
case "ROLES": case "ROLES":
out.Values[i] = ec._Env_ROLES(ctx, field, obj) out.Values[i] = ec._Env_ROLES(ctx, field, obj)
case "PROTECTED_ROLES": case "PROTECTED_ROLES":
@@ -9179,6 +9425,10 @@ func (ec *executionContext) _Env(ctx context.Context, sel ast.SelectionSet, obj
out.Values[i] = ec._Env_LINKEDIN_CLIENT_ID(ctx, field, obj) out.Values[i] = ec._Env_LINKEDIN_CLIENT_ID(ctx, field, obj)
case "LINKEDIN_CLIENT_SECRET": case "LINKEDIN_CLIENT_SECRET":
out.Values[i] = ec._Env_LINKEDIN_CLIENT_SECRET(ctx, field, obj) out.Values[i] = ec._Env_LINKEDIN_CLIENT_SECRET(ctx, field, obj)
case "APPLE_CLIENT_ID":
out.Values[i] = ec._Env_APPLE_CLIENT_ID(ctx, field, obj)
case "APPLE_CLIENT_SECRET":
out.Values[i] = ec._Env_APPLE_CLIENT_SECRET(ctx, field, obj)
case "ORGANIZATION_NAME": case "ORGANIZATION_NAME":
out.Values[i] = ec._Env_ORGANIZATION_NAME(ctx, field, obj) out.Values[i] = ec._Env_ORGANIZATION_NAME(ctx, field, obj)
case "ORGANIZATION_LOGO": case "ORGANIZATION_LOGO":
@@ -9295,6 +9545,11 @@ func (ec *executionContext) _Meta(ctx context.Context, sel ast.SelectionSet, obj
if out.Values[i] == graphql.Null { if out.Values[i] == graphql.Null {
invalids++ invalids++
} }
case "is_apple_login_enabled":
out.Values[i] = ec._Meta_is_apple_login_enabled(ctx, field, obj)
if out.Values[i] == graphql.Null {
invalids++
}
case "is_email_verification_enabled": case "is_email_verification_enabled":
out.Values[i] = ec._Meta_is_email_verification_enabled(ctx, field, obj) out.Values[i] = ec._Meta_is_email_verification_enabled(ctx, field, obj)
if out.Values[i] == graphql.Null { if out.Values[i] == graphql.Null {
@@ -9315,6 +9570,11 @@ func (ec *executionContext) _Meta(ctx context.Context, sel ast.SelectionSet, obj
if out.Values[i] == graphql.Null { if out.Values[i] == graphql.Null {
invalids++ invalids++
} }
case "is_strong_password_enabled":
out.Values[i] = ec._Meta_is_strong_password_enabled(ctx, field, obj)
if out.Values[i] == graphql.Null {
invalids++
}
default: default:
panic("unknown field " + strconv.Quote(field.Name)) panic("unknown field " + strconv.Quote(field.Name))
} }

View File

@@ -55,6 +55,7 @@ type Env struct {
DisableLoginPage bool `json:"DISABLE_LOGIN_PAGE"` DisableLoginPage bool `json:"DISABLE_LOGIN_PAGE"`
DisableSignUp bool `json:"DISABLE_SIGN_UP"` DisableSignUp bool `json:"DISABLE_SIGN_UP"`
DisableRedisForEnv bool `json:"DISABLE_REDIS_FOR_ENV"` DisableRedisForEnv bool `json:"DISABLE_REDIS_FOR_ENV"`
DisableStrongPassword bool `json:"DISABLE_STRONG_PASSWORD"`
Roles []string `json:"ROLES"` Roles []string `json:"ROLES"`
ProtectedRoles []string `json:"PROTECTED_ROLES"` ProtectedRoles []string `json:"PROTECTED_ROLES"`
DefaultRoles []string `json:"DEFAULT_ROLES"` DefaultRoles []string `json:"DEFAULT_ROLES"`
@@ -67,6 +68,8 @@ type Env struct {
FacebookClientSecret *string `json:"FACEBOOK_CLIENT_SECRET"` FacebookClientSecret *string `json:"FACEBOOK_CLIENT_SECRET"`
LinkedinClientID *string `json:"LINKEDIN_CLIENT_ID"` LinkedinClientID *string `json:"LINKEDIN_CLIENT_ID"`
LinkedinClientSecret *string `json:"LINKEDIN_CLIENT_SECRET"` LinkedinClientSecret *string `json:"LINKEDIN_CLIENT_SECRET"`
AppleClientID *string `json:"APPLE_CLIENT_ID"`
AppleClientSecret *string `json:"APPLE_CLIENT_SECRET"`
OrganizationName *string `json:"ORGANIZATION_NAME"` OrganizationName *string `json:"ORGANIZATION_NAME"`
OrganizationLogo *string `json:"ORGANIZATION_LOGO"` OrganizationLogo *string `json:"ORGANIZATION_LOGO"`
} }
@@ -119,10 +122,12 @@ type Meta struct {
IsFacebookLoginEnabled bool `json:"is_facebook_login_enabled"` IsFacebookLoginEnabled bool `json:"is_facebook_login_enabled"`
IsGithubLoginEnabled bool `json:"is_github_login_enabled"` IsGithubLoginEnabled bool `json:"is_github_login_enabled"`
IsLinkedinLoginEnabled bool `json:"is_linkedin_login_enabled"` IsLinkedinLoginEnabled bool `json:"is_linkedin_login_enabled"`
IsAppleLoginEnabled bool `json:"is_apple_login_enabled"`
IsEmailVerificationEnabled bool `json:"is_email_verification_enabled"` IsEmailVerificationEnabled bool `json:"is_email_verification_enabled"`
IsBasicAuthenticationEnabled bool `json:"is_basic_authentication_enabled"` IsBasicAuthenticationEnabled bool `json:"is_basic_authentication_enabled"`
IsMagicLinkLoginEnabled bool `json:"is_magic_link_login_enabled"` IsMagicLinkLoginEnabled bool `json:"is_magic_link_login_enabled"`
IsSignUpEnabled bool `json:"is_sign_up_enabled"` IsSignUpEnabled bool `json:"is_sign_up_enabled"`
IsStrongPasswordEnabled bool `json:"is_strong_password_enabled"`
} }
type OAuthRevokeInput struct { type OAuthRevokeInput struct {
@@ -209,6 +214,7 @@ type UpdateEnvInput struct {
DisableLoginPage *bool `json:"DISABLE_LOGIN_PAGE"` DisableLoginPage *bool `json:"DISABLE_LOGIN_PAGE"`
DisableSignUp *bool `json:"DISABLE_SIGN_UP"` DisableSignUp *bool `json:"DISABLE_SIGN_UP"`
DisableRedisForEnv *bool `json:"DISABLE_REDIS_FOR_ENV"` DisableRedisForEnv *bool `json:"DISABLE_REDIS_FOR_ENV"`
DisableStrongPassword *bool `json:"DISABLE_STRONG_PASSWORD"`
Roles []string `json:"ROLES"` Roles []string `json:"ROLES"`
ProtectedRoles []string `json:"PROTECTED_ROLES"` ProtectedRoles []string `json:"PROTECTED_ROLES"`
DefaultRoles []string `json:"DEFAULT_ROLES"` DefaultRoles []string `json:"DEFAULT_ROLES"`
@@ -221,6 +227,8 @@ type UpdateEnvInput struct {
FacebookClientSecret *string `json:"FACEBOOK_CLIENT_SECRET"` FacebookClientSecret *string `json:"FACEBOOK_CLIENT_SECRET"`
LinkedinClientID *string `json:"LINKEDIN_CLIENT_ID"` LinkedinClientID *string `json:"LINKEDIN_CLIENT_ID"`
LinkedinClientSecret *string `json:"LINKEDIN_CLIENT_SECRET"` LinkedinClientSecret *string `json:"LINKEDIN_CLIENT_SECRET"`
AppleClientID *string `json:"APPLE_CLIENT_ID"`
AppleClientSecret *string `json:"APPLE_CLIENT_SECRET"`
OrganizationName *string `json:"ORGANIZATION_NAME"` OrganizationName *string `json:"ORGANIZATION_NAME"`
OrganizationLogo *string `json:"ORGANIZATION_LOGO"` OrganizationLogo *string `json:"ORGANIZATION_LOGO"`
} }

View File

@@ -19,10 +19,12 @@ type Meta {
is_facebook_login_enabled: Boolean! is_facebook_login_enabled: Boolean!
is_github_login_enabled: Boolean! is_github_login_enabled: Boolean!
is_linkedin_login_enabled: Boolean! is_linkedin_login_enabled: Boolean!
is_apple_login_enabled: Boolean!
is_email_verification_enabled: Boolean! is_email_verification_enabled: Boolean!
is_basic_authentication_enabled: Boolean! is_basic_authentication_enabled: Boolean!
is_magic_link_login_enabled: Boolean! is_magic_link_login_enabled: Boolean!
is_sign_up_enabled: Boolean! is_sign_up_enabled: Boolean!
is_strong_password_enabled: Boolean!
} }
type User { type User {
@@ -119,6 +121,7 @@ type Env {
DISABLE_LOGIN_PAGE: Boolean! DISABLE_LOGIN_PAGE: Boolean!
DISABLE_SIGN_UP: Boolean! DISABLE_SIGN_UP: Boolean!
DISABLE_REDIS_FOR_ENV: Boolean! DISABLE_REDIS_FOR_ENV: Boolean!
DISABLE_STRONG_PASSWORD: Boolean!
ROLES: [String!] ROLES: [String!]
PROTECTED_ROLES: [String!] PROTECTED_ROLES: [String!]
DEFAULT_ROLES: [String!] DEFAULT_ROLES: [String!]
@@ -131,6 +134,8 @@ type Env {
FACEBOOK_CLIENT_SECRET: String FACEBOOK_CLIENT_SECRET: String
LINKEDIN_CLIENT_ID: String LINKEDIN_CLIENT_ID: String
LINKEDIN_CLIENT_SECRET: String LINKEDIN_CLIENT_SECRET: String
APPLE_CLIENT_ID: String
APPLE_CLIENT_SECRET: String
ORGANIZATION_NAME: String ORGANIZATION_NAME: String
ORGANIZATION_LOGO: String ORGANIZATION_LOGO: String
} }
@@ -168,6 +173,7 @@ input UpdateEnvInput {
DISABLE_LOGIN_PAGE: Boolean DISABLE_LOGIN_PAGE: Boolean
DISABLE_SIGN_UP: Boolean DISABLE_SIGN_UP: Boolean
DISABLE_REDIS_FOR_ENV: Boolean DISABLE_REDIS_FOR_ENV: Boolean
DISABLE_STRONG_PASSWORD: Boolean
ROLES: [String!] ROLES: [String!]
PROTECTED_ROLES: [String!] PROTECTED_ROLES: [String!]
DEFAULT_ROLES: [String!] DEFAULT_ROLES: [String!]
@@ -180,6 +186,8 @@ input UpdateEnvInput {
FACEBOOK_CLIENT_SECRET: String FACEBOOK_CLIENT_SECRET: String
LINKEDIN_CLIENT_ID: String LINKEDIN_CLIENT_ID: String
LINKEDIN_CLIENT_SECRET: String LINKEDIN_CLIENT_SECRET: String
APPLE_CLIENT_ID: String
APPLE_CLIENT_SECRET: String
ORGANIZATION_NAME: String ORGANIZATION_NAME: String
ORGANIZATION_LOGO: String ORGANIZATION_LOGO: String
} }

View File

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

View File

@@ -1,6 +1,7 @@
package handlers package handlers
import ( import (
"encoding/json"
"net/http" "net/http"
"strings" "strings"
@@ -10,6 +11,7 @@ import (
"github.com/authorizerdev/authorizer/server/cookie" "github.com/authorizerdev/authorizer/server/cookie"
"github.com/authorizerdev/authorizer/server/crypto" "github.com/authorizerdev/authorizer/server/crypto"
"github.com/authorizerdev/authorizer/server/memorystore" "github.com/authorizerdev/authorizer/server/memorystore"
"github.com/authorizerdev/authorizer/server/token"
) )
// Handler to logout user // Handler to logout user
@@ -35,12 +37,17 @@ func LogoutHandler() gin.HandlerFunc {
return return
} }
fingerPrint := string(decryptedFingerPrint) var sessionData token.SessionData
err = json.Unmarshal([]byte(decryptedFingerPrint), &sessionData)
err = memorystore.Provider.RemoveState(fingerPrint)
if err != nil { 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) cookie.DeleteSession(gc)
if redirectURL != "" { if redirectURL != "" {

View File

@@ -2,6 +2,7 @@ package handlers
import ( import (
"context" "context"
"encoding/base64"
"encoding/json" "encoding/json"
"fmt" "fmt"
"io/ioutil" "io/ioutil"
@@ -36,7 +37,6 @@ func OAuthCallbackHandler() gin.HandlerFunc {
log.Debug("Invalid oauth state: ", state) log.Debug("Invalid oauth state: ", state)
c.JSON(400, gin.H{"error": "invalid oauth state"}) c.JSON(400, gin.H{"error": "invalid oauth state"})
} }
memorystore.Provider.GetState(state)
// contains random token, redirect url, role // contains random token, redirect url, role
sessionSplit := strings.Split(state, "___") sessionSplit := strings.Split(state, "___")
@@ -46,6 +46,9 @@ func OAuthCallbackHandler() gin.HandlerFunc {
return return
} }
// remove state from store
go memorystore.Provider.RemoveState(state)
stateValue := sessionSplit[0] stateValue := sessionSplit[0]
redirectURL := sessionSplit[1] redirectURL := sessionSplit[1]
inputRoles := strings.Split(sessionSplit[2], ",") inputRoles := strings.Split(sessionSplit[2], ",")
@@ -54,14 +57,16 @@ func OAuthCallbackHandler() gin.HandlerFunc {
user := models.User{} user := models.User{}
code := c.Request.FormValue("code") code := c.Request.FormValue("code")
switch provider { switch provider {
case constants.SignupMethodGoogle: case constants.AuthRecipeMethodGoogle:
user, err = processGoogleUserInfo(code) user, err = processGoogleUserInfo(code)
case constants.SignupMethodGithub: case constants.AuthRecipeMethodGithub:
user, err = processGithubUserInfo(code) user, err = processGithubUserInfo(code)
case constants.SignupMethodFacebook: case constants.AuthRecipeMethodFacebook:
user, err = processFacebookUserInfo(code) user, err = processFacebookUserInfo(code)
case constants.SignupMethodLinkedIn: case constants.AuthRecipeMethodLinkedIn:
user, err = processLinkedInUserInfo(code) user, err = processLinkedInUserInfo(code)
case constants.AuthRecipeMethodApple:
user, err = processAppleUserInfo(code)
default: default:
log.Info("Invalid oauth provider") log.Info("Invalid oauth provider")
err = fmt.Errorf(`invalid oauth provider`) err = fmt.Errorf(`invalid oauth provider`)
@@ -117,9 +122,11 @@ func OAuthCallbackHandler() gin.HandlerFunc {
user.EmailVerifiedAt = &now user.EmailVerifiedAt = &now
user, _ = db.Provider.AddUser(user) user, _ = db.Provider.AddUser(user)
} else { } else {
user = existingUser
if user.RevokedTimestamp != nil { if user.RevokedTimestamp != nil {
log.Debug("User access revoked at: ", user.RevokedTimestamp) log.Debug("User access revoked at: ", user.RevokedTimestamp)
c.JSON(400, gin.H{"error": "user access has been revoked"}) c.JSON(400, gin.H{"error": "user access has been revoked"})
return
} }
// user exists in db, check if method was google // user exists in db, check if method was google
@@ -128,7 +135,6 @@ func OAuthCallbackHandler() gin.HandlerFunc {
if !strings.Contains(signupMethod, provider) { if !strings.Contains(signupMethod, provider) {
signupMethod = signupMethod + "," + provider signupMethod = signupMethod + "," + provider
} }
user = existingUser
user.SignupMethods = signupMethod user.SignupMethods = signupMethod
if user.EmailVerifiedAt == nil { if user.EmailVerifiedAt == nil {
@@ -186,7 +192,7 @@ func OAuthCallbackHandler() gin.HandlerFunc {
} }
} }
authToken, err := token.CreateAuthToken(c, user, inputRoles, scopes) authToken, err := token.CreateAuthToken(c, user, inputRoles, scopes, provider)
if err != nil { if err != nil {
log.Debug("Failed to create auth token: ", err) log.Debug("Failed to create auth token: ", err)
c.JSON(500, gin.H{"error": err.Error()}) c.JSON(500, gin.H{"error": err.Error()})
@@ -199,13 +205,14 @@ 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 params := "access_token=" + authToken.AccessToken.Token + "&token_type=bearer&expires_in=" + strconv.FormatInt(expiresIn, 10) + "&state=" + stateValue + "&id_token=" + authToken.IDToken.Token
sessionKey := provider + ":" + user.ID
cookie.SetSession(c, authToken.FingerPrintHash) cookie.SetSession(c, authToken.FingerPrintHash)
memorystore.Provider.SetState(authToken.FingerPrintHash, authToken.FingerPrint+"@"+user.ID) memorystore.Provider.SetUserSession(sessionKey, constants.TokenTypeSessionToken+"_"+authToken.FingerPrint, authToken.FingerPrintHash)
memorystore.Provider.SetState(authToken.AccessToken.Token, authToken.FingerPrint+"@"+user.ID) memorystore.Provider.SetUserSession(sessionKey, constants.TokenTypeAccessToken+"_"+authToken.FingerPrint, authToken.AccessToken.Token)
if authToken.RefreshToken != nil { if authToken.RefreshToken != nil {
params = params + `&refresh_token=` + authToken.RefreshToken.Token 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{ go db.Provider.AddSession(models.Session{
@@ -219,7 +226,7 @@ func OAuthCallbackHandler() gin.HandlerFunc {
redirectURL = redirectURL + "?" + strings.TrimPrefix(params, "&") redirectURL = redirectURL + "?" + strings.TrimPrefix(params, "&")
} }
c.Redirect(http.StatusTemporaryRedirect, redirectURL) c.Redirect(http.StatusFound, redirectURL)
} }
} }
@@ -258,7 +265,7 @@ func processGoogleUserInfo(code string) (models.User, error) {
func processGithubUserInfo(code string) (models.User, error) { func processGithubUserInfo(code string) (models.User, error) {
user := models.User{} user := models.User{}
token, err := oauth.OAuthProviders.GithubConfig.Exchange(oauth2.NoContext, code) oauth2Token, err := oauth.OAuthProviders.GithubConfig.Exchange(oauth2.NoContext, code)
if err != nil { if err != nil {
log.Debug("Failed to exchange code for token: ", err) log.Debug("Failed to exchange code for token: ", err)
return user, fmt.Errorf("invalid github exchange code: %s", err.Error()) return user, fmt.Errorf("invalid github exchange code: %s", err.Error())
@@ -270,7 +277,7 @@ func processGithubUserInfo(code string) (models.User, error) {
return user, fmt.Errorf("error creating github user info request: %s", err.Error()) return user, fmt.Errorf("error creating github user info request: %s", err.Error())
} }
req.Header = http.Header{ 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) response, err := client.Do(req)
@@ -286,8 +293,8 @@ func processGithubUserInfo(code string) (models.User, error) {
return user, fmt.Errorf("failed to read github response body: %s", err.Error()) return user, fmt.Errorf("failed to read github response body: %s", err.Error())
} }
if response.StatusCode >= 400 { if response.StatusCode >= 400 {
log.Debug("Failed to request linkedin user info: ", string(body)) log.Debug("Failed to request github user info: ", string(body))
return user, fmt.Errorf("failed to request linkedin user info: %s", string(body)) return user, fmt.Errorf("failed to request github user info: %s", string(body))
} }
userRawData := make(map[string]string) userRawData := make(map[string]string)
@@ -317,13 +324,13 @@ func processGithubUserInfo(code string) (models.User, error) {
func processFacebookUserInfo(code string) (models.User, error) { func processFacebookUserInfo(code string) (models.User, error) {
user := models.User{} user := models.User{}
token, err := oauth.OAuthProviders.FacebookConfig.Exchange(oauth2.NoContext, code) oauth2Token, err := oauth.OAuthProviders.FacebookConfig.Exchange(oauth2.NoContext, code)
if err != nil { if err != nil {
log.Debug("Invalid facebook exchange code: ", err) log.Debug("Invalid facebook exchange code: ", err)
return user, fmt.Errorf("invalid facebook exchange code: %s", err.Error()) return user, fmt.Errorf("invalid facebook exchange code: %s", err.Error())
} }
client := http.Client{} 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 { if err != nil {
log.Debug("Error creating facebook user info request: ", err) log.Debug("Error creating facebook user info request: ", err)
return user, fmt.Errorf("error creating facebook user info request: %s", err.Error()) return user, fmt.Errorf("error creating facebook user info request: %s", err.Error())
@@ -342,8 +349,8 @@ func processFacebookUserInfo(code string) (models.User, error) {
return user, fmt.Errorf("failed to read facebook response body: %s", err.Error()) return user, fmt.Errorf("failed to read facebook response body: %s", err.Error())
} }
if response.StatusCode >= 400 { if response.StatusCode >= 400 {
log.Debug("Failed to request linkedin user info: ", string(body)) log.Debug("Failed to request facebook user info: ", string(body))
return user, fmt.Errorf("failed to request linkedin user info: %s", string(body)) return user, fmt.Errorf("failed to request facebook user info: %s", string(body))
} }
userRawData := make(map[string]interface{}) userRawData := make(map[string]interface{})
json.Unmarshal(body, &userRawData) json.Unmarshal(body, &userRawData)
@@ -368,7 +375,7 @@ func processFacebookUserInfo(code string) (models.User, error) {
func processLinkedInUserInfo(code string) (models.User, error) { func processLinkedInUserInfo(code string) (models.User, error) {
user := models.User{} user := models.User{}
token, err := oauth.OAuthProviders.LinkedInConfig.Exchange(oauth2.NoContext, code) oauth2Token, err := oauth.OAuthProviders.LinkedInConfig.Exchange(oauth2.NoContext, code)
if err != nil { if err != nil {
log.Debug("Failed to exchange code for token: ", err) log.Debug("Failed to exchange code for token: ", err)
return user, fmt.Errorf("invalid linkedin exchange code: %s", err.Error()) return user, fmt.Errorf("invalid linkedin exchange code: %s", err.Error())
@@ -381,7 +388,7 @@ func processLinkedInUserInfo(code string) (models.User, error) {
return user, fmt.Errorf("error creating linkedin user info request: %s", err.Error()) return user, fmt.Errorf("error creating linkedin user info request: %s", err.Error())
} }
req.Header = http.Header{ req.Header = http.Header{
"Authorization": []string{fmt.Sprintf("Bearer %s", token.AccessToken)}, "Authorization": []string{fmt.Sprintf("Bearer %s", oauth2Token.AccessToken)},
} }
response, err := client.Do(req) response, err := client.Do(req)
@@ -411,7 +418,7 @@ func processLinkedInUserInfo(code string) (models.User, error) {
return user, fmt.Errorf("error creating linkedin user info request: %s", err.Error()) return user, fmt.Errorf("error creating linkedin user info request: %s", err.Error())
} }
req.Header = http.Header{ req.Header = http.Header{
"Authorization": []string{fmt.Sprintf("Bearer %s", token.AccessToken)}, "Authorization": []string{fmt.Sprintf("Bearer %s", oauth2Token.AccessToken)},
} }
response, err = client.Do(req) response, err = client.Do(req)
@@ -447,3 +454,56 @@ func processLinkedInUserInfo(code string) (models.User, error) {
return user, nil 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" "github.com/gin-gonic/gin"
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
"golang.org/x/oauth2"
"github.com/authorizerdev/authorizer/server/constants" "github.com/authorizerdev/authorizer/server/constants"
"github.com/authorizerdev/authorizer/server/memorystore" "github.com/authorizerdev/authorizer/server/memorystore"
@@ -99,13 +100,13 @@ func OAuthLoginHandler() gin.HandlerFunc {
provider := c.Param("oauth_provider") provider := c.Param("oauth_provider")
isProviderConfigured := true isProviderConfigured := true
switch provider { switch provider {
case constants.SignupMethodGoogle: case constants.AuthRecipeMethodGoogle:
if oauth.OAuthProviders.GoogleConfig == nil { if oauth.OAuthProviders.GoogleConfig == nil {
log.Debug("Google OAuth provider is not configured") log.Debug("Google OAuth provider is not configured")
isProviderConfigured = false isProviderConfigured = false
break break
} }
err := memorystore.Provider.SetState(oauthStateString, constants.SignupMethodGoogle) err := memorystore.Provider.SetState(oauthStateString, constants.AuthRecipeMethodGoogle)
if err != nil { if err != nil {
log.Debug("Error setting state: ", err) log.Debug("Error setting state: ", err)
c.JSON(500, gin.H{ c.JSON(500, gin.H{
@@ -114,16 +115,16 @@ func OAuthLoginHandler() gin.HandlerFunc {
return return
} }
// during the init of OAuthProvider authorizer url might be empty // during the init of OAuthProvider authorizer url might be empty
oauth.OAuthProviders.GoogleConfig.RedirectURL = hostname + "/oauth_callback/google" oauth.OAuthProviders.GoogleConfig.RedirectURL = hostname + "/oauth_callback/" + constants.AuthRecipeMethodGoogle
url := oauth.OAuthProviders.GoogleConfig.AuthCodeURL(oauthStateString) url := oauth.OAuthProviders.GoogleConfig.AuthCodeURL(oauthStateString)
c.Redirect(http.StatusTemporaryRedirect, url) c.Redirect(http.StatusTemporaryRedirect, url)
case constants.SignupMethodGithub: case constants.AuthRecipeMethodGithub:
if oauth.OAuthProviders.GithubConfig == nil { if oauth.OAuthProviders.GithubConfig == nil {
log.Debug("Github OAuth provider is not configured") log.Debug("Github OAuth provider is not configured")
isProviderConfigured = false isProviderConfigured = false
break break
} }
err := memorystore.Provider.SetState(oauthStateString, constants.SignupMethodGithub) err := memorystore.Provider.SetState(oauthStateString, constants.AuthRecipeMethodGithub)
if err != nil { if err != nil {
log.Debug("Error setting state: ", err) log.Debug("Error setting state: ", err)
c.JSON(500, gin.H{ c.JSON(500, gin.H{
@@ -131,16 +132,16 @@ func OAuthLoginHandler() gin.HandlerFunc {
}) })
return return
} }
oauth.OAuthProviders.GithubConfig.RedirectURL = hostname + "/oauth_callback/github" oauth.OAuthProviders.GithubConfig.RedirectURL = hostname + "/oauth_callback/" + constants.AuthRecipeMethodGithub
url := oauth.OAuthProviders.GithubConfig.AuthCodeURL(oauthStateString) url := oauth.OAuthProviders.GithubConfig.AuthCodeURL(oauthStateString)
c.Redirect(http.StatusTemporaryRedirect, url) c.Redirect(http.StatusTemporaryRedirect, url)
case constants.SignupMethodFacebook: case constants.AuthRecipeMethodFacebook:
if oauth.OAuthProviders.FacebookConfig == nil { if oauth.OAuthProviders.FacebookConfig == nil {
log.Debug("Facebook OAuth provider is not configured") log.Debug("Facebook OAuth provider is not configured")
isProviderConfigured = false isProviderConfigured = false
break break
} }
err := memorystore.Provider.SetState(oauthStateString, constants.SignupMethodFacebook) err := memorystore.Provider.SetState(oauthStateString, constants.AuthRecipeMethodFacebook)
if err != nil { if err != nil {
log.Debug("Error setting state: ", err) log.Debug("Error setting state: ", err)
c.JSON(500, gin.H{ c.JSON(500, gin.H{
@@ -148,16 +149,16 @@ func OAuthLoginHandler() gin.HandlerFunc {
}) })
return return
} }
oauth.OAuthProviders.FacebookConfig.RedirectURL = hostname + "/oauth_callback/facebook" oauth.OAuthProviders.FacebookConfig.RedirectURL = hostname + "/oauth_callback/" + constants.AuthRecipeMethodFacebook
url := oauth.OAuthProviders.FacebookConfig.AuthCodeURL(oauthStateString) url := oauth.OAuthProviders.FacebookConfig.AuthCodeURL(oauthStateString)
c.Redirect(http.StatusTemporaryRedirect, url) c.Redirect(http.StatusTemporaryRedirect, url)
case constants.SignupMethodLinkedIn: case constants.AuthRecipeMethodLinkedIn:
if oauth.OAuthProviders.LinkedInConfig == nil { if oauth.OAuthProviders.LinkedInConfig == nil {
log.Debug("Linkedin OAuth provider is not configured") log.Debug("Linkedin OAuth provider is not configured")
isProviderConfigured = false isProviderConfigured = false
break break
} }
err := memorystore.Provider.SetState(oauthStateString, constants.SignupMethodLinkedIn) err := memorystore.Provider.SetState(oauthStateString, constants.AuthRecipeMethodLinkedIn)
if err != nil { if err != nil {
log.Debug("Error setting state: ", err) log.Debug("Error setting state: ", err)
c.JSON(500, gin.H{ c.JSON(500, gin.H{
@@ -165,9 +166,28 @@ func OAuthLoginHandler() gin.HandlerFunc {
}) })
return return
} }
oauth.OAuthProviders.LinkedInConfig.RedirectURL = hostname + "/oauth_callback/linkedin" oauth.OAuthProviders.LinkedInConfig.RedirectURL = hostname + "/oauth_callback/" + constants.AuthRecipeMethodLinkedIn
url := oauth.OAuthProviders.LinkedInConfig.AuthCodeURL(oauthStateString) url := oauth.OAuthProviders.LinkedInConfig.AuthCodeURL(oauthStateString)
c.Redirect(http.StatusTemporaryRedirect, url) 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: default:
log.Debug("Invalid oauth provider: ", provider) log.Debug("Invalid oauth provider: ", provider)
c.JSON(422, gin.H{ c.JSON(422, gin.H{

View File

@@ -9,6 +9,7 @@ import (
"github.com/authorizerdev/authorizer/server/constants" "github.com/authorizerdev/authorizer/server/constants"
"github.com/authorizerdev/authorizer/server/memorystore" "github.com/authorizerdev/authorizer/server/memorystore"
"github.com/authorizerdev/authorizer/server/token"
) )
// Revoke handler to revoke refresh token // Revoke handler to revoke refresh token
@@ -45,7 +46,24 @@ func RevokeHandler() gin.HandlerFunc {
return 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{ gc.JSON(http.StatusOK, gin.H{
"message": "Token revoked successfully", "message": "Token revoked successfully",

View File

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

View File

@@ -9,6 +9,7 @@ import (
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
"github.com/authorizerdev/authorizer/server/constants"
"github.com/authorizerdev/authorizer/server/cookie" "github.com/authorizerdev/authorizer/server/cookie"
"github.com/authorizerdev/authorizer/server/db" "github.com/authorizerdev/authorizer/server/db"
"github.com/authorizerdev/authorizer/server/db/models" "github.com/authorizerdev/authorizer/server/db/models"
@@ -42,7 +43,7 @@ func VerifyEmailHandler() gin.HandlerFunc {
// verify if token exists in db // verify if token exists in db
hostname := parsers.GetHost(c) hostname := parsers.GetHost(c)
claim, err := token.ParseJWTToken(tokenInQuery, hostname, verificationRequest.Nonce, verificationRequest.Email) claim, err := token.ParseJWTToken(tokenInQuery)
if err != nil { if err != nil {
log.Debug("Error parsing token: ", err) log.Debug("Error parsing token: ", err)
errorRes["error_description"] = err.Error() errorRes["error_description"] = err.Error()
@@ -50,7 +51,14 @@ func VerifyEmailHandler() gin.HandlerFunc {
return 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(verificationRequest.Email)
if err != nil { if err != nil {
log.Debug("Error getting user: ", err) log.Debug("Error getting user: ", err)
errorRes["error_description"] = err.Error() errorRes["error_description"] = err.Error()
@@ -84,7 +92,11 @@ func VerifyEmailHandler() gin.HandlerFunc {
} else { } else {
scope = strings.Split(scopeString, " ") 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 { if err != nil {
log.Debug("Error creating auth token: ", err) log.Debug("Error creating auth token: ", err)
errorRes["error_description"] = err.Error() errorRes["error_description"] = err.Error()
@@ -99,13 +111,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 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) cookie.SetSession(c, authToken.FingerPrintHash)
memorystore.Provider.SetState(authToken.FingerPrintHash, authToken.FingerPrint+"@"+user.ID) memorystore.Provider.SetUserSession(sessionKey, constants.TokenTypeSessionToken+"_"+authToken.FingerPrint, authToken.FingerPrintHash)
memorystore.Provider.SetState(authToken.AccessToken.Token, authToken.FingerPrint+"@"+user.ID) memorystore.Provider.SetUserSession(sessionKey, constants.TokenTypeAccessToken+"_"+authToken.FingerPrint, authToken.AccessToken.Token)
if authToken.RefreshToken != nil { if authToken.RefreshToken != nil {
params = params + `&refresh_token=${refresh_token}` 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)
} }
if redirectURL == "" { if redirectURL == "" {

View File

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

View File

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

View File

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

View File

@@ -1,8 +1,10 @@
package inmemory package stores
import ( import (
"os" "os"
"sync" "sync"
"github.com/authorizerdev/authorizer/server/constants"
) )
// EnvStore struct to store the env variables // EnvStore struct to store the env variables
@@ -11,9 +13,17 @@ type EnvStore struct {
store map[string]interface{} 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 // UpdateEnvStore to update the whole env store object
func (e *EnvStore) UpdateStore(store map[string]interface{}) { func (e *EnvStore) UpdateStore(store map[string]interface{}) {
if os.Getenv("ENV") != "test" { if os.Getenv("ENV") != constants.TestEnv {
e.mutex.Lock() e.mutex.Lock()
defer e.mutex.Unlock() defer e.mutex.Unlock()
} }
@@ -26,26 +36,17 @@ func (e *EnvStore) UpdateStore(store map[string]interface{}) {
// GetStore returns the env store // GetStore returns the env store
func (e *EnvStore) GetStore() map[string]interface{} { func (e *EnvStore) GetStore() map[string]interface{} {
if os.Getenv("ENV") != "test" {
e.mutex.Lock()
defer e.mutex.Unlock()
}
return e.store return e.store
} }
// Get returns the value of the key in evn store // Get returns the value of the key in evn store
func (e *EnvStore) Get(key string) interface{} { func (e *EnvStore) Get(key string) interface{} {
if os.Getenv("ENV") != "test" {
e.mutex.Lock()
defer e.mutex.Unlock()
}
return e.store[key] return e.store[key]
} }
// Set sets the value of the key in env store // Set sets the value of the key in env store
func (e *EnvStore) Set(key string, value interface{}) { func (e *EnvStore) Set(key string, value interface{}) {
if os.Getenv("ENV") != "test" { if os.Getenv("ENV") != constants.TestEnv {
e.mutex.Lock() e.mutex.Lock()
defer e.mutex.Unlock() defer e.mutex.Unlock()
} }

View File

@@ -0,0 +1,83 @@
package stores
import (
"os"
"strings"
"sync"
"github.com/authorizerdev/authorizer/server/constants"
)
// 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) {
if os.Getenv("ENV") != constants.TestEnv {
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) {
if os.Getenv("ENV") != constants.TestEnv {
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) {
if os.Getenv("ENV") != constants.TestEnv {
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 {
if os.Getenv("ENV") != constants.TestEnv {
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,46 @@
package stores
import (
"os"
"sync"
"github.com/authorizerdev/authorizer/server/constants"
)
// 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) {
if os.Getenv("ENV") != constants.TestEnv {
s.mutex.Lock()
defer s.mutex.Unlock()
}
s.store[key] = value
}
// Remove removes the key from state store
func (s *StateStore) Remove(key string) {
if os.Getenv("ENV") != constants.TestEnv {
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 // Provider defines current memory store provider
type Provider interface { 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 // DeleteAllSessions deletes all the sessions from the session store
DeleteAllUserSession(userId string) error DeleteAllUserSessions(userId string) error
// GetUserSessions returns all the user sessions from the session store // DeleteSessionForNamespace deletes the session for a given namespace
GetUserSessions(userId string) map[string]string DeleteSessionForNamespace(namespace string) error
// ClearStore clears the session store for authorizer tokens
ClearStore() error
// SetState sets the login state (key, value form) in the session store // SetState sets the login state (key, value form) in the session store
SetState(key, state string) error SetState(key, state string) error
// GetState returns the state from the session store // GetState returns the state from the session store

View File

@@ -2,58 +2,106 @@ package redis
import ( import (
"strconv" "strconv"
"strings"
"github.com/authorizerdev/authorizer/server/constants" "github.com/authorizerdev/authorizer/server/constants"
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
) )
var ( var (
// session store prefix // state store prefix
sessionStorePrefix = "authorizer_session:" stateStorePrefix = "authorizer_state:"
// env store prefix // env store prefix
envStorePrefix = "authorizer_env" envStorePrefix = "authorizer_env"
) )
// ClearStore clears the redis store for authorizer related tokens // SetUserSession sets the user session in redis store.
func (c *provider) ClearStore() error { func (c *provider) SetUserSession(userId, key, token string) error {
err := c.store.Del(c.ctx, sessionStorePrefix+"*").Err() err := c.store.HSet(c.ctx, userId, key, token).Err()
if err != nil { if err != nil {
log.Debug("Error clearing redis store: ", err) log.Debug("Error saving to redis: ", err)
return err return err
} }
return nil return nil
} }
// GetUserSessions returns all the user session token from the redis store. // GetAllUserSessions returns all the user session token from the redis store.
func (c *provider) GetUserSessions(userID string) map[string]string { func (c *provider) GetAllUserSessions(userID string) (map[string]string, error) {
data, err := c.store.HGetAll(c.ctx, "*").Result() data, err := c.store.HGetAll(c.ctx, userID).Result()
if err != nil { 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{} return data, nil
for k, v := range data {
split := strings.Split(v, "@")
if split[1] == userID {
res[k] = split[0]
}
} }
return res // 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()
// 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()
if err != nil { if err != nil {
log.Debug("Error deleting redis token: ", err) 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 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.Debugf("Error deleting sessions for %s namespace: %s", namespace, err.Error())
return err
}
}
if cursor == 0 { // no more keys
break
} }
} }
@@ -62,7 +110,7 @@ func (c *provider) DeleteAllUserSession(userId string) error {
// SetState sets the state in redis store. // SetState sets the state in redis store.
func (c *provider) SetState(key, value string) error { 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 { if err != nil {
log.Debug("Error saving redis token: ", err) log.Debug("Error saving redis token: ", err)
return err return err
@@ -73,18 +121,18 @@ func (c *provider) SetState(key, value string) error {
// GetState gets the state from redis store. // GetState gets the state from redis store.
func (c *provider) GetState(key string) (string, error) { func (c *provider) GetState(key string) (string, error) {
var res string data, err := c.store.Get(c.ctx, stateStorePrefix+key).Result()
err := c.store.Get(c.ctx, sessionStorePrefix+key).Scan(&res)
if err != nil { if err != nil {
log.Debug("error getting token from redis store: ", err) log.Debug("error getting token from redis store: ", err)
return "", err
} }
return res, err return data, err
} }
// RemoveState removes the state from redis store. // RemoveState removes the state from redis store.
func (c *provider) RemoveState(key string) error { 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 { if err != nil {
log.Fatalln("Error deleting redis token: ", err) log.Fatalln("Error deleting redis token: ", err)
return err return err
@@ -112,7 +160,7 @@ func (c *provider) GetEnvStore() (map[string]interface{}, error) {
return nil, err return nil, err
} }
for key, value := range data { 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) boolValue, err := strconv.ParseBool(value)
if err != nil { if err != nil {
return res, err 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 // GetStringStoreEnvVariable to get the string env variable from env store
func (c *provider) GetStringStoreEnvVariable(key string) (string, error) { func (c *provider) GetStringStoreEnvVariable(key string) (string, error) {
var res string data, err := c.store.HGet(c.ctx, envStorePrefix, key).Result()
err := c.store.HGet(c.ctx, envStorePrefix, key).Scan(&res)
if err != nil { if err != nil {
return "", nil return "", nil
} }
return res, nil return data, nil
} }
// GetBoolStoreEnvVariable to get the bool env variable from env store // GetBoolStoreEnvVariable to get the bool env variable from env store
func (c *provider) GetBoolStoreEnvVariable(key string) (bool, error) { func (c *provider) GetBoolStoreEnvVariable(key string) (bool, error) {
var res bool data, err := c.store.HGet(c.ctx, envStorePrefix, key).Result()
err := c.store.HGet(c.ctx, envStorePrefix, key).Scan(res)
if err != nil { if err != nil {
return false, nil return false, nil
} }
return res, nil return data == "1", nil
} }

View File

@@ -19,6 +19,7 @@ type OAuthProvider struct {
GithubConfig *oauth2.Config GithubConfig *oauth2.Config
FacebookConfig *oauth2.Config FacebookConfig *oauth2.Config
LinkedInConfig *oauth2.Config LinkedInConfig *oauth2.Config
AppleConfig *oauth2.Config
} }
// OIDCProviders is a struct that contains reference all the OpenID providers // OIDCProviders is a struct that contains reference all the OpenID providers
@@ -112,5 +113,25 @@ func InitOAuth() error {
} }
} }
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 return nil
} }

View File

@@ -38,7 +38,7 @@ func DeleteUserResolver(ctx context.Context, params model.DeleteUserInput) (*mod
return res, err return res, err
} }
go memorystore.Provider.DeleteAllUserSession(fmt.Sprintf("%x", user.ID)) go memorystore.Provider.DeleteAllUserSessions(user.ID)
err = db.Provider.DeleteUser(user) err = db.Provider.DeleteUser(user)
if err != nil { if err != nil {

View File

@@ -136,6 +136,12 @@ func EnvResolver(ctx context.Context) (*model.Env, error) {
if val, ok := store[constants.EnvKeyLinkedInClientSecret]; ok { if val, ok := store[constants.EnvKeyLinkedInClientSecret]; ok {
res.LinkedinClientSecret = utils.NewStringRef(val.(string)) 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 { if val, ok := store[constants.EnvKeyOrganizationName]; ok {
res.OrganizationName = utils.NewStringRef(val.(string)) res.OrganizationName = utils.NewStringRef(val.(string))
} }
@@ -147,7 +153,14 @@ func EnvResolver(ctx context.Context) (*model.Env, error) {
res.AllowedOrigins = strings.Split(store[constants.EnvKeyAllowedOrigins].(string), ",") res.AllowedOrigins = strings.Split(store[constants.EnvKeyAllowedOrigins].(string), ",")
res.Roles = strings.Split(store[constants.EnvKeyRoles].(string), ",") res.Roles = strings.Split(store[constants.EnvKeyRoles].(string), ",")
res.DefaultRoles = strings.Split(store[constants.EnvKeyDefaultRoles].(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 // bool vars
res.DisableEmailVerification = store[constants.EnvKeyDisableEmailVerification].(bool) res.DisableEmailVerification = store[constants.EnvKeyDisableEmailVerification].(bool)
@@ -155,6 +168,7 @@ func EnvResolver(ctx context.Context) (*model.Env, error) {
res.DisableMagicLinkLogin = store[constants.EnvKeyDisableMagicLinkLogin].(bool) res.DisableMagicLinkLogin = store[constants.EnvKeyDisableMagicLinkLogin].(bool)
res.DisableLoginPage = store[constants.EnvKeyDisableLoginPage].(bool) res.DisableLoginPage = store[constants.EnvKeyDisableLoginPage].(bool)
res.DisableSignUp = store[constants.EnvKeyDisableSignUp].(bool) res.DisableSignUp = store[constants.EnvKeyDisableSignUp].(bool)
res.DisableStrongPassword = store[constants.EnvKeyDisableStrongPassword].(bool)
return res, nil return res, nil
} }

View File

@@ -129,11 +129,11 @@ func InviteMembersResolver(ctx context.Context, params model.InviteMemberInput)
// use magic link login if that option is on // use magic link login if that option is on
if !isMagicLinkLoginDisabled { if !isMagicLinkLoginDisabled {
user.SignupMethods = constants.SignupMethodMagicLinkLogin user.SignupMethods = constants.AuthRecipeMethodMagicLinkLogin
verificationRequest.Identifier = constants.VerificationTypeMagicLinkLogin verificationRequest.Identifier = constants.VerificationTypeMagicLinkLogin
} else { } else {
// use basic authentication if that option is on // use basic authentication if that option is on
user.SignupMethods = constants.SignupMethodBasicAuth user.SignupMethods = constants.AuthRecipeMethodBasicAuth
verificationRequest.Identifier = constants.VerificationTypeForgotPassword verificationRequest.Identifier = constants.VerificationTypeForgotPassword
verifyEmailURL = appURL + "/setup-password" verifyEmailURL = appURL + "/setup-password"

View File

@@ -56,7 +56,7 @@ func LoginResolver(ctx context.Context, params model.LoginInput) (*model.AuthRes
return res, fmt.Errorf(`user access has been revoked`) 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") log.Debug("User signup method is not basic auth")
return res, fmt.Errorf(`user has not signed up email & password`) 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 scope = params.Scope
} }
authToken, err := token.CreateAuthToken(gc, user, roles, scope) authToken, err := token.CreateAuthToken(gc, user, roles, scope, constants.AuthRecipeMethodBasicAuth)
if err != nil { if err != nil {
log.Debug("Failed to create auth token", err) log.Debug("Failed to create auth token", err)
return res, err return res, err
@@ -117,12 +117,13 @@ func LoginResolver(ctx context.Context, params model.LoginInput) (*model.AuthRes
} }
cookie.SetSession(gc, authToken.FingerPrintHash) cookie.SetSession(gc, authToken.FingerPrintHash)
memorystore.Provider.SetState(authToken.FingerPrintHash, authToken.FingerPrint+"@"+user.ID) sessionStoreKey := constants.AuthRecipeMethodBasicAuth + ":" + user.ID
memorystore.Provider.SetState(authToken.AccessToken.Token, authToken.FingerPrint+"@"+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 { if authToken.RefreshToken != nil {
res.RefreshToken = &authToken.RefreshToken.Token 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{ go db.Provider.AddSession(models.Session{

View File

@@ -2,6 +2,7 @@ package resolvers
import ( import (
"context" "context"
"encoding/json"
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
@@ -9,38 +10,46 @@ import (
"github.com/authorizerdev/authorizer/server/crypto" "github.com/authorizerdev/authorizer/server/crypto"
"github.com/authorizerdev/authorizer/server/graph/model" "github.com/authorizerdev/authorizer/server/graph/model"
"github.com/authorizerdev/authorizer/server/memorystore" "github.com/authorizerdev/authorizer/server/memorystore"
"github.com/authorizerdev/authorizer/server/token"
"github.com/authorizerdev/authorizer/server/utils" "github.com/authorizerdev/authorizer/server/utils"
) )
// LogoutResolver is a resolver for logout mutation // LogoutResolver is a resolver for logout mutation
func LogoutResolver(ctx context.Context) (*model.Response, error) { func LogoutResolver(ctx context.Context) (*model.Response, error) {
var res *model.Response
gc, err := utils.GinContextFromContext(ctx) gc, err := utils.GinContextFromContext(ctx)
if err != nil { if err != nil {
log.Debug("Failed to get GinContext: ", err) log.Debug("Failed to get GinContext: ", err)
return res, err return nil, err
} }
// get fingerprint hash // get fingerprint hash
fingerprintHash, err := cookie.GetSession(gc) fingerprintHash, err := cookie.GetSession(gc)
if err != nil { if err != nil {
log.Debug("Failed to get fingerprint hash: ", err) log.Debug("Failed to get fingerprint hash: ", err)
return res, err return nil, err
} }
decryptedFingerPrint, err := crypto.DecryptAES(fingerprintHash) decryptedFingerPrint, err := crypto.DecryptAES(fingerprintHash)
if err != nil { if err != nil {
log.Debug("Failed to decrypt fingerprint hash: ", err) 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) cookie.DeleteSession(gc)
res = &model.Response{ res := &model.Response{
Message: "Logged out successfully", Message: "Logged out successfully",
} }

View File

@@ -70,7 +70,7 @@ func MagicLinkLoginResolver(ctx context.Context, params model.MagicLinkLoginInpu
return res, fmt.Errorf(`signup is disabled for this instance`) return res, fmt.Errorf(`signup is disabled for this instance`)
} }
user.SignupMethods = constants.SignupMethodMagicLinkLogin user.SignupMethods = constants.AuthRecipeMethodMagicLinkLogin
// define roles for new user // define roles for new user
if len(params.Roles) > 0 { if len(params.Roles) > 0 {
// check if roles exists // check if roles exists
@@ -158,8 +158,8 @@ func MagicLinkLoginResolver(ctx context.Context, params model.MagicLinkLoginInpu
} }
signupMethod := existingUser.SignupMethods signupMethod := existingUser.SignupMethods
if !strings.Contains(signupMethod, constants.SignupMethodMagicLinkLogin) { if !strings.Contains(signupMethod, constants.AuthRecipeMethodMagicLinkLogin) {
signupMethod = signupMethod + "," + constants.SignupMethodMagicLinkLogin signupMethod = signupMethod + "," + constants.AuthRecipeMethodMagicLinkLogin
} }
user.SignupMethods = signupMethod user.SignupMethods = signupMethod

View File

@@ -43,16 +43,28 @@ func MetaResolver(ctx context.Context) (*model.Meta, error) {
linkedClientID, err := memorystore.Provider.GetStringStoreEnvVariable(constants.EnvKeyLinkedInClientID) linkedClientID, err := memorystore.Provider.GetStringStoreEnvVariable(constants.EnvKeyLinkedInClientID)
if err != nil { if err != nil {
log.Debug("Failed to get Facebook Client ID from environment variable", err) log.Debug("Failed to get LinkedIn Client ID from environment variable", err)
linkedClientID = "" linkedClientID = ""
} }
linkedInClientSecret, err := memorystore.Provider.GetStringStoreEnvVariable(constants.EnvKeyLinkedInClientSecret) linkedInClientSecret, err := memorystore.Provider.GetStringStoreEnvVariable(constants.EnvKeyLinkedInClientSecret)
if err != nil { if err != nil {
log.Debug("Failed to get Facebook Client Secret from environment variable", err) log.Debug("Failed to get LinkedIn Client Secret from environment variable", err)
linkedInClientSecret = "" 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) githubClientID, err := memorystore.Provider.GetStringStoreEnvVariable(constants.EnvKeyGithubClientID)
if err != nil { if err != nil {
log.Debug("Failed to get Github Client ID from environment variable", err) log.Debug("Failed to get Github Client ID from environment variable", err)
@@ -89,6 +101,12 @@ func MetaResolver(ctx context.Context) (*model.Meta, error) {
isSignUpDisabled = true 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{ metaInfo := model.Meta{
Version: constants.VERSION, Version: constants.VERSION,
ClientID: clientID, ClientID: clientID,
@@ -96,10 +114,12 @@ func MetaResolver(ctx context.Context) (*model.Meta, error) {
IsGithubLoginEnabled: githubClientID != "" && githubClientSecret != "", IsGithubLoginEnabled: githubClientID != "" && githubClientSecret != "",
IsFacebookLoginEnabled: facebookClientID != "" && facebookClientSecret != "", IsFacebookLoginEnabled: facebookClientID != "" && facebookClientSecret != "",
IsLinkedinLoginEnabled: linkedClientID != "" && linkedInClientSecret != "", IsLinkedinLoginEnabled: linkedClientID != "" && linkedInClientSecret != "",
IsAppleLoginEnabled: appleClientID != "" && appleClientSecret != "",
IsBasicAuthenticationEnabled: !isBasicAuthDisabled, IsBasicAuthenticationEnabled: !isBasicAuthDisabled,
IsEmailVerificationEnabled: !isEmailVerificationDisabled, IsEmailVerificationEnabled: !isEmailVerificationDisabled,
IsMagicLinkLoginEnabled: !isMagicLinkLoginDisabled, IsMagicLinkLoginEnabled: !isMagicLinkLoginDisabled,
IsSignUpEnabled: !isSignUpDisabled, IsSignUpEnabled: !isSignUpDisabled,
IsStrongPasswordEnabled: !isStrongPasswordDisabled,
} }
return &metaInfo, nil return &metaInfo, nil
} }

View File

@@ -50,19 +50,24 @@ func ResetPasswordResolver(ctx context.Context, params model.ResetPasswordInput)
return res, fmt.Errorf(`passwords don't match`) return res, fmt.Errorf(`passwords don't match`)
} }
if !validators.IsValidPassword(params.Password) { if err := validators.IsValidPassword(params.Password); err != nil {
log.Debug("Invalid password") 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 // verify if token exists in db
hostname := parsers.GetHost(gc) hostname := parsers.GetHost(gc)
claim, err := token.ParseJWTToken(params.Token, hostname, verificationRequest.Nonce, verificationRequest.Email) claim, err := token.ParseJWTToken(params.Token)
if err != nil { if err != nil {
log.Debug("Failed to parse token: ", err) log.Debug("Failed to parse token: ", err)
return res, fmt.Errorf(`invalid token`) 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) email := claim["sub"].(string)
log := log.WithFields(log.Fields{ log := log.WithFields(log.Fields{
"email": email, "email": email,
@@ -77,8 +82,8 @@ func ResetPasswordResolver(ctx context.Context, params model.ResetPasswordInput)
user.Password = &password user.Password = &password
signupMethod := user.SignupMethods signupMethod := user.SignupMethods
if !strings.Contains(signupMethod, constants.SignupMethodBasicAuth) { if !strings.Contains(signupMethod, constants.AuthRecipeMethodBasicAuth) {
signupMethod = signupMethod + "," + constants.SignupMethodBasicAuth signupMethod = signupMethod + "," + constants.AuthRecipeMethodBasicAuth
} }
user.SignupMethods = signupMethod user.SignupMethods = signupMethod

View File

@@ -47,7 +47,7 @@ func RevokeAccessResolver(ctx context.Context, params model.UpdateAccessInput) (
return res, err return res, err
} }
go memorystore.Provider.DeleteAllUserSession(fmt.Sprintf("%x", user.ID)) go memorystore.Provider.DeleteAllUserSessions(user.ID)
res = &model.Response{ res = &model.Response{
Message: `user access revoked successfully`, Message: `user access revoked successfully`,

View File

@@ -8,6 +8,7 @@ import (
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
"github.com/authorizerdev/authorizer/server/constants"
"github.com/authorizerdev/authorizer/server/cookie" "github.com/authorizerdev/authorizer/server/cookie"
"github.com/authorizerdev/authorizer/server/db" "github.com/authorizerdev/authorizer/server/db"
"github.com/authorizerdev/authorizer/server/graph/model" "github.com/authorizerdev/authorizer/server/graph/model"
@@ -29,7 +30,7 @@ func SessionResolver(ctx context.Context, params *model.SessionQueryInput) (*mod
sessionToken, err := cookie.GetSession(gc) sessionToken, err := cookie.GetSession(gc)
if err != nil { if err != nil {
log.Debug("Failed to get session token", err) log.Debug("Failed to get session token: ", err)
return res, errors.New("unauthorized") return res, errors.New("unauthorized")
} }
@@ -69,17 +70,18 @@ func SessionResolver(ctx context.Context, params *model.SessionQueryInput) (*mod
scope = params.Scope scope = params.Scope
} }
authToken, err := token.CreateAuthToken(gc, user, claimRoles, scope) authToken, err := token.CreateAuthToken(gc, user, claimRoles, scope, claims.LoginMethod)
if err != nil { if err != nil {
log.Debug("Failed to create auth token: ", err) log.Debug("Failed to create auth token: ", err)
return res, err return res, err
} }
// rollover the session for security // rollover the session for security
memorystore.Provider.RemoveState(sessionToken) sessionKey := userID
memorystore.Provider.SetState(authToken.FingerPrintHash, authToken.FingerPrint+"@"+user.ID) if claims.LoginMethod != "" {
memorystore.Provider.SetState(authToken.AccessToken.Token, authToken.FingerPrint+"@"+user.ID) sessionKey = claims.LoginMethod + ":" + userID
cookie.SetSession(gc, authToken.FingerPrintHash) }
go memorystore.Provider.DeleteUserSession(sessionKey, claims.Nonce)
expiresIn := authToken.AccessToken.ExpiresAt - time.Now().Unix() expiresIn := authToken.AccessToken.ExpiresAt - time.Now().Unix()
if expiresIn <= 0 { if expiresIn <= 0 {
@@ -94,10 +96,13 @@ func SessionResolver(ctx context.Context, params *model.SessionQueryInput) (*mod
User: user.AsAPIUser(), User: user.AsAPIUser(),
} }
cookie.SetSession(gc, 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 { if authToken.RefreshToken != nil {
res.RefreshToken = &authToken.RefreshToken.Token res.RefreshToken = &authToken.RefreshToken.Token
memorystore.Provider.SetState(authToken.RefreshToken.Token, authToken.FingerPrint+"@"+user.ID) memorystore.Provider.SetUserSession(sessionKey, constants.TokenTypeRefreshToken+"_"+authToken.FingerPrint, authToken.RefreshToken.Token)
} }
return res, nil return res, nil
} }

View File

@@ -58,9 +58,9 @@ func SignupResolver(ctx context.Context, params model.SignUpInput) (*model.AuthR
return res, fmt.Errorf(`password and confirm password does not match`) return res, fmt.Errorf(`password and confirm password does not match`)
} }
if !validators.IsValidPassword(params.Password) { if err := validators.IsValidPassword(params.Password); err != nil {
log.Debug("Invalid password") 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
} }
params.Email = strings.ToLower(params.Email) params.Email = strings.ToLower(params.Email)
@@ -100,7 +100,7 @@ func SignupResolver(ctx context.Context, params model.SignUpInput) (*model.AuthR
} else { } else {
roles = strings.Split(rolesString, ",") roles = strings.Split(rolesString, ",")
} }
if !validators.IsValidRoles(roles, params.Roles) { if !validators.IsValidRoles(params.Roles, roles) {
log.Debug("Invalid roles: ", params.Roles) log.Debug("Invalid roles: ", params.Roles)
return res, fmt.Errorf(`invalid roles`) return res, fmt.Errorf(`invalid roles`)
} else { } else {
@@ -157,7 +157,7 @@ func SignupResolver(ctx context.Context, params model.SignUpInput) (*model.AuthR
user.Picture = params.Picture user.Picture = params.Picture
} }
user.SignupMethods = constants.SignupMethodBasicAuth user.SignupMethods = constants.AuthRecipeMethodBasicAuth
isEmailVerificationDisabled, err := memorystore.Provider.GetBoolStoreEnvVariable(constants.EnvKeyDisableEmailVerification) isEmailVerificationDisabled, err := memorystore.Provider.GetBoolStoreEnvVariable(constants.EnvKeyDisableEmailVerification)
if err != nil { if err != nil {
log.Debug("Error getting email verification disabled: ", err) log.Debug("Error getting email verification disabled: ", err)
@@ -219,14 +219,12 @@ func SignupResolver(ctx context.Context, params model.SignUpInput) (*model.AuthR
scope = params.Scope scope = params.Scope
} }
authToken, err := token.CreateAuthToken(gc, user, roles, scope) authToken, err := token.CreateAuthToken(gc, user, roles, scope, constants.AuthRecipeMethodBasicAuth)
if err != nil { if err != nil {
log.Debug("Failed to create auth token: ", err) log.Debug("Failed to create auth token: ", err)
return res, err return res, err
} }
memorystore.Provider.SetState(authToken.FingerPrintHash, authToken.FingerPrint+"@"+user.ID)
cookie.SetSession(gc, authToken.FingerPrintHash)
go db.Provider.AddSession(models.Session{ go db.Provider.AddSession(models.Session{
UserID: user.ID, UserID: user.ID,
UserAgent: utils.GetUserAgent(gc.Request), UserAgent: utils.GetUserAgent(gc.Request),
@@ -244,6 +242,16 @@ func SignupResolver(ctx context.Context, params model.SignUpInput) (*model.AuthR
ExpiresIn: &expiresIn, ExpiresIn: &expiresIn,
User: userToReturn, User: userToReturn,
} }
sessionKey := constants.AuthRecipeMethodBasicAuth + ":" + user.ID
cookie.SetSession(gc, 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 {
res.RefreshToken = &authToken.RefreshToken.Token
memorystore.Provider.SetUserSession(sessionKey, constants.TokenTypeRefreshToken+"_"+authToken.FingerPrint, authToken.RefreshToken.Token)
}
} }
return res, nil return res, nil

View File

@@ -21,6 +21,54 @@ import (
"github.com/authorizerdev/authorizer/server/utils" "github.com/authorizerdev/authorizer/server/utils"
) )
// check if login methods have been disabled
// remove the session tokens for those methods
func clearSessionIfRequired(currentData, updatedData map[string]interface{}) {
isCurrentBasicAuthEnabled := !currentData[constants.EnvKeyDisableBasicAuthentication].(bool)
isCurrentMagicLinkLoginEnabled := !currentData[constants.EnvKeyDisableMagicLinkLogin].(bool)
isCurrentAppleLoginEnabled := currentData[constants.EnvKeyAppleClientID] != nil && currentData[constants.EnvKeyAppleClientSecret] != nil && currentData[constants.EnvKeyAppleClientID].(string) != "" && currentData[constants.EnvKeyAppleClientSecret].(string) != ""
isCurrentFacebookLoginEnabled := currentData[constants.EnvKeyFacebookClientID] != nil && currentData[constants.EnvKeyFacebookClientSecret] != nil && currentData[constants.EnvKeyFacebookClientID].(string) != "" && currentData[constants.EnvKeyFacebookClientSecret].(string) != ""
isCurrentGoogleLoginEnabled := currentData[constants.EnvKeyGoogleClientID] != nil && currentData[constants.EnvKeyGoogleClientSecret] != nil && currentData[constants.EnvKeyGoogleClientID].(string) != "" && currentData[constants.EnvKeyGoogleClientSecret].(string) != ""
isCurrentGithubLoginEnabled := currentData[constants.EnvKeyGithubClientID] != nil && currentData[constants.EnvKeyGithubClientSecret] != nil && currentData[constants.EnvKeyGithubClientID].(string) != "" && currentData[constants.EnvKeyGithubClientSecret].(string) != ""
isCurrentLinkedInLoginEnabled := currentData[constants.EnvKeyLinkedInClientID] != nil && currentData[constants.EnvKeyLinkedInClientSecret] != nil && currentData[constants.EnvKeyLinkedInClientID].(string) != "" && currentData[constants.EnvKeyLinkedInClientSecret].(string) != ""
isUpdatedBasicAuthEnabled := !updatedData[constants.EnvKeyDisableBasicAuthentication].(bool)
isUpdatedMagicLinkLoginEnabled := !updatedData[constants.EnvKeyDisableMagicLinkLogin].(bool)
isUpdatedAppleLoginEnabled := updatedData[constants.EnvKeyAppleClientID] != nil && updatedData[constants.EnvKeyAppleClientSecret] != nil && updatedData[constants.EnvKeyAppleClientID].(string) != "" && updatedData[constants.EnvKeyAppleClientSecret].(string) != ""
isUpdatedFacebookLoginEnabled := updatedData[constants.EnvKeyFacebookClientID] != nil && updatedData[constants.EnvKeyFacebookClientSecret] != nil && updatedData[constants.EnvKeyFacebookClientID].(string) != "" && updatedData[constants.EnvKeyFacebookClientSecret].(string) != ""
isUpdatedGoogleLoginEnabled := updatedData[constants.EnvKeyGoogleClientID] != nil && updatedData[constants.EnvKeyGoogleClientSecret] != nil && updatedData[constants.EnvKeyGoogleClientID].(string) != "" && updatedData[constants.EnvKeyGoogleClientSecret].(string) != ""
isUpdatedGithubLoginEnabled := updatedData[constants.EnvKeyGithubClientID] != nil && updatedData[constants.EnvKeyGithubClientSecret] != nil && updatedData[constants.EnvKeyGithubClientID].(string) != "" && updatedData[constants.EnvKeyGithubClientSecret].(string) != ""
isUpdatedLinkedInLoginEnabled := updatedData[constants.EnvKeyLinkedInClientID] != nil && updatedData[constants.EnvKeyLinkedInClientSecret] != nil && updatedData[constants.EnvKeyLinkedInClientID].(string) != "" && updatedData[constants.EnvKeyLinkedInClientSecret].(string) != ""
if isCurrentBasicAuthEnabled && !isUpdatedBasicAuthEnabled {
memorystore.Provider.DeleteSessionForNamespace(constants.AuthRecipeMethodBasicAuth)
}
if isCurrentMagicLinkLoginEnabled && !isUpdatedMagicLinkLoginEnabled {
memorystore.Provider.DeleteSessionForNamespace(constants.AuthRecipeMethodMagicLinkLogin)
}
if isCurrentAppleLoginEnabled && !isUpdatedAppleLoginEnabled {
memorystore.Provider.DeleteSessionForNamespace(constants.AuthRecipeMethodApple)
}
if isCurrentFacebookLoginEnabled && !isUpdatedFacebookLoginEnabled {
memorystore.Provider.DeleteSessionForNamespace(constants.AuthRecipeMethodFacebook)
}
if isCurrentGoogleLoginEnabled && !isUpdatedGoogleLoginEnabled {
memorystore.Provider.DeleteSessionForNamespace(constants.AuthRecipeMethodGoogle)
}
if isCurrentGithubLoginEnabled && !isUpdatedGithubLoginEnabled {
memorystore.Provider.DeleteSessionForNamespace(constants.AuthRecipeMethodGithub)
}
if isCurrentLinkedInLoginEnabled && !isUpdatedLinkedInLoginEnabled {
memorystore.Provider.DeleteSessionForNamespace(constants.AuthRecipeMethodLinkedIn)
}
}
// UpdateEnvResolver is a resolver for update config mutation // UpdateEnvResolver is a resolver for update config mutation
// This is admin only mutation // This is admin only mutation
func UpdateEnvResolver(ctx context.Context, params model.UpdateEnvInput) (*model.Response, error) { func UpdateEnvResolver(ctx context.Context, params model.UpdateEnvInput) (*model.Response, error) {
@@ -37,12 +85,19 @@ func UpdateEnvResolver(ctx context.Context, params model.UpdateEnvInput) (*model
return res, fmt.Errorf("unauthorized") return res, fmt.Errorf("unauthorized")
} }
updatedData, err := memorystore.Provider.GetEnvStore() currentData, err := memorystore.Provider.GetEnvStore()
if err != nil { if err != nil {
log.Debug("Failed to get env store: ", err) log.Debug("Failed to get env store: ", err)
return res, err return res, err
} }
// clone currentData in new var
// that will be updated based on the req
updatedData := make(map[string]interface{})
for key, val := range currentData {
updatedData[key] = val
}
isJWTUpdated := false isJWTUpdated := false
algo := updatedData[constants.EnvKeyJwtType].(string) algo := updatedData[constants.EnvKeyJwtType].(string)
if params.JwtType != nil { if params.JwtType != nil {
@@ -210,6 +265,8 @@ func UpdateEnvResolver(ctx context.Context, params model.UpdateEnvInput) (*model
} }
} }
go clearSessionIfRequired(currentData, updatedData)
// Update local store // Update local store
memorystore.Provider.UpdateEnvStore(updatedData) memorystore.Provider.UpdateEnvStore(updatedData)
jwk, err := crypto.GenerateJWKBasedOnEnv() jwk, err := crypto.GenerateJWKBasedOnEnv()
@@ -224,12 +281,6 @@ func UpdateEnvResolver(ctx context.Context, params model.UpdateEnvInput) (*model
return res, err return res, err
} }
// TODO check how to update session store based on env change.
// err = sessionstore.InitSession()
// if err != nil {
// log.Debug("Failed to init session store: ", err)
// return res, err
// }
err = oauth.InitOAuth() err = oauth.InitOAuth()
if err != nil { if err != nil {
return res, err return res, err

View File

@@ -142,7 +142,7 @@ func UpdateProfileResolver(ctx context.Context, params model.UpdateProfileInput)
return res, fmt.Errorf("user with this email address already exists") return res, fmt.Errorf("user with this email address already exists")
} }
go memorystore.Provider.DeleteAllUserSession(user.ID) go memorystore.Provider.DeleteAllUserSessions(user.ID)
go cookie.DeleteSession(gc) go cookie.DeleteSession(gc)
user.Email = newEmail user.Email = newEmail

View File

@@ -113,7 +113,7 @@ func UpdateUserResolver(ctx context.Context, params model.UpdateUserInput) (*mod
} }
// TODO figure out how to do this // TODO figure out how to do this
go memorystore.Provider.DeleteAllUserSession(user.ID) go memorystore.Provider.DeleteAllUserSessions(user.ID)
hostname := parsers.GetHost(gc) hostname := parsers.GetHost(gc)
user.Email = newEmail user.Email = newEmail
@@ -182,7 +182,7 @@ func UpdateUserResolver(ctx context.Context, params model.UpdateUserInput) (*mod
rolesToSave = strings.Join(inputRoles, ",") rolesToSave = strings.Join(inputRoles, ",")
} }
go memorystore.Provider.DeleteAllUserSession(user.ID) go memorystore.Provider.DeleteAllUserSessions(user.ID)
} }
if rolesToSave != "" { if rolesToSave != "" {

View File

@@ -4,11 +4,11 @@ import (
"context" "context"
"errors" "errors"
"fmt" "fmt"
"strings"
"github.com/golang-jwt/jwt" "github.com/golang-jwt/jwt"
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
"github.com/authorizerdev/authorizer/server/constants"
"github.com/authorizerdev/authorizer/server/graph/model" "github.com/authorizerdev/authorizer/server/graph/model"
"github.com/authorizerdev/authorizer/server/memorystore" "github.com/authorizerdev/authorizer/server/memorystore"
"github.com/authorizerdev/authorizer/server/parsers" "github.com/authorizerdev/authorizer/server/parsers"
@@ -30,48 +30,51 @@ func ValidateJwtTokenResolver(ctx context.Context, params model.ValidateJWTToken
} }
tokenType := params.TokenType tokenType := params.TokenType
if tokenType != "access_token" && tokenType != "refresh_token" && tokenType != "id_token" { if tokenType != constants.TokenTypeAccessToken && tokenType != constants.TokenTypeRefreshToken && tokenType != constants.TokenTypeIdentityToken {
log.Debug("Invalid token type: ", tokenType) log.Debug("Invalid token type: ", tokenType)
return nil, errors.New("invalid token type") return nil, errors.New("invalid token type")
} }
var claimRoles []string
var claims jwt.MapClaims
userID := "" userID := ""
nonce := "" nonce := ""
// access_token and refresh_token should be validated from session store as well
if tokenType == "access_token" || tokenType == "refresh_token" { claims, err = token.ParseJWTToken(params.Token)
savedSession, err := memorystore.Provider.GetState(params.Token) if err != nil {
if savedSession == "" || err != nil { log.Debug("Failed to parse JWT token: ", err)
return &model.ValidateJWTTokenResponse{ return nil, err
IsValid: false, }
}, nil userID = claims["sub"].(string)
// access_token and refresh_token should be validated from session store as well
if tokenType == constants.TokenTypeAccessToken || tokenType == constants.TokenTypeRefreshToken {
nonce = claims["nonce"].(string)
loginMethod := claims["login_method"]
sessionKey := userID
if loginMethod != nil && loginMethod != "" {
sessionKey = loginMethod.(string) + ":" + userID
}
token, err := memorystore.Provider.GetUserSession(sessionKey, tokenType+"_"+claims["nonce"].(string))
if err != nil || token == "" {
log.Debug("Failed to get user session: ", err)
return nil, errors.New("invalid token")
} }
savedSessionSplit := strings.Split(savedSession, "@")
nonce = savedSessionSplit[0]
userID = savedSessionSplit[1]
} }
hostname := parsers.GetHost(gc) hostname := parsers.GetHost(gc)
var claimRoles []string
var claims jwt.MapClaims
// we cannot validate sub and nonce in case of id_token as that token is not persisted in session store // we cannot validate nonce in case of id_token as that token is not persisted in session store
if userID != "" && nonce != "" { if nonce != "" {
claims, err = token.ParseJWTToken(params.Token, hostname, nonce, userID) if ok, err := token.ValidateJWTClaims(claims, hostname, nonce, userID); !ok || err != nil {
if err != nil {
log.Debug("Failed to parse jwt token: ", err) log.Debug("Failed to parse jwt token: ", err)
return &model.ValidateJWTTokenResponse{ return nil, errors.New("invalid claims")
IsValid: false,
}, nil
} }
} else { } else {
claims, err = token.ParseJWTTokenWithoutNonce(params.Token, hostname) if ok, err := token.ValidateJWTTokenWithoutNonce(claims, hostname, userID); !ok || err != nil {
if err != nil {
log.Debug("Failed to parse jwt token without nonce: ", err) log.Debug("Failed to parse jwt token without nonce: ", err)
return &model.ValidateJWTTokenResponse{ return nil, errors.New("invalid claims")
IsValid: false,
}, nil
} }
} }
claimRolesInterface := claims["roles"] claimRolesInterface := claims["roles"]

View File

@@ -8,6 +8,7 @@ import (
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
"github.com/authorizerdev/authorizer/server/constants"
"github.com/authorizerdev/authorizer/server/cookie" "github.com/authorizerdev/authorizer/server/cookie"
"github.com/authorizerdev/authorizer/server/db" "github.com/authorizerdev/authorizer/server/db"
"github.com/authorizerdev/authorizer/server/db/models" "github.com/authorizerdev/authorizer/server/db/models"
@@ -36,12 +37,17 @@ func VerifyEmailResolver(ctx context.Context, params model.VerifyEmailInput) (*m
// verify if token exists in db // verify if token exists in db
hostname := parsers.GetHost(gc) hostname := parsers.GetHost(gc)
claim, err := token.ParseJWTToken(params.Token, hostname, verificationRequest.Nonce, verificationRequest.Email) claim, err := token.ParseJWTToken(params.Token)
if err != nil { if err != nil {
log.Debug("Failed to parse token: ", err) log.Debug("Failed to parse token: ", err)
return res, fmt.Errorf(`invalid token: %s`, err.Error()) return res, fmt.Errorf(`invalid token: %s`, err.Error())
} }
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: %s`, err.Error())
}
email := claim["sub"].(string) email := claim["sub"].(string)
log := log.WithFields(log.Fields{ log := log.WithFields(log.Fields{
"email": email, "email": email,
@@ -67,17 +73,19 @@ func VerifyEmailResolver(ctx context.Context, params model.VerifyEmailInput) (*m
return res, err return res, err
} }
loginMethod := constants.AuthRecipeMethodBasicAuth
if loginMethod == constants.VerificationTypeMagicLinkLogin {
loginMethod = constants.AuthRecipeMethodMagicLinkLogin
}
roles := strings.Split(user.Roles, ",") roles := strings.Split(user.Roles, ",")
scope := []string{"openid", "email", "profile"} scope := []string{"openid", "email", "profile"}
authToken, err := token.CreateAuthToken(gc, user, roles, scope) authToken, err := token.CreateAuthToken(gc, user, roles, scope, loginMethod)
if err != nil { if err != nil {
log.Debug("Failed to create auth token: ", err) log.Debug("Failed to create auth token: ", err)
return res, err return res, err
} }
memorystore.Provider.SetState(authToken.FingerPrintHash, authToken.FingerPrint+"@"+user.ID)
memorystore.Provider.SetState(authToken.AccessToken.Token, authToken.FingerPrint+"@"+user.ID)
cookie.SetSession(gc, authToken.FingerPrintHash)
go db.Provider.AddSession(models.Session{ go db.Provider.AddSession(models.Session{
UserID: user.ID, UserID: user.ID,
UserAgent: utils.GetUserAgent(gc.Request), UserAgent: utils.GetUserAgent(gc.Request),
@@ -96,5 +104,15 @@ func VerifyEmailResolver(ctx context.Context, params model.VerifyEmailInput) (*m
ExpiresIn: &expiresIn, ExpiresIn: &expiresIn,
User: user.AsAPIUser(), User: user.AsAPIUser(),
} }
sessionKey := loginMethod + ":" + user.ID
cookie.SetSession(gc, 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 {
res.RefreshToken = &authToken.RefreshToken.Token
memorystore.Provider.SetUserSession(sessionKey, constants.TokenTypeRefreshToken+"_"+authToken.FingerPrint, authToken.RefreshToken.Token)
}
return res, nil return res, nil
} }

View File

@@ -23,6 +23,7 @@ func InitRouter(log *logrus.Logger) *gin.Engine {
router.GET("/playground", handlers.PlaygroundHandler()) router.GET("/playground", handlers.PlaygroundHandler())
router.GET("/oauth_login/:oauth_provider", handlers.OAuthLoginHandler()) router.GET("/oauth_login/:oauth_provider", handlers.OAuthLoginHandler())
router.GET("/oauth_callback/:oauth_provider", handlers.OAuthCallbackHandler()) router.GET("/oauth_callback/:oauth_provider", handlers.OAuthCallbackHandler())
router.POST("/oauth_callback/:oauth_provider", handlers.OAuthCallbackHandler())
router.GET("/verify_email", handlers.VerifyEmailHandler()) router.GET("/verify_email", handlers.VerifyEmailHandler())
// OPEN ID routes // OPEN ID routes
router.GET("/.well-known/openid-configuration", handlers.OpenIDConfigurationHandler()) router.GET("/.well-known/openid-configuration", handlers.OpenIDConfigurationHandler())

View File

@@ -52,7 +52,7 @@ func TestJwt(t *testing.T) {
} }
jwtToken, err := token.SignJWTToken(expiredClaims) jwtToken, err := token.SignJWTToken(expiredClaims)
assert.NoError(t, err) assert.NoError(t, err)
_, err = token.ParseJWTToken(jwtToken, hostname, nonce, subject) _, err = token.ParseJWTToken(jwtToken)
assert.Error(t, err, err.Error(), "Token is expired") assert.Error(t, err, err.Error(), "Token is expired")
}) })
t.Run("HMAC algorithms", func(t *testing.T) { t.Run("HMAC algorithms", func(t *testing.T) {
@@ -62,27 +62,36 @@ func TestJwt(t *testing.T) {
jwtToken, err := token.SignJWTToken(claims) jwtToken, err := token.SignJWTToken(claims)
assert.NoError(t, err) assert.NoError(t, err)
assert.NotEmpty(t, jwtToken) assert.NotEmpty(t, jwtToken)
c, err := token.ParseJWTToken(jwtToken, hostname, nonce, subject) c, err := token.ParseJWTToken(jwtToken)
assert.NoError(t, err) assert.NoError(t, err)
assert.Equal(t, c["email"].(string), claims["email"]) assert.Equal(t, c["email"].(string), claims["email"])
valid, err := token.ValidateJWTClaims(c, hostname, nonce, subject)
assert.NoError(t, err)
assert.True(t, valid)
}) })
t.Run("HS384", func(t *testing.T) { t.Run("HS384", func(t *testing.T) {
memorystore.Provider.UpdateEnvVariable(constants.EnvKeyJwtType, "HS384") memorystore.Provider.UpdateEnvVariable(constants.EnvKeyJwtType, "HS384")
jwtToken, err := token.SignJWTToken(claims) jwtToken, err := token.SignJWTToken(claims)
assert.NoError(t, err) assert.NoError(t, err)
assert.NotEmpty(t, jwtToken) assert.NotEmpty(t, jwtToken)
c, err := token.ParseJWTToken(jwtToken, hostname, nonce, subject) c, err := token.ParseJWTToken(jwtToken)
assert.NoError(t, err) assert.NoError(t, err)
assert.Equal(t, c["email"].(string), claims["email"]) assert.Equal(t, c["email"].(string), claims["email"])
valid, err := token.ValidateJWTClaims(c, hostname, nonce, subject)
assert.NoError(t, err)
assert.True(t, valid)
}) })
t.Run("HS512", func(t *testing.T) { t.Run("HS512", func(t *testing.T) {
memorystore.Provider.UpdateEnvVariable(constants.EnvKeyJwtType, "HS512") memorystore.Provider.UpdateEnvVariable(constants.EnvKeyJwtType, "HS512")
jwtToken, err := token.SignJWTToken(claims) jwtToken, err := token.SignJWTToken(claims)
assert.NoError(t, err) assert.NoError(t, err)
assert.NotEmpty(t, jwtToken) assert.NotEmpty(t, jwtToken)
c, err := token.ParseJWTToken(jwtToken, hostname, nonce, subject) c, err := token.ParseJWTToken(jwtToken)
assert.NoError(t, err) assert.NoError(t, err)
assert.Equal(t, c["email"].(string), claims["email"]) assert.Equal(t, c["email"].(string), claims["email"])
valid, err := token.ValidateJWTClaims(c, hostname, nonce, subject)
assert.NoError(t, err)
assert.True(t, valid)
}) })
}) })
@@ -96,9 +105,12 @@ func TestJwt(t *testing.T) {
jwtToken, err := token.SignJWTToken(claims) jwtToken, err := token.SignJWTToken(claims)
assert.NoError(t, err) assert.NoError(t, err)
assert.NotEmpty(t, jwtToken) assert.NotEmpty(t, jwtToken)
c, err := token.ParseJWTToken(jwtToken, hostname, nonce, subject) c, err := token.ParseJWTToken(jwtToken)
assert.NoError(t, err) assert.NoError(t, err)
assert.Equal(t, c["email"].(string), claims["email"]) assert.Equal(t, c["email"].(string), claims["email"])
valid, err := token.ValidateJWTClaims(c, hostname, nonce, subject)
assert.NoError(t, err)
assert.True(t, valid)
}) })
t.Run("RS384", func(t *testing.T) { t.Run("RS384", func(t *testing.T) {
_, privateKey, publickKey, _, err := crypto.NewRSAKey("RS384", clientID) _, privateKey, publickKey, _, err := crypto.NewRSAKey("RS384", clientID)
@@ -109,9 +121,12 @@ func TestJwt(t *testing.T) {
jwtToken, err := token.SignJWTToken(claims) jwtToken, err := token.SignJWTToken(claims)
assert.NoError(t, err) assert.NoError(t, err)
assert.NotEmpty(t, jwtToken) assert.NotEmpty(t, jwtToken)
c, err := token.ParseJWTToken(jwtToken, hostname, nonce, subject) c, err := token.ParseJWTToken(jwtToken)
assert.NoError(t, err) assert.NoError(t, err)
assert.Equal(t, c["email"].(string), claims["email"]) assert.Equal(t, c["email"].(string), claims["email"])
valid, err := token.ValidateJWTClaims(c, hostname, nonce, subject)
assert.NoError(t, err)
assert.True(t, valid)
}) })
t.Run("RS512", func(t *testing.T) { t.Run("RS512", func(t *testing.T) {
_, privateKey, publickKey, _, err := crypto.NewRSAKey("RS512", clientID) _, privateKey, publickKey, _, err := crypto.NewRSAKey("RS512", clientID)
@@ -122,9 +137,12 @@ func TestJwt(t *testing.T) {
jwtToken, err := token.SignJWTToken(claims) jwtToken, err := token.SignJWTToken(claims)
assert.NoError(t, err) assert.NoError(t, err)
assert.NotEmpty(t, jwtToken) assert.NotEmpty(t, jwtToken)
c, err := token.ParseJWTToken(jwtToken, hostname, nonce, subject) c, err := token.ParseJWTToken(jwtToken)
assert.NoError(t, err) assert.NoError(t, err)
assert.Equal(t, c["email"].(string), claims["email"]) assert.Equal(t, c["email"].(string), claims["email"])
valid, err := token.ValidateJWTClaims(c, hostname, nonce, subject)
assert.NoError(t, err)
assert.True(t, valid)
}) })
}) })
@@ -138,9 +156,12 @@ func TestJwt(t *testing.T) {
jwtToken, err := token.SignJWTToken(claims) jwtToken, err := token.SignJWTToken(claims)
assert.NoError(t, err) assert.NoError(t, err)
assert.NotEmpty(t, jwtToken) assert.NotEmpty(t, jwtToken)
c, err := token.ParseJWTToken(jwtToken, hostname, nonce, subject) c, err := token.ParseJWTToken(jwtToken)
assert.NoError(t, err) assert.NoError(t, err)
assert.Equal(t, c["email"].(string), claims["email"]) assert.Equal(t, c["email"].(string), claims["email"])
valid, err := token.ValidateJWTClaims(c, hostname, nonce, subject)
assert.NoError(t, err)
assert.True(t, valid)
}) })
t.Run("ES384", func(t *testing.T) { t.Run("ES384", func(t *testing.T) {
_, privateKey, publickKey, _, err := crypto.NewECDSAKey("ES384", clientID) _, privateKey, publickKey, _, err := crypto.NewECDSAKey("ES384", clientID)
@@ -151,9 +172,12 @@ func TestJwt(t *testing.T) {
jwtToken, err := token.SignJWTToken(claims) jwtToken, err := token.SignJWTToken(claims)
assert.NoError(t, err) assert.NoError(t, err)
assert.NotEmpty(t, jwtToken) assert.NotEmpty(t, jwtToken)
c, err := token.ParseJWTToken(jwtToken, hostname, nonce, subject) c, err := token.ParseJWTToken(jwtToken)
assert.NoError(t, err) assert.NoError(t, err)
assert.Equal(t, c["email"].(string), claims["email"]) assert.Equal(t, c["email"].(string), claims["email"])
valid, err := token.ValidateJWTClaims(c, hostname, nonce, subject)
assert.NoError(t, err)
assert.True(t, valid)
}) })
t.Run("ES512", func(t *testing.T) { t.Run("ES512", func(t *testing.T) {
_, privateKey, publickKey, _, err := crypto.NewECDSAKey("ES512", clientID) _, privateKey, publickKey, _, err := crypto.NewECDSAKey("ES512", clientID)
@@ -164,9 +188,12 @@ func TestJwt(t *testing.T) {
jwtToken, err := token.SignJWTToken(claims) jwtToken, err := token.SignJWTToken(claims)
assert.NoError(t, err) assert.NoError(t, err)
assert.NotEmpty(t, jwtToken) assert.NotEmpty(t, jwtToken)
c, err := token.ParseJWTToken(jwtToken, hostname, nonce, subject) c, err := token.ParseJWTToken(jwtToken)
assert.NoError(t, err) assert.NoError(t, err)
assert.Equal(t, c["email"].(string), claims["email"]) assert.Equal(t, c["email"].(string), claims["email"])
valid, err := token.ValidateJWTClaims(c, hostname, nonce, subject)
assert.NoError(t, err)
assert.True(t, valid)
}) })
}) })

View File

@@ -2,6 +2,7 @@ package test
import ( import (
"fmt" "fmt"
"strings"
"testing" "testing"
"github.com/authorizerdev/authorizer/server/constants" "github.com/authorizerdev/authorizer/server/constants"
@@ -9,6 +10,7 @@ import (
"github.com/authorizerdev/authorizer/server/graph/model" "github.com/authorizerdev/authorizer/server/graph/model"
"github.com/authorizerdev/authorizer/server/memorystore" "github.com/authorizerdev/authorizer/server/memorystore"
"github.com/authorizerdev/authorizer/server/resolvers" "github.com/authorizerdev/authorizer/server/resolvers"
"github.com/authorizerdev/authorizer/server/token"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
) )
@@ -27,16 +29,26 @@ func logoutTests(t *testing.T, s TestSetup) {
Token: verificationRequest.Token, Token: verificationRequest.Token,
}) })
token := *verifyRes.AccessToken accessToken := *verifyRes.AccessToken
sessions := memorystore.Provider.GetUserSessions(verifyRes.User.ID) assert.NotEmpty(t, accessToken)
cookie := ""
// set all they keys in cookie one of them should be session cookie claims, err := token.ParseJWTToken(accessToken)
for key := range sessions { assert.NoError(t, err)
if key != token { assert.NotEmpty(t, claims)
cookie += fmt.Sprintf("%s=%s;", constants.AppCookieName+"_session", key)
} loginMethod := claims["login_method"]
sessionKey := verifyRes.User.ID
if loginMethod != nil && loginMethod != "" {
sessionKey = loginMethod.(string) + ":" + verifyRes.User.ID
} }
sessionToken, err := memorystore.Provider.GetUserSession(sessionKey, constants.TokenTypeSessionToken+"_"+claims["nonce"].(string))
assert.NoError(t, err)
assert.NotEmpty(t, sessionToken)
cookie := fmt.Sprintf("%s=%s;", constants.AppCookieName+"_session", sessionToken)
cookie = strings.TrimSuffix(cookie, ";")
req.Header.Set("Cookie", cookie) req.Header.Set("Cookie", cookie)
_, err = resolvers.LogoutResolver(ctx) _, err = resolvers.LogoutResolver(ctx)
assert.Nil(t, err) assert.Nil(t, err)

View File

@@ -37,9 +37,9 @@ func profileTests(t *testing.T, s TestSetup) {
ctx = context.WithValue(req.Context(), "GinContextKey", s.GinContext) ctx = context.WithValue(req.Context(), "GinContextKey", s.GinContext)
profileRes, err := resolvers.ProfileResolver(ctx) profileRes, err := resolvers.ProfileResolver(ctx)
assert.Nil(t, err) assert.Nil(t, err)
assert.NotNil(t, profileRes)
s.GinContext.Request.Header.Set("Authorization", "") s.GinContext.Request.Header.Set("Authorization", "")
newEmail := profileRes.Email
newEmail := *&profileRes.Email
assert.Equal(t, email, newEmail, "emails should be equal") assert.Equal(t, email, newEmail, "emails should be equal")
cleanData(email) cleanData(email)

View File

@@ -10,6 +10,7 @@ import (
"github.com/authorizerdev/authorizer/server/graph/model" "github.com/authorizerdev/authorizer/server/graph/model"
"github.com/authorizerdev/authorizer/server/memorystore" "github.com/authorizerdev/authorizer/server/memorystore"
"github.com/authorizerdev/authorizer/server/resolvers" "github.com/authorizerdev/authorizer/server/resolvers"
"github.com/authorizerdev/authorizer/server/token"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
) )
@@ -33,19 +34,22 @@ func sessionTests(t *testing.T, s TestSetup) {
Token: verificationRequest.Token, Token: verificationRequest.Token,
}) })
sessions := memorystore.Provider.GetUserSessions(verifyRes.User.ID) accessToken := *verifyRes.AccessToken
cookie := "" assert.NotEmpty(t, accessToken)
token := *verifyRes.AccessToken
// set all they keys in cookie one of them should be session cookie claims, err := token.ParseJWTToken(accessToken)
for key := range sessions { assert.NoError(t, err)
if key != token { assert.NotEmpty(t, claims)
cookie += fmt.Sprintf("%s=%s;", constants.AppCookieName+"_session", key)
} sessionKey := constants.AuthRecipeMethodBasicAuth + ":" + verifyRes.User.ID
} sessionToken, err := memorystore.Provider.GetUserSession(sessionKey, constants.TokenTypeSessionToken+"_"+claims["nonce"].(string))
assert.NoError(t, err)
assert.NotEmpty(t, sessionToken)
cookie := fmt.Sprintf("%s=%s;", constants.AppCookieName+"_session", sessionToken)
cookie = strings.TrimSuffix(cookie, ";") cookie = strings.TrimSuffix(cookie, ";")
req.Header.Set("Cookie", cookie) req.Header.Set("Cookie", cookie)
_, err = resolvers.SessionResolver(ctx, &model.SessionQueryInput{}) _, err = resolvers.SessionResolver(ctx, &model.SessionQueryInput{})
assert.Nil(t, err) assert.Nil(t, err)

View File

@@ -4,6 +4,7 @@ import (
"testing" "testing"
"time" "time"
"github.com/authorizerdev/authorizer/server/constants"
"github.com/authorizerdev/authorizer/server/db/models" "github.com/authorizerdev/authorizer/server/db/models"
"github.com/authorizerdev/authorizer/server/graph/model" "github.com/authorizerdev/authorizer/server/graph/model"
"github.com/authorizerdev/authorizer/server/memorystore" "github.com/authorizerdev/authorizer/server/memorystore"
@@ -22,12 +23,14 @@ func validateJwtTokenTest(t *testing.T, s TestSetup) {
TokenType: "access_token", TokenType: "access_token",
Token: "", Token: "",
}) })
assert.False(t, res.IsValid) assert.Error(t, err)
assert.Nil(t, res)
res, err = resolvers.ValidateJwtTokenResolver(ctx, model.ValidateJWTTokenInput{ res, err = resolvers.ValidateJwtTokenResolver(ctx, model.ValidateJWTTokenInput{
TokenType: "access_token", TokenType: "access_token",
Token: "invalid", Token: "invalid",
}) })
assert.False(t, res.IsValid) assert.Error(t, err)
assert.Nil(t, res)
_, err = resolvers.ValidateJwtTokenResolver(ctx, model.ValidateJWTTokenInput{ _, err = resolvers.ValidateJwtTokenResolver(ctx, model.ValidateJWTTokenInput{
TokenType: "access_token_invalid", TokenType: "access_token_invalid",
Token: "invalid@invalid", Token: "invalid@invalid",
@@ -47,9 +50,14 @@ func validateJwtTokenTest(t *testing.T, s TestSetup) {
roles := []string{"user"} roles := []string{"user"}
gc, err := utils.GinContextFromContext(ctx) gc, err := utils.GinContextFromContext(ctx)
assert.NoError(t, err) assert.NoError(t, err)
authToken, err := token.CreateAuthToken(gc, user, roles, scope) sessionKey := constants.AuthRecipeMethodBasicAuth + ":" + user.ID
memorystore.Provider.SetState(authToken.AccessToken.Token, authToken.FingerPrint+"@"+user.ID) authToken, err := token.CreateAuthToken(gc, user, roles, scope, constants.AuthRecipeMethodBasicAuth)
memorystore.Provider.SetState(authToken.RefreshToken.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 {
memorystore.Provider.SetUserSession(sessionKey, constants.TokenTypeRefreshToken+"_"+authToken.FingerPrint, authToken.RefreshToken.Token)
}
t.Run(`should validate the access token`, func(t *testing.T) { t.Run(`should validate the access token`, func(t *testing.T) {
res, err := resolvers.ValidateJwtTokenResolver(ctx, model.ValidateJWTTokenInput{ res, err := resolvers.ValidateJwtTokenResolver(ctx, model.ValidateJWTTokenInput{
@@ -57,7 +65,6 @@ func validateJwtTokenTest(t *testing.T, s TestSetup) {
Token: authToken.AccessToken.Token, Token: authToken.AccessToken.Token,
Roles: []string{"user"}, Roles: []string{"user"},
}) })
assert.NoError(t, err) assert.NoError(t, err)
assert.True(t, res.IsValid) assert.True(t, res.IsValid)

View File

@@ -43,9 +43,9 @@ func TestIsValidIdentifier(t *testing.T) {
} }
func TestIsValidPassword(t *testing.T) { func TestIsValidPassword(t *testing.T) {
assert.False(t, validators.IsValidPassword("test"), "it should be invalid password") assert.Error(t, validators.IsValidPassword("test"), "it should be invalid password")
assert.False(t, validators.IsValidPassword("Te@1"), "it should be invalid password") assert.Error(t, validators.IsValidPassword("Te@1"), "it should be invalid password")
assert.False(t, validators.IsValidPassword("n*rp7GGTd29V{xx%{pDb@7n{](SD.!+.Mp#*$EHDGk&$pAMf7e#432Sg,Gr](j3n]jV/3F8BJJT+9u9{q=8zK:8u!rpQBaXJp%A+7r!jQj)M(vC$UX,h;;WKm$U6i#7dBnC&2ryKzKd+(y&=Ud)hErT/j;v3t..CM).8nS)9qLtV7pmP;@2QuzDyGfL7KB()k:BpjAGL@bxD%r5gcBfh7$&wutk!wzMfPFY#nkjjqyZbEHku,{jc;gvbYq2)3w=KExnYz9Vbv:;*;?f##faxkULdMpmm&yEfePixzx+[{[38zGN;3TzF;6M#Xy_tMtx:yK*n$bc(bPyGz%EYkC&]ttUF@#aZ%$QZ:u!icF@+"), "it should be invalid password") assert.Error(t, validators.IsValidPassword("n*rp7GGTd29V{xx%{pDb@7n{](SD.!+.Mp#*$EHDGk&$pAMf7e#432Sg,Gr](j3n]jV/3F8BJJT+9u9{q=8zK:8u!rpQBaXJp%A+7r!jQj)M(vC$UX,h;;WKm$U6i#7dBnC&2ryKzKd+(y&=Ud)hErT/j;v3t..CM).8nS)9qLtV7pmP;@2QuzDyGfL7KB()k:BpjAGL@bxD%r5gcBfh7$&wutk!wzMfPFY#nkjjqyZbEHku,{jc;gvbYq2)3w=KExnYz9Vbv:;*;?f##faxkULdMpmm&yEfePixzx+[{[38zGN;3TzF;6M#Xy_tMtx:yK*n$bc(bPyGz%EYkC&]ttUF@#aZ%$QZ:u!icF@+"), "it should be invalid password")
assert.False(t, validators.IsValidPassword("test@123"), "it should be invalid password") assert.Error(t, validators.IsValidPassword("test@123"), "it should be invalid password")
assert.True(t, validators.IsValidPassword("Test@123"), "it should be valid password") assert.NoError(t, validators.IsValidPassword("Test@123"), "it should be valid password")
} }

View File

@@ -44,15 +44,17 @@ type SessionData struct {
Nonce string `json:"nonce"` Nonce string `json:"nonce"`
IssuedAt int64 `json:"iat"` IssuedAt int64 `json:"iat"`
ExpiresAt int64 `json:"exp"` ExpiresAt int64 `json:"exp"`
LoginMethod string `json:"login_method"`
} }
// CreateSessionToken creates a new session token // CreateSessionToken creates a new session token
func CreateSessionToken(user models.User, nonce string, roles, scope []string) (*SessionData, string, error) { func CreateSessionToken(user models.User, nonce string, roles, scope []string, loginMethod string) (*SessionData, string, error) {
fingerPrintMap := &SessionData{ fingerPrintMap := &SessionData{
Nonce: nonce, Nonce: nonce,
Roles: roles, Roles: roles,
Subject: user.ID, Subject: user.ID,
Scope: scope, Scope: scope,
LoginMethod: loginMethod,
IssuedAt: time.Now().Unix(), IssuedAt: time.Now().Unix(),
ExpiresAt: time.Now().AddDate(1, 0, 0).Unix(), ExpiresAt: time.Now().AddDate(1, 0, 0).Unix(),
} }
@@ -66,19 +68,19 @@ func CreateSessionToken(user models.User, nonce string, roles, scope []string) (
} }
// CreateAuthToken creates a new auth token when userlogs in // CreateAuthToken creates a new auth token when userlogs in
func CreateAuthToken(gc *gin.Context, user models.User, roles, scope []string) (*Token, error) { func CreateAuthToken(gc *gin.Context, user models.User, roles, scope []string, loginMethod string) (*Token, error) {
hostname := parsers.GetHost(gc) hostname := parsers.GetHost(gc)
nonce := uuid.New().String() nonce := uuid.New().String()
_, fingerPrintHash, err := CreateSessionToken(user, nonce, roles, scope) _, fingerPrintHash, err := CreateSessionToken(user, nonce, roles, scope, loginMethod)
if err != nil { if err != nil {
return nil, err return nil, err
} }
accessToken, accessTokenExpiresAt, err := CreateAccessToken(user, roles, scope, hostname, nonce) accessToken, accessTokenExpiresAt, err := CreateAccessToken(user, roles, scope, hostname, nonce, loginMethod)
if err != nil { if err != nil {
return nil, err return nil, err
} }
idToken, idTokenExpiresAt, err := CreateIDToken(user, roles, hostname, nonce) idToken, idTokenExpiresAt, err := CreateIDToken(user, roles, hostname, nonce, loginMethod)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -91,7 +93,7 @@ func CreateAuthToken(gc *gin.Context, user models.User, roles, scope []string) (
} }
if utils.StringSliceContains(scope, "offline_access") { if utils.StringSliceContains(scope, "offline_access") {
refreshToken, refreshTokenExpiresAt, err := CreateRefreshToken(user, roles, scope, hostname, nonce) refreshToken, refreshTokenExpiresAt, err := CreateRefreshToken(user, roles, scope, hostname, nonce, loginMethod)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -103,7 +105,7 @@ func CreateAuthToken(gc *gin.Context, user models.User, roles, scope []string) (
} }
// CreateRefreshToken util to create JWT token // CreateRefreshToken util to create JWT token
func CreateRefreshToken(user models.User, roles, scopes []string, hostname, nonce string) (string, int64, error) { func CreateRefreshToken(user models.User, roles, scopes []string, hostname, nonce, loginMethod string) (string, int64, error) {
// expires in 1 year // expires in 1 year
expiryBound := time.Hour * 8760 expiryBound := time.Hour * 8760
expiresAt := time.Now().Add(expiryBound).Unix() expiresAt := time.Now().Add(expiryBound).Unix()
@@ -121,6 +123,7 @@ func CreateRefreshToken(user models.User, roles, scopes []string, hostname, nonc
"roles": roles, "roles": roles,
"scope": scopes, "scope": scopes,
"nonce": nonce, "nonce": nonce,
"login_method": loginMethod,
} }
token, err := SignJWTToken(customClaims) token, err := SignJWTToken(customClaims)
@@ -133,7 +136,7 @@ func CreateRefreshToken(user models.User, roles, scopes []string, hostname, nonc
// CreateAccessToken util to create JWT token, based on // CreateAccessToken util to create JWT token, based on
// user information, roles config and CUSTOM_ACCESS_TOKEN_SCRIPT // user information, roles config and CUSTOM_ACCESS_TOKEN_SCRIPT
func CreateAccessToken(user models.User, roles, scopes []string, hostName, nonce string) (string, int64, error) { func CreateAccessToken(user models.User, roles, scopes []string, hostName, nonce, loginMethod string) (string, int64, error) {
expireTime, err := memorystore.Provider.GetStringStoreEnvVariable(constants.EnvKeyAccessTokenExpiryTime) expireTime, err := memorystore.Provider.GetStringStoreEnvVariable(constants.EnvKeyAccessTokenExpiryTime)
if err != nil { if err != nil {
return "", 0, err return "", 0, err
@@ -159,6 +162,7 @@ func CreateAccessToken(user models.User, roles, scopes []string, hostName, nonce
"token_type": constants.TokenTypeAccessToken, "token_type": constants.TokenTypeAccessToken,
"scope": scopes, "scope": scopes,
"roles": roles, "roles": roles,
"login_method": loginMethod,
} }
token, err := SignJWTToken(customClaims) token, err := SignJWTToken(customClaims)
@@ -198,18 +202,30 @@ func ValidateAccessToken(gc *gin.Context, accessToken string) (map[string]interf
return res, fmt.Errorf(`unauthorized`) return res, fmt.Errorf(`unauthorized`)
} }
savedSession, err := memorystore.Provider.GetState(accessToken) res, err := ParseJWTToken(accessToken)
if savedSession == "" || err != nil { if err != nil {
return res, err
}
userID := res["sub"].(string)
nonce := res["nonce"].(string)
loginMethod := res["login_method"]
sessionKey := userID
if loginMethod != nil && loginMethod != "" {
sessionKey = loginMethod.(string) + ":" + userID
}
token, err := memorystore.Provider.GetUserSession(sessionKey, constants.TokenTypeAccessToken+"_"+nonce)
if nonce == "" || err != nil {
return res, fmt.Errorf(`unauthorized`) return res, fmt.Errorf(`unauthorized`)
} }
savedSessionSplit := strings.Split(savedSession, "@") if token != accessToken {
nonce := savedSessionSplit[0] return res, fmt.Errorf(`unauthorized`)
userID := savedSessionSplit[1] }
hostname := parsers.GetHost(gc) hostname := parsers.GetHost(gc)
res, err = ParseJWTToken(accessToken, hostname, nonce, userID) if ok, err := ValidateJWTClaims(res, hostname, nonce, userID); !ok || err != nil {
if err != nil {
return res, err return res, err
} }
@@ -228,18 +244,30 @@ func ValidateRefreshToken(gc *gin.Context, refreshToken string) (map[string]inte
return res, fmt.Errorf(`unauthorized`) return res, fmt.Errorf(`unauthorized`)
} }
savedSession, err := memorystore.Provider.GetState(refreshToken) res, err := ParseJWTToken(refreshToken)
if savedSession == "" || err != nil { if err != nil {
return res, err
}
userID := res["sub"].(string)
nonce := res["nonce"].(string)
loginMethod := res["login_method"]
sessionKey := userID
if loginMethod != nil && loginMethod != "" {
sessionKey = loginMethod.(string) + ":" + userID
}
token, err := memorystore.Provider.GetUserSession(sessionKey, constants.TokenTypeRefreshToken+"_"+nonce)
if nonce == "" || err != nil {
return res, fmt.Errorf(`unauthorized`) return res, fmt.Errorf(`unauthorized`)
} }
savedSessionSplit := strings.Split(savedSession, "@") if token != refreshToken {
nonce := savedSessionSplit[0] return res, fmt.Errorf(`unauthorized`)
userID := savedSessionSplit[1] }
hostname := parsers.GetHost(gc) hostname := parsers.GetHost(gc)
res, err = ParseJWTToken(refreshToken, hostname, nonce, userID) if ok, err := ValidateJWTClaims(res, hostname, nonce, userID); !ok || err != nil {
if err != nil {
return res, err return res, err
} }
@@ -255,15 +283,6 @@ func ValidateBrowserSession(gc *gin.Context, encryptedSession string) (*SessionD
return nil, fmt.Errorf(`unauthorized`) return nil, fmt.Errorf(`unauthorized`)
} }
savedSession, err := memorystore.Provider.GetState(encryptedSession)
if savedSession == "" || err != nil {
return nil, fmt.Errorf(`unauthorized`)
}
savedSessionSplit := strings.Split(savedSession, "@")
nonce := savedSessionSplit[0]
userID := savedSessionSplit[1]
decryptedFingerPrint, err := crypto.DecryptAES(encryptedSession) decryptedFingerPrint, err := crypto.DecryptAES(encryptedSession)
if err != nil { if err != nil {
return nil, err return nil, err
@@ -275,29 +294,31 @@ func ValidateBrowserSession(gc *gin.Context, encryptedSession string) (*SessionD
return nil, err return nil, err
} }
if res.Nonce != nonce { sessionStoreKey := res.Subject
return nil, fmt.Errorf(`unauthorized: invalid nonce`) if res.LoginMethod != "" {
sessionStoreKey = res.LoginMethod + ":" + res.Subject
} }
if res.Subject != userID { token, err := memorystore.Provider.GetUserSession(sessionStoreKey, constants.TokenTypeSessionToken+"_"+res.Nonce)
return nil, fmt.Errorf(`unauthorized: invalid user id`) if token == "" || err != nil {
log.Debug("invalid browser session:", err)
return nil, fmt.Errorf(`unauthorized`)
}
if encryptedSession != token {
return nil, fmt.Errorf(`unauthorized: invalid nonce`)
} }
if res.ExpiresAt < time.Now().Unix() { if res.ExpiresAt < time.Now().Unix() {
return nil, fmt.Errorf(`unauthorized: token expired`) return nil, fmt.Errorf(`unauthorized: token expired`)
} }
// TODO validate scope
// if !reflect.DeepEqual(res.Roles, roles) {
// return res, "", fmt.Errorf(`unauthorized`)
// }
return &res, nil return &res, nil
} }
// CreateIDToken util to create JWT token, based on // CreateIDToken util to create JWT token, based on
// user information, roles config and CUSTOM_ACCESS_TOKEN_SCRIPT // user information, roles config and CUSTOM_ACCESS_TOKEN_SCRIPT
func CreateIDToken(user models.User, roles []string, hostname, nonce string) (string, int64, error) { func CreateIDToken(user models.User, roles []string, hostname, nonce, loginMethod string) (string, int64, error) {
expireTime, err := memorystore.Provider.GetStringStoreEnvVariable(constants.EnvKeyAccessTokenExpiryTime) expireTime, err := memorystore.Provider.GetStringStoreEnvVariable(constants.EnvKeyAccessTokenExpiryTime)
if err != nil { if err != nil {
return "", 0, err return "", 0, err
@@ -332,6 +353,7 @@ func CreateIDToken(user models.User, roles []string, hostname, nonce string) (st
"iat": time.Now().Unix(), "iat": time.Now().Unix(),
"token_type": constants.TokenTypeIdentityToken, "token_type": constants.TokenTypeIdentityToken,
"allowed_roles": strings.Split(user.Roles, ","), "allowed_roles": strings.Split(user.Roles, ","),
"login_method": loginMethod,
claimKey: roles, claimKey: roles,
} }

View File

@@ -60,7 +60,7 @@ func SignJWTToken(claims jwt.MapClaims) (string, error) {
} }
// ParseJWTToken common util to parse jwt token // ParseJWTToken common util to parse jwt token
func ParseJWTToken(token, hostname, nonce, subject string) (jwt.MapClaims, error) { func ParseJWTToken(token string) (jwt.MapClaims, error) {
jwtType, err := memorystore.Provider.GetStringStoreEnvVariable(constants.EnvKeyJwtType) jwtType, err := memorystore.Provider.GetStringStoreEnvVariable(constants.EnvKeyJwtType)
if err != nil { if err != nil {
return nil, err return nil, err
@@ -116,98 +116,51 @@ func ParseJWTToken(token, hostname, nonce, subject string) (jwt.MapClaims, error
intIat := int64(claims["iat"].(float64)) intIat := int64(claims["iat"].(float64))
claims["exp"] = intExp claims["exp"] = intExp
claims["iat"] = intIat claims["iat"] = intIat
return claims, nil
}
// ValidateJWTClaims common util to validate claims
func ValidateJWTClaims(claims jwt.MapClaims, hostname, nonce, subject string) (bool, error) {
clientID, err := memorystore.Provider.GetStringStoreEnvVariable(constants.EnvKeyClientID) clientID, err := memorystore.Provider.GetStringStoreEnvVariable(constants.EnvKeyClientID)
if err != nil { if err != nil {
return claims, err return false, err
} }
if claims["aud"] != clientID { if claims["aud"] != clientID {
return claims, errors.New("invalid audience") return false, errors.New("invalid audience")
} }
if claims["nonce"] != nonce { if claims["nonce"] != nonce {
return claims, errors.New("invalid nonce") return false, errors.New("invalid nonce")
} }
if claims["iss"] != hostname { if claims["iss"] != hostname {
return claims, errors.New("invalid issuer") return false, errors.New("invalid issuer")
} }
if claims["sub"] != subject { if claims["sub"] != subject {
return claims, errors.New("invalid subject") return false, errors.New("invalid subject")
} }
return claims, nil return true, nil
} }
// ParseJWTTokenWithoutNonce common util to parse jwt token without nonce // ValidateJWTTokenWithoutNonce common util to validate claims without nonce
// used to validate ID token as it is not persisted in store func ValidateJWTTokenWithoutNonce(claims jwt.MapClaims, hostname, subject string) (bool, error) {
func ParseJWTTokenWithoutNonce(token, hostname string) (jwt.MapClaims, error) {
jwtType, err := memorystore.Provider.GetStringStoreEnvVariable(constants.EnvKeyJwtType)
if err != nil {
return nil, err
}
signingMethod := jwt.GetSigningMethod(jwtType)
var claims jwt.MapClaims
switch signingMethod {
case jwt.SigningMethodHS256, jwt.SigningMethodHS384, jwt.SigningMethodHS512:
_, err = jwt.ParseWithClaims(token, &claims, func(token *jwt.Token) (interface{}, error) {
jwtSecret, err := memorystore.Provider.GetStringStoreEnvVariable(constants.EnvKeyJwtSecret)
if err != nil {
return nil, err
}
return []byte(jwtSecret), nil
})
case jwt.SigningMethodRS256, jwt.SigningMethodRS384, jwt.SigningMethodRS512:
_, err = jwt.ParseWithClaims(token, &claims, func(token *jwt.Token) (interface{}, error) {
jwtPublicKey, err := memorystore.Provider.GetStringStoreEnvVariable(constants.EnvKeyJwtPublicKey)
if err != nil {
return nil, err
}
key, err := crypto.ParseRsaPublicKeyFromPemStr(jwtPublicKey)
if err != nil {
return nil, err
}
return key, nil
})
case jwt.SigningMethodES256, jwt.SigningMethodES384, jwt.SigningMethodES512:
_, err = jwt.ParseWithClaims(token, &claims, func(token *jwt.Token) (interface{}, error) {
jwtPublicKey, err := memorystore.Provider.GetStringStoreEnvVariable(constants.EnvKeyJwtPublicKey)
if err != nil {
return nil, err
}
key, err := crypto.ParseEcdsaPublicKeyFromPemStr(jwtPublicKey)
if err != nil {
return nil, err
}
return key, nil
})
default:
err = errors.New("unsupported signing method")
}
if err != nil {
return claims, err
}
// claim parses exp & iat into float 64 with e^10,
// but we expect it to be int64
// hence we need to assert interface and convert to int64
intExp := int64(claims["exp"].(float64))
intIat := int64(claims["iat"].(float64))
claims["exp"] = intExp
claims["iat"] = intIat
clientID, err := memorystore.Provider.GetStringStoreEnvVariable(constants.EnvKeyClientID) clientID, err := memorystore.Provider.GetStringStoreEnvVariable(constants.EnvKeyClientID)
if err != nil { if err != nil {
return claims, err return false, err
} }
if claims["aud"] != clientID { if claims["aud"] != clientID {
return claims, errors.New("invalid audience") return false, errors.New("invalid audience")
} }
if claims["iss"] != hostname { if claims["iss"] != hostname {
return claims, errors.New("invalid issuer") return false, errors.New("invalid issuer")
} }
return claims, nil if claims["sub"] != subject {
return false, errors.New("invalid subject")
}
return true, nil
} }

View File

@@ -1,5 +1,12 @@
package validators package validators
import (
"errors"
"github.com/authorizerdev/authorizer/server/constants"
"github.com/authorizerdev/authorizer/server/memorystore"
)
// ValidatePassword to validate the password against the following policy // ValidatePassword to validate the password against the following policy
// min char length: 6 // min char length: 6
// max char length: 36 // max char length: 36
@@ -7,9 +14,16 @@ package validators
// at least one lower case letter // at least one lower case letter
// at least one digit // at least one digit
// at least one special character // at least one special character
func IsValidPassword(password string) bool { func IsValidPassword(password string) error {
if len(password) < 6 || len(password) > 36 { if len(password) < 6 || len(password) > 36 {
return false return errors.New("password must be of minimum 6 characters and maximum 36 characters")
}
// if strong password is disabled
// just check for min 6 chars & max 36
isStrongPasswordDisabled, _ := memorystore.Provider.GetBoolStoreEnvVariable(constants.EnvKeyDisableStrongPassword)
if isStrongPasswordDisabled {
return nil
} }
hasUpperCase := false hasUpperCase := false
@@ -29,5 +43,11 @@ func IsValidPassword(password string) bool {
} }
} }
return hasUpperCase && hasLowerCase && hasDigit && hasSpecialChar isValid := hasUpperCase && hasLowerCase && hasDigit && hasSpecialChar
if isValid {
return nil
}
return errors.New(`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`)
} }