Merge branch 'main' of https://github.com/authorizerdev/authorizer into feat/casandra-db
This commit is contained in:
commit
325aa88368
|
@ -26,18 +26,16 @@
|
||||||
- ✅ Sign-in / Sign-up with email ID and password
|
- ✅ Sign-in / Sign-up with email ID and password
|
||||||
- ✅ Secure session management
|
- ✅ Secure session management
|
||||||
- ✅ Email verification
|
- ✅ Email verification
|
||||||
|
- ✅ OAuth2 and OpenID compatible APIs
|
||||||
- ✅ APIs to update profile securely
|
- ✅ APIs to update profile securely
|
||||||
- ✅ Forgot password flow using email
|
- ✅ Forgot password flow using email
|
||||||
- ✅ Social logins (Google, Github, Facebook, more coming soon)
|
- ✅ Social logins (Google, Github, Facebook, more coming soon)
|
||||||
- ✅ Role-based access management
|
- ✅ Role-based access management
|
||||||
- ✅ Password-less login with email and magic link
|
- ✅ Password-less login with magic link login
|
||||||
|
|
||||||
## Roadmap
|
## Roadmap
|
||||||
|
|
||||||
- Support more JWT encryption algorithms (Currently supporting HS256)
|
|
||||||
- 2 Factor authentication
|
- 2 Factor authentication
|
||||||
- Back office (Admin dashboard to manage user)
|
|
||||||
- Support more database
|
|
||||||
- VueJS SDK
|
- VueJS SDK
|
||||||
- Svelte SDK
|
- Svelte SDK
|
||||||
- React Native SDK
|
- React Native SDK
|
||||||
|
|
30
app/package-lock.json
generated
30
app/package-lock.json
generated
|
@ -9,7 +9,7 @@
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@authorizerdev/authorizer-react": "latest",
|
"@authorizerdev/authorizer-react": "^0.17.0",
|
||||||
"@types/react": "^17.0.15",
|
"@types/react": "^17.0.15",
|
||||||
"@types/react-dom": "^17.0.9",
|
"@types/react-dom": "^17.0.9",
|
||||||
"esbuild": "^0.12.17",
|
"esbuild": "^0.12.17",
|
||||||
|
@ -24,9 +24,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@authorizerdev/authorizer-js": {
|
"node_modules/@authorizerdev/authorizer-js": {
|
||||||
"version": "0.6.0",
|
"version": "0.10.0",
|
||||||
"resolved": "https://registry.npmjs.org/@authorizerdev/authorizer-js/-/authorizer-js-0.6.0.tgz",
|
"resolved": "https://registry.npmjs.org/@authorizerdev/authorizer-js/-/authorizer-js-0.10.0.tgz",
|
||||||
"integrity": "sha512-WbqeUmhQwLNlvk4ZYTptlbAIINh7aZPyTCVA/B0FE3EoPtx1tNOtkPtJOycrn0H0HyueeXQnBSCDxkvPAP65Bw==",
|
"integrity": "sha512-REM8FLD/Ej9gzA2zDGDAke6QFss33ubePlTDmLDmIYUuQmpHFlO5mCCS6nVsKkN7F/Bcwkmp+eUNQjkdGCaKLg==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"node-fetch": "^2.6.1"
|
"node-fetch": "^2.6.1"
|
||||||
},
|
},
|
||||||
|
@ -35,11 +35,11 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@authorizerdev/authorizer-react": {
|
"node_modules/@authorizerdev/authorizer-react": {
|
||||||
"version": "0.11.0",
|
"version": "0.17.0",
|
||||||
"resolved": "https://registry.npmjs.org/@authorizerdev/authorizer-react/-/authorizer-react-0.11.0.tgz",
|
"resolved": "https://registry.npmjs.org/@authorizerdev/authorizer-react/-/authorizer-react-0.17.0.tgz",
|
||||||
"integrity": "sha512-VzSZvEB/t6N2ESn4O8c/+2hPUO7L4Iux8IBzXKrobKkoqRyb+u5TPZn0UWCOaoxIdiiZY+1Yq2A/H6q9LAqLGw==",
|
"integrity": "sha512-7WcNCU7hDFkVfFb8LcJXFwWiLYd8aY78z1AbNPxCa2Cw5G85PaRkzjKybP6h01ITVOHO6M03lLwPj8p6Sr6fEg==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@authorizerdev/authorizer-js": "^0.6.0",
|
"@authorizerdev/authorizer-js": "^0.10.0",
|
||||||
"final-form": "^4.20.2",
|
"final-form": "^4.20.2",
|
||||||
"react-final-form": "^6.5.3",
|
"react-final-form": "^6.5.3",
|
||||||
"styled-components": "^5.3.0"
|
"styled-components": "^5.3.0"
|
||||||
|
@ -829,19 +829,19 @@
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@authorizerdev/authorizer-js": {
|
"@authorizerdev/authorizer-js": {
|
||||||
"version": "0.6.0",
|
"version": "0.10.0",
|
||||||
"resolved": "https://registry.npmjs.org/@authorizerdev/authorizer-js/-/authorizer-js-0.6.0.tgz",
|
"resolved": "https://registry.npmjs.org/@authorizerdev/authorizer-js/-/authorizer-js-0.10.0.tgz",
|
||||||
"integrity": "sha512-WbqeUmhQwLNlvk4ZYTptlbAIINh7aZPyTCVA/B0FE3EoPtx1tNOtkPtJOycrn0H0HyueeXQnBSCDxkvPAP65Bw==",
|
"integrity": "sha512-REM8FLD/Ej9gzA2zDGDAke6QFss33ubePlTDmLDmIYUuQmpHFlO5mCCS6nVsKkN7F/Bcwkmp+eUNQjkdGCaKLg==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"node-fetch": "^2.6.1"
|
"node-fetch": "^2.6.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@authorizerdev/authorizer-react": {
|
"@authorizerdev/authorizer-react": {
|
||||||
"version": "0.11.0",
|
"version": "0.17.0",
|
||||||
"resolved": "https://registry.npmjs.org/@authorizerdev/authorizer-react/-/authorizer-react-0.11.0.tgz",
|
"resolved": "https://registry.npmjs.org/@authorizerdev/authorizer-react/-/authorizer-react-0.17.0.tgz",
|
||||||
"integrity": "sha512-VzSZvEB/t6N2ESn4O8c/+2hPUO7L4Iux8IBzXKrobKkoqRyb+u5TPZn0UWCOaoxIdiiZY+1Yq2A/H6q9LAqLGw==",
|
"integrity": "sha512-7WcNCU7hDFkVfFb8LcJXFwWiLYd8aY78z1AbNPxCa2Cw5G85PaRkzjKybP6h01ITVOHO6M03lLwPj8p6Sr6fEg==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"@authorizerdev/authorizer-js": "^0.6.0",
|
"@authorizerdev/authorizer-js": "^0.10.0",
|
||||||
"final-form": "^4.20.2",
|
"final-form": "^4.20.2",
|
||||||
"react-final-form": "^6.5.3",
|
"react-final-form": "^6.5.3",
|
||||||
"styled-components": "^5.3.0"
|
"styled-components": "^5.3.0"
|
||||||
|
|
|
@ -11,7 +11,7 @@
|
||||||
"author": "Lakhan Samani",
|
"author": "Lakhan Samani",
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@authorizerdev/authorizer-react": "latest",
|
"@authorizerdev/authorizer-react": "^0.17.0",
|
||||||
"@types/react": "^17.0.15",
|
"@types/react": "^17.0.15",
|
||||||
"@types/react-dom": "^17.0.9",
|
"@types/react-dom": "^17.0.9",
|
||||||
"esbuild": "^0.12.17",
|
"esbuild": "^0.12.17",
|
||||||
|
|
|
@ -10,6 +10,9 @@ const queryClient = createClient({
|
||||||
fetchOptions: () => {
|
fetchOptions: () => {
|
||||||
return {
|
return {
|
||||||
credentials: 'include',
|
credentials: 'include',
|
||||||
|
headers: {
|
||||||
|
'x-authorizer-url': window.location.origin,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
requestPolicy: 'network-only',
|
requestPolicy: 'network-only',
|
||||||
|
|
247
dashboard/src/components/GenerateKeysModal.tsx
Normal file
247
dashboard/src/components/GenerateKeysModal.tsx
Normal file
|
@ -0,0 +1,247 @@
|
||||||
|
import React from 'react';
|
||||||
|
import {
|
||||||
|
Button,
|
||||||
|
Center,
|
||||||
|
Flex,
|
||||||
|
Modal,
|
||||||
|
ModalBody,
|
||||||
|
ModalCloseButton,
|
||||||
|
ModalContent,
|
||||||
|
ModalFooter,
|
||||||
|
ModalHeader,
|
||||||
|
ModalOverlay,
|
||||||
|
useDisclosure,
|
||||||
|
Text,
|
||||||
|
useToast,
|
||||||
|
Input,
|
||||||
|
Spinner,
|
||||||
|
} from '@chakra-ui/react';
|
||||||
|
import { useClient } from 'urql';
|
||||||
|
import { FaSave } from 'react-icons/fa';
|
||||||
|
import {
|
||||||
|
ECDSAEncryptionType,
|
||||||
|
HMACEncryptionType,
|
||||||
|
RSAEncryptionType,
|
||||||
|
SelectInputType,
|
||||||
|
TextAreaInputType,
|
||||||
|
} from '../constants';
|
||||||
|
import InputField from './InputField';
|
||||||
|
import { GenerateKeys, UpdateEnvVariables } from '../graphql/mutation';
|
||||||
|
|
||||||
|
interface propTypes {
|
||||||
|
jwtType: string;
|
||||||
|
getData: Function;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface stateVarTypes {
|
||||||
|
JWT_TYPE: string;
|
||||||
|
JWT_SECRET: string;
|
||||||
|
JWT_PRIVATE_KEY: string;
|
||||||
|
JWT_PUBLIC_KEY: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const initState: stateVarTypes = {
|
||||||
|
JWT_TYPE: '',
|
||||||
|
JWT_SECRET: '',
|
||||||
|
JWT_PRIVATE_KEY: '',
|
||||||
|
JWT_PUBLIC_KEY: '',
|
||||||
|
};
|
||||||
|
|
||||||
|
const GenerateKeysModal = ({ jwtType, getData }: propTypes) => {
|
||||||
|
const client = useClient();
|
||||||
|
const toast = useToast();
|
||||||
|
const { isOpen, onOpen, onClose } = useDisclosure();
|
||||||
|
const [stateVariables, setStateVariables] = React.useState<stateVarTypes>({
|
||||||
|
...initState,
|
||||||
|
});
|
||||||
|
const [isLoading, setIsLoading] = React.useState(false);
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
if (isOpen) {
|
||||||
|
setStateVariables({ ...initState, JWT_TYPE: jwtType });
|
||||||
|
}
|
||||||
|
}, [isOpen]);
|
||||||
|
|
||||||
|
const fetchKeys = async () => {
|
||||||
|
setIsLoading(true);
|
||||||
|
try {
|
||||||
|
const res = await client
|
||||||
|
.mutation(GenerateKeys, { params: { type: stateVariables.JWT_TYPE } })
|
||||||
|
.toPromise();
|
||||||
|
if (res?.error) {
|
||||||
|
toast({
|
||||||
|
title: 'Error occurred generating jwt keys',
|
||||||
|
isClosable: true,
|
||||||
|
status: 'error',
|
||||||
|
position: 'bottom-right',
|
||||||
|
});
|
||||||
|
closeHandler();
|
||||||
|
} else {
|
||||||
|
setStateVariables({
|
||||||
|
...stateVariables,
|
||||||
|
JWT_SECRET: res?.data?._generate_jwt_keys?.secret || '',
|
||||||
|
JWT_PRIVATE_KEY: res?.data?._generate_jwt_keys?.private_key || '',
|
||||||
|
JWT_PUBLIC_KEY: res?.data?._generate_jwt_keys?.public_key || '',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.log(error);
|
||||||
|
} finally {
|
||||||
|
setIsLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
if (isOpen && stateVariables.JWT_TYPE) {
|
||||||
|
fetchKeys();
|
||||||
|
}
|
||||||
|
}, [stateVariables.JWT_TYPE]);
|
||||||
|
|
||||||
|
const saveHandler = async () => {
|
||||||
|
const res = await client
|
||||||
|
.mutation(UpdateEnvVariables, { params: { ...stateVariables } })
|
||||||
|
.toPromise();
|
||||||
|
|
||||||
|
if (res.error) {
|
||||||
|
toast({
|
||||||
|
title: 'Error occurred setting jwt keys',
|
||||||
|
isClosable: true,
|
||||||
|
status: 'error',
|
||||||
|
position: 'bottom-right',
|
||||||
|
});
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
toast({
|
||||||
|
title: 'JWT keys updated successfully',
|
||||||
|
isClosable: true,
|
||||||
|
status: 'success',
|
||||||
|
position: 'bottom-right',
|
||||||
|
});
|
||||||
|
closeHandler();
|
||||||
|
};
|
||||||
|
|
||||||
|
const closeHandler = () => {
|
||||||
|
setStateVariables({ ...initState });
|
||||||
|
getData();
|
||||||
|
onClose();
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Button
|
||||||
|
colorScheme="blue"
|
||||||
|
h="1.75rem"
|
||||||
|
size="sm"
|
||||||
|
variant="ghost"
|
||||||
|
onClick={onOpen}
|
||||||
|
>
|
||||||
|
Generate new keys
|
||||||
|
</Button>
|
||||||
|
<Modal isOpen={isOpen} onClose={closeHandler}>
|
||||||
|
<ModalOverlay />
|
||||||
|
<ModalContent>
|
||||||
|
<ModalHeader>New JWT keys</ModalHeader>
|
||||||
|
<ModalCloseButton />
|
||||||
|
<ModalBody>
|
||||||
|
<Flex>
|
||||||
|
<Flex w="30%" justifyContent="start" alignItems="center">
|
||||||
|
<Text fontSize="sm">JWT Type:</Text>
|
||||||
|
</Flex>
|
||||||
|
<InputField
|
||||||
|
variables={stateVariables}
|
||||||
|
setVariables={setStateVariables}
|
||||||
|
inputType={SelectInputType.JWT_TYPE}
|
||||||
|
value={SelectInputType.JWT_TYPE}
|
||||||
|
options={{
|
||||||
|
...HMACEncryptionType,
|
||||||
|
...RSAEncryptionType,
|
||||||
|
...ECDSAEncryptionType,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Flex>
|
||||||
|
{isLoading ? (
|
||||||
|
<Center minH="25vh">
|
||||||
|
<Spinner />
|
||||||
|
</Center>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
{Object.values(HMACEncryptionType).includes(
|
||||||
|
stateVariables.JWT_TYPE
|
||||||
|
) ? (
|
||||||
|
<Flex marginTop="8">
|
||||||
|
<Flex w="23%" justifyContent="start" alignItems="center">
|
||||||
|
<Text fontSize="sm">JWT Secret</Text>
|
||||||
|
</Flex>
|
||||||
|
<Center w="77%">
|
||||||
|
<Input
|
||||||
|
size="sm"
|
||||||
|
value={stateVariables.JWT_SECRET}
|
||||||
|
onChange={(event: any) =>
|
||||||
|
setStateVariables({
|
||||||
|
...stateVariables,
|
||||||
|
JWT_SECRET: event.target.value,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
readOnly
|
||||||
|
/>
|
||||||
|
</Center>
|
||||||
|
</Flex>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<Flex marginTop="8">
|
||||||
|
<Flex w="23%" justifyContent="start" alignItems="center">
|
||||||
|
<Text fontSize="sm">Public Key</Text>
|
||||||
|
</Flex>
|
||||||
|
<Center w="77%">
|
||||||
|
<InputField
|
||||||
|
variables={stateVariables}
|
||||||
|
setVariables={setStateVariables}
|
||||||
|
inputType={TextAreaInputType.JWT_PUBLIC_KEY}
|
||||||
|
placeholder="Add public key here"
|
||||||
|
minH="25vh"
|
||||||
|
readOnly
|
||||||
|
/>
|
||||||
|
</Center>
|
||||||
|
</Flex>
|
||||||
|
<Flex marginTop="8">
|
||||||
|
<Flex w="23%" justifyContent="start" alignItems="center">
|
||||||
|
<Text fontSize="sm">Private Key</Text>
|
||||||
|
</Flex>
|
||||||
|
<Center w="77%">
|
||||||
|
<InputField
|
||||||
|
variables={stateVariables}
|
||||||
|
setVariables={setStateVariables}
|
||||||
|
inputType={TextAreaInputType.JWT_PRIVATE_KEY}
|
||||||
|
placeholder="Add private key here"
|
||||||
|
minH="25vh"
|
||||||
|
readOnly
|
||||||
|
/>
|
||||||
|
</Center>
|
||||||
|
</Flex>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</ModalBody>
|
||||||
|
|
||||||
|
<ModalFooter>
|
||||||
|
<Button
|
||||||
|
leftIcon={<FaSave />}
|
||||||
|
colorScheme="blue"
|
||||||
|
variant="solid"
|
||||||
|
onClick={saveHandler}
|
||||||
|
isDisabled={isLoading}
|
||||||
|
>
|
||||||
|
<Center h="100%" pt="5%">
|
||||||
|
Apply
|
||||||
|
</Center>
|
||||||
|
</Button>
|
||||||
|
</ModalFooter>
|
||||||
|
</ModalContent>
|
||||||
|
</Modal>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default GenerateKeysModal;
|
|
@ -26,7 +26,6 @@ import {
|
||||||
import { useClient } from 'urql';
|
import { useClient } from 'urql';
|
||||||
import { FaUserPlus, FaMinusCircle, FaPlus, FaUpload } from 'react-icons/fa';
|
import { FaUserPlus, FaMinusCircle, FaPlus, FaUpload } from 'react-icons/fa';
|
||||||
import { useDropzone } from 'react-dropzone';
|
import { useDropzone } from 'react-dropzone';
|
||||||
import { escape } from 'lodash';
|
|
||||||
import { validateEmail, validateURI } from '../utils';
|
import { validateEmail, validateURI } from '../utils';
|
||||||
import { InviteMembers } from '../graphql/mutation';
|
import { InviteMembers } from '../graphql/mutation';
|
||||||
import { ArrayInputOperations } from '../constants';
|
import { ArrayInputOperations } from '../constants';
|
||||||
|
|
|
@ -2,6 +2,7 @@ export const LOGO_URL =
|
||||||
'https://user-images.githubusercontent.com/6964334/147834043-fc384cab-e7ca-40f8-9663-38fc25fd5f3a.png';
|
'https://user-images.githubusercontent.com/6964334/147834043-fc384cab-e7ca-40f8-9663-38fc25fd5f3a.png';
|
||||||
|
|
||||||
export const TextInputType = {
|
export const TextInputType = {
|
||||||
|
ACCESS_TOKEN_EXPIRY_TIME: 'ACCESS_TOKEN_EXPIRY_TIME',
|
||||||
CLIENT_ID: 'CLIENT_ID',
|
CLIENT_ID: 'CLIENT_ID',
|
||||||
GOOGLE_CLIENT_ID: 'GOOGLE_CLIENT_ID',
|
GOOGLE_CLIENT_ID: 'GOOGLE_CLIENT_ID',
|
||||||
GITHUB_CLIENT_ID: 'GITHUB_CLIENT_ID',
|
GITHUB_CLIENT_ID: 'GITHUB_CLIENT_ID',
|
||||||
|
@ -89,3 +90,41 @@ export const ECDSAEncryptionType = {
|
||||||
ES384: 'ES384',
|
ES384: 'ES384',
|
||||||
ES512: 'ES512',
|
ES512: 'ES512',
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export interface envVarTypes {
|
||||||
|
GOOGLE_CLIENT_ID: string;
|
||||||
|
GOOGLE_CLIENT_SECRET: string;
|
||||||
|
GITHUB_CLIENT_ID: string;
|
||||||
|
GITHUB_CLIENT_SECRET: string;
|
||||||
|
FACEBOOK_CLIENT_ID: string;
|
||||||
|
FACEBOOK_CLIENT_SECRET: string;
|
||||||
|
ROLES: [string] | [];
|
||||||
|
DEFAULT_ROLES: [string] | [];
|
||||||
|
PROTECTED_ROLES: [string] | [];
|
||||||
|
JWT_TYPE: string;
|
||||||
|
JWT_SECRET: string;
|
||||||
|
JWT_ROLE_CLAIM: string;
|
||||||
|
JWT_PRIVATE_KEY: string;
|
||||||
|
JWT_PUBLIC_KEY: string;
|
||||||
|
REDIS_URL: string;
|
||||||
|
SMTP_HOST: string;
|
||||||
|
SMTP_PORT: string;
|
||||||
|
SMTP_USERNAME: string;
|
||||||
|
SMTP_PASSWORD: string;
|
||||||
|
SENDER_EMAIL: string;
|
||||||
|
ALLOWED_ORIGINS: [string] | [];
|
||||||
|
ORGANIZATION_NAME: string;
|
||||||
|
ORGANIZATION_LOGO: string;
|
||||||
|
CUSTOM_ACCESS_TOKEN_SCRIPT: string;
|
||||||
|
ADMIN_SECRET: string;
|
||||||
|
DISABLE_LOGIN_PAGE: boolean;
|
||||||
|
DISABLE_MAGIC_LINK_LOGIN: boolean;
|
||||||
|
DISABLE_EMAIL_VERIFICATION: boolean;
|
||||||
|
DISABLE_BASIC_AUTHENTICATION: boolean;
|
||||||
|
DISABLE_SIGN_UP: boolean;
|
||||||
|
OLD_ADMIN_SECRET: string;
|
||||||
|
DATABASE_NAME: string;
|
||||||
|
DATABASE_TYPE: string;
|
||||||
|
DATABASE_URL: string;
|
||||||
|
ACCESS_TOKEN_EXPIRY_TIME: string;
|
||||||
|
}
|
||||||
|
|
|
@ -53,3 +53,29 @@ export const InviteMembers = `
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
export const RevokeAccess = `
|
||||||
|
mutation revokeAccess($param: UpdateAccessInput!) {
|
||||||
|
_revoke_access(param: $param) {
|
||||||
|
message
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
export const EnableAccess = `
|
||||||
|
mutation revokeAccess($param: UpdateAccessInput!) {
|
||||||
|
_enable_access(param: $param) {
|
||||||
|
message
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
export const GenerateKeys = `
|
||||||
|
mutation generateKeys($params: GenerateJWTKeysInput!) {
|
||||||
|
_generate_jwt_keys(params: $params) {
|
||||||
|
secret
|
||||||
|
public_key
|
||||||
|
private_key
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
|
@ -53,6 +53,7 @@ export const EnvVariablesQuery = `
|
||||||
DATABASE_NAME,
|
DATABASE_NAME,
|
||||||
DATABASE_TYPE,
|
DATABASE_TYPE,
|
||||||
DATABASE_URL,
|
DATABASE_URL,
|
||||||
|
ACCESS_TOKEN_EXPIRY_TIME,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
@ -81,6 +82,7 @@ export const UserDetailsQuery = `
|
||||||
signup_methods
|
signup_methods
|
||||||
roles
|
roles
|
||||||
created_at
|
created_at
|
||||||
|
revoked_timestamp
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -34,46 +34,11 @@ import {
|
||||||
HMACEncryptionType,
|
HMACEncryptionType,
|
||||||
RSAEncryptionType,
|
RSAEncryptionType,
|
||||||
ECDSAEncryptionType,
|
ECDSAEncryptionType,
|
||||||
|
envVarTypes,
|
||||||
} from '../constants';
|
} from '../constants';
|
||||||
import { UpdateEnvVariables } from '../graphql/mutation';
|
import { UpdateEnvVariables } from '../graphql/mutation';
|
||||||
import { getObjectDiff, capitalizeFirstLetter } from '../utils';
|
import { getObjectDiff, capitalizeFirstLetter } from '../utils';
|
||||||
|
import GenerateKeysModal from '../components/GenerateKeysModal';
|
||||||
interface envVarTypes {
|
|
||||||
GOOGLE_CLIENT_ID: string;
|
|
||||||
GOOGLE_CLIENT_SECRET: string;
|
|
||||||
GITHUB_CLIENT_ID: string;
|
|
||||||
GITHUB_CLIENT_SECRET: string;
|
|
||||||
FACEBOOK_CLIENT_ID: string;
|
|
||||||
FACEBOOK_CLIENT_SECRET: string;
|
|
||||||
ROLES: [string] | [];
|
|
||||||
DEFAULT_ROLES: [string] | [];
|
|
||||||
PROTECTED_ROLES: [string] | [];
|
|
||||||
JWT_TYPE: string;
|
|
||||||
JWT_SECRET: string;
|
|
||||||
JWT_ROLE_CLAIM: string;
|
|
||||||
JWT_PRIVATE_KEY: string;
|
|
||||||
JWT_PUBLIC_KEY: string;
|
|
||||||
REDIS_URL: string;
|
|
||||||
SMTP_HOST: string;
|
|
||||||
SMTP_PORT: string;
|
|
||||||
SMTP_USERNAME: string;
|
|
||||||
SMTP_PASSWORD: string;
|
|
||||||
SENDER_EMAIL: string;
|
|
||||||
ALLOWED_ORIGINS: [string] | [];
|
|
||||||
ORGANIZATION_NAME: string;
|
|
||||||
ORGANIZATION_LOGO: string;
|
|
||||||
CUSTOM_ACCESS_TOKEN_SCRIPT: string;
|
|
||||||
ADMIN_SECRET: string;
|
|
||||||
DISABLE_LOGIN_PAGE: boolean;
|
|
||||||
DISABLE_MAGIC_LINK_LOGIN: boolean;
|
|
||||||
DISABLE_EMAIL_VERIFICATION: boolean;
|
|
||||||
DISABLE_BASIC_AUTHENTICATION: boolean;
|
|
||||||
DISABLE_SIGN_UP: boolean;
|
|
||||||
OLD_ADMIN_SECRET: string;
|
|
||||||
DATABASE_NAME: string;
|
|
||||||
DATABASE_TYPE: string;
|
|
||||||
DATABASE_URL: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function Environment() {
|
export default function Environment() {
|
||||||
const client = useClient();
|
const client = useClient();
|
||||||
|
@ -120,6 +85,7 @@ export default function Environment() {
|
||||||
DATABASE_NAME: '',
|
DATABASE_NAME: '',
|
||||||
DATABASE_TYPE: '',
|
DATABASE_TYPE: '',
|
||||||
DATABASE_URL: '',
|
DATABASE_URL: '',
|
||||||
|
ACCESS_TOKEN_EXPIRY_TIME: '',
|
||||||
});
|
});
|
||||||
|
|
||||||
const [fieldVisibility, setFieldVisibility] = React.useState<
|
const [fieldVisibility, setFieldVisibility] = React.useState<
|
||||||
|
@ -134,32 +100,24 @@ export default function Environment() {
|
||||||
OLD_ADMIN_SECRET: false,
|
OLD_ADMIN_SECRET: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
async function getData() {
|
||||||
|
const {
|
||||||
|
data: { _env: envData },
|
||||||
|
} = await client.query(EnvVariablesQuery).toPromise();
|
||||||
|
setLoading(false);
|
||||||
|
setEnvVariables({
|
||||||
|
...envData,
|
||||||
|
OLD_ADMIN_SECRET: envData.ADMIN_SECRET,
|
||||||
|
ADMIN_SECRET: '',
|
||||||
|
});
|
||||||
|
setAdminSecret({
|
||||||
|
value: '',
|
||||||
|
disableInputField: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
let isMounted = true;
|
|
||||||
async function getData() {
|
|
||||||
const {
|
|
||||||
data: { _env: envData },
|
|
||||||
} = await client.query(EnvVariablesQuery).toPromise();
|
|
||||||
|
|
||||||
if (isMounted) {
|
|
||||||
setLoading(false);
|
|
||||||
setEnvVariables({
|
|
||||||
...envData,
|
|
||||||
OLD_ADMIN_SECRET: envData.ADMIN_SECRET,
|
|
||||||
ADMIN_SECRET: '',
|
|
||||||
});
|
|
||||||
setAdminSecret({
|
|
||||||
value: '',
|
|
||||||
disableInputField: true,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
getData();
|
getData();
|
||||||
|
|
||||||
return () => {
|
|
||||||
isMounted = false;
|
|
||||||
};
|
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const validateAdminSecretHandler = (event: any) => {
|
const validateAdminSecretHandler = (event: any) => {
|
||||||
|
@ -230,6 +188,8 @@ export default function Environment() {
|
||||||
disableInputField: true,
|
disableInputField: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
getData();
|
||||||
|
|
||||||
toast({
|
toast({
|
||||||
title: `Successfully updated ${
|
title: `Successfully updated ${
|
||||||
Object.keys(updatedEnvVariables).length
|
Object.keys(updatedEnvVariables).length
|
||||||
|
@ -256,7 +216,7 @@ export default function Environment() {
|
||||||
setVariables={() => {}}
|
setVariables={() => {}}
|
||||||
inputType={TextInputType.CLIENT_ID}
|
inputType={TextInputType.CLIENT_ID}
|
||||||
placeholder="Client ID"
|
placeholder="Client ID"
|
||||||
isDisabled={true}
|
readOnly={true}
|
||||||
/>
|
/>
|
||||||
</Center>
|
</Center>
|
||||||
</Flex>
|
</Flex>
|
||||||
|
@ -272,7 +232,7 @@ export default function Environment() {
|
||||||
setFieldVisibility={setFieldVisibility}
|
setFieldVisibility={setFieldVisibility}
|
||||||
inputType={HiddenInputType.CLIENT_SECRET}
|
inputType={HiddenInputType.CLIENT_SECRET}
|
||||||
placeholder="Client Secret"
|
placeholder="Client Secret"
|
||||||
isDisabled={true}
|
readOnly={true}
|
||||||
/>
|
/>
|
||||||
</Center>
|
</Center>
|
||||||
</Flex>
|
</Flex>
|
||||||
|
@ -410,9 +370,22 @@ export default function Environment() {
|
||||||
</Flex>
|
</Flex>
|
||||||
</Stack>
|
</Stack>
|
||||||
<Divider marginTop="2%" marginBottom="2%" />
|
<Divider marginTop="2%" marginBottom="2%" />
|
||||||
<Text fontSize="md" paddingTop="2%" fontWeight="bold">
|
<Flex
|
||||||
JWT (JSON Web Tokens) Configurations
|
width="100%"
|
||||||
</Text>
|
justifyContent="space-between"
|
||||||
|
alignItems="center"
|
||||||
|
paddingTop="2%"
|
||||||
|
>
|
||||||
|
<Text fontSize="md" fontWeight="bold">
|
||||||
|
JWT (JSON Web Tokens) Configurations
|
||||||
|
</Text>
|
||||||
|
<Flex>
|
||||||
|
<GenerateKeysModal
|
||||||
|
jwtType={envVariables.JWT_TYPE}
|
||||||
|
getData={getData}
|
||||||
|
/>
|
||||||
|
</Flex>
|
||||||
|
</Flex>
|
||||||
<Stack spacing={6} padding="2% 0%">
|
<Stack spacing={6} padding="2% 0%">
|
||||||
<Flex>
|
<Flex>
|
||||||
<Flex w="30%" justifyContent="start" alignItems="center">
|
<Flex w="30%" justifyContent="start" alignItems="center">
|
||||||
|
@ -628,11 +601,28 @@ export default function Environment() {
|
||||||
</Stack>
|
</Stack>
|
||||||
<Divider marginTop="2%" marginBottom="2%" />
|
<Divider marginTop="2%" marginBottom="2%" />
|
||||||
<Text fontSize="md" paddingTop="2%" fontWeight="bold">
|
<Text fontSize="md" paddingTop="2%" fontWeight="bold">
|
||||||
Custom Access Token Scripts
|
Access Token
|
||||||
</Text>
|
</Text>
|
||||||
<Stack spacing={6} padding="2% 0%">
|
<Stack spacing={6} padding="2% 0%">
|
||||||
<Flex>
|
<Flex>
|
||||||
<Center w="100%">
|
<Flex w="30%" justifyContent="start" alignItems="center">
|
||||||
|
<Text fontSize="sm">Access Token Expiry Time:</Text>
|
||||||
|
</Flex>
|
||||||
|
<Flex w="70%">
|
||||||
|
<InputField
|
||||||
|
variables={envVariables}
|
||||||
|
setVariables={setEnvVariables}
|
||||||
|
inputType={TextInputType.ACCESS_TOKEN_EXPIRY_TIME}
|
||||||
|
placeholder="0h15m0s"
|
||||||
|
/>
|
||||||
|
</Flex>
|
||||||
|
</Flex>
|
||||||
|
<Flex>
|
||||||
|
<Flex w="30%" justifyContent="start" direction="column">
|
||||||
|
<Text fontSize="sm">Custom Scripts:</Text>
|
||||||
|
<Text fontSize="sm">Used to add custom fields in ID token</Text>
|
||||||
|
</Flex>
|
||||||
|
<Flex w="70%">
|
||||||
<InputField
|
<InputField
|
||||||
variables={envVariables}
|
variables={envVariables}
|
||||||
setVariables={setEnvVariables}
|
setVariables={setEnvVariables}
|
||||||
|
@ -640,7 +630,7 @@ export default function Environment() {
|
||||||
placeholder="Add script here"
|
placeholder="Add script here"
|
||||||
minH="25vh"
|
minH="25vh"
|
||||||
/>
|
/>
|
||||||
</Center>
|
</Flex>
|
||||||
</Flex>
|
</Flex>
|
||||||
</Stack>
|
</Stack>
|
||||||
<Divider marginTop="2%" marginBottom="2%" />
|
<Divider marginTop="2%" marginBottom="2%" />
|
||||||
|
|
|
@ -39,7 +39,7 @@ import {
|
||||||
FaAngleDown,
|
FaAngleDown,
|
||||||
} from 'react-icons/fa';
|
} from 'react-icons/fa';
|
||||||
import { EmailVerificationQuery, UserDetailsQuery } from '../graphql/queries';
|
import { EmailVerificationQuery, UserDetailsQuery } from '../graphql/queries';
|
||||||
import { UpdateUser } from '../graphql/mutation';
|
import { EnableAccess, RevokeAccess, UpdateUser } from '../graphql/mutation';
|
||||||
import EditUserModal from '../components/EditUserModal';
|
import EditUserModal from '../components/EditUserModal';
|
||||||
import DeleteUserModal from '../components/DeleteUserModal';
|
import DeleteUserModal from '../components/DeleteUserModal';
|
||||||
import InviteMembersModal from '../components/InviteMembersModal';
|
import InviteMembersModal from '../components/InviteMembersModal';
|
||||||
|
@ -67,6 +67,12 @@ interface userDataTypes {
|
||||||
signup_methods: string;
|
signup_methods: string;
|
||||||
roles: [string];
|
roles: [string];
|
||||||
created_at: number;
|
created_at: number;
|
||||||
|
revoked_timestamp: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
const enum updateAccessActions {
|
||||||
|
REVOKE = 'REVOKE',
|
||||||
|
ENABLE = 'ENABLE',
|
||||||
}
|
}
|
||||||
|
|
||||||
const getMaxPages = (pagination: paginationPropTypes) => {
|
const getMaxPages = (pagination: paginationPropTypes) => {
|
||||||
|
@ -185,6 +191,66 @@ export default function Users() {
|
||||||
updateUserList();
|
updateUserList();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const updateAccessHandler = async (
|
||||||
|
id: string,
|
||||||
|
action: updateAccessActions
|
||||||
|
) => {
|
||||||
|
switch (action) {
|
||||||
|
case updateAccessActions.ENABLE:
|
||||||
|
const enableAccessRes = await client
|
||||||
|
.mutation(EnableAccess, {
|
||||||
|
param: {
|
||||||
|
user_id: id,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.toPromise();
|
||||||
|
if (enableAccessRes.error) {
|
||||||
|
toast({
|
||||||
|
title: 'User access enable failed',
|
||||||
|
isClosable: true,
|
||||||
|
status: 'error',
|
||||||
|
position: 'bottom-right',
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
toast({
|
||||||
|
title: 'User access enabled successfully',
|
||||||
|
isClosable: true,
|
||||||
|
status: 'success',
|
||||||
|
position: 'bottom-right',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
updateUserList();
|
||||||
|
break;
|
||||||
|
case updateAccessActions.REVOKE:
|
||||||
|
const revokeAccessRes = await client
|
||||||
|
.mutation(RevokeAccess, {
|
||||||
|
param: {
|
||||||
|
user_id: id,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.toPromise();
|
||||||
|
if (revokeAccessRes.error) {
|
||||||
|
toast({
|
||||||
|
title: 'User access revoke failed',
|
||||||
|
isClosable: true,
|
||||||
|
status: 'error',
|
||||||
|
position: 'bottom-right',
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
toast({
|
||||||
|
title: 'User access revoked successfully',
|
||||||
|
isClosable: true,
|
||||||
|
status: 'success',
|
||||||
|
position: 'bottom-right',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
updateUserList();
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box m="5" py="5" px="10" bg="white" rounded="md">
|
<Box m="5" py="5" px="10" bg="white" rounded="md">
|
||||||
<Flex margin="2% 0" justifyContent="space-between" alignItems="center">
|
<Flex margin="2% 0" justifyContent="space-between" alignItems="center">
|
||||||
|
@ -206,6 +272,7 @@ export default function Users() {
|
||||||
<Th>Signup Methods</Th>
|
<Th>Signup Methods</Th>
|
||||||
<Th>Roles</Th>
|
<Th>Roles</Th>
|
||||||
<Th>Verified</Th>
|
<Th>Verified</Th>
|
||||||
|
<Th>Access</Th>
|
||||||
<Th>Actions</Th>
|
<Th>Actions</Th>
|
||||||
</Tr>
|
</Tr>
|
||||||
</Thead>
|
</Thead>
|
||||||
|
@ -214,7 +281,7 @@ export default function Users() {
|
||||||
const { email_verified, created_at, ...rest }: any = user;
|
const { email_verified, created_at, ...rest }: any = user;
|
||||||
return (
|
return (
|
||||||
<Tr key={user.id} style={{ fontSize: 14 }}>
|
<Tr key={user.id} style={{ fontSize: 14 }}>
|
||||||
<Td>{user.email}</Td>
|
<Td maxW="300">{user.email}</Td>
|
||||||
<Td>
|
<Td>
|
||||||
{dayjs(user.created_at * 1000).format('MMM DD, YYYY')}
|
{dayjs(user.created_at * 1000).format('MMM DD, YYYY')}
|
||||||
</Td>
|
</Td>
|
||||||
|
@ -229,6 +296,15 @@ export default function Users() {
|
||||||
{user.email_verified.toString()}
|
{user.email_verified.toString()}
|
||||||
</Tag>
|
</Tag>
|
||||||
</Td>
|
</Td>
|
||||||
|
<Td>
|
||||||
|
<Tag
|
||||||
|
size="sm"
|
||||||
|
variant="outline"
|
||||||
|
colorScheme={user.revoked_timestamp ? 'red' : 'green'}
|
||||||
|
>
|
||||||
|
{user.revoked_timestamp ? 'Revoked' : 'Enabled'}
|
||||||
|
</Tag>
|
||||||
|
</Td>
|
||||||
<Td>
|
<Td>
|
||||||
<Menu>
|
<Menu>
|
||||||
<MenuButton as={Button} variant="unstyled" size="sm">
|
<MenuButton as={Button} variant="unstyled" size="sm">
|
||||||
|
@ -258,6 +334,29 @@ export default function Users() {
|
||||||
user={rest}
|
user={rest}
|
||||||
updateUserList={updateUserList}
|
updateUserList={updateUserList}
|
||||||
/>
|
/>
|
||||||
|
{user.revoked_timestamp ? (
|
||||||
|
<MenuItem
|
||||||
|
onClick={() =>
|
||||||
|
updateAccessHandler(
|
||||||
|
user.id,
|
||||||
|
updateAccessActions.ENABLE
|
||||||
|
)
|
||||||
|
}
|
||||||
|
>
|
||||||
|
Enable Access
|
||||||
|
</MenuItem>
|
||||||
|
) : (
|
||||||
|
<MenuItem
|
||||||
|
onClick={() =>
|
||||||
|
updateAccessHandler(
|
||||||
|
user.id,
|
||||||
|
updateAccessActions.REVOKE
|
||||||
|
)
|
||||||
|
}
|
||||||
|
>
|
||||||
|
Revoke Access
|
||||||
|
</MenuItem>
|
||||||
|
)}
|
||||||
</MenuList>
|
</MenuList>
|
||||||
</Menu>
|
</Menu>
|
||||||
</Td>
|
</Td>
|
||||||
|
|
|
@ -16,11 +16,12 @@ const (
|
||||||
// EnvKeyEnvPath key for cli arg variable ENV_PATH
|
// EnvKeyEnvPath key for cli arg variable ENV_PATH
|
||||||
EnvKeyEnvPath = "ENV_PATH"
|
EnvKeyEnvPath = "ENV_PATH"
|
||||||
// EnvKeyAuthorizerURL key for env variable AUTHORIZER_URL
|
// EnvKeyAuthorizerURL key for env variable AUTHORIZER_URL
|
||||||
// TODO: remove support AUTHORIZER_URL env
|
|
||||||
EnvKeyAuthorizerURL = "AUTHORIZER_URL"
|
EnvKeyAuthorizerURL = "AUTHORIZER_URL"
|
||||||
// EnvKeyPort key for env variable PORT
|
// EnvKeyPort key for env variable PORT
|
||||||
EnvKeyPort = "PORT"
|
EnvKeyPort = "PORT"
|
||||||
|
|
||||||
|
// EnvKeyAccessTokenExpiryTime key for env variable ACCESS_TOKEN_EXPIRY_TIME
|
||||||
|
EnvKeyAccessTokenExpiryTime = "ACCESS_TOKEN_EXPIRY_TIME"
|
||||||
// EnvKeyAdminSecret key for env variable ADMIN_SECRET
|
// EnvKeyAdminSecret key for env variable ADMIN_SECRET
|
||||||
EnvKeyAdminSecret = "ADMIN_SECRET"
|
EnvKeyAdminSecret = "ADMIN_SECRET"
|
||||||
// EnvKeyDatabaseType key for env variable DATABASE_TYPE
|
// EnvKeyDatabaseType key for env variable DATABASE_TYPE
|
||||||
|
|
|
@ -27,6 +27,7 @@ type User struct {
|
||||||
Roles string `json:"roles" bson:"roles" cql:"roles"`
|
Roles string `json:"roles" bson:"roles" cql:"roles"`
|
||||||
UpdatedAt int64 `json:"updated_at" bson:"updated_at" cql:"updated_at"`
|
UpdatedAt int64 `json:"updated_at" bson:"updated_at" cql:"updated_at"`
|
||||||
CreatedAt int64 `json:"created_at" bson:"created_at" cql:"created_at"`
|
CreatedAt int64 `json:"created_at" bson:"created_at" cql:"created_at"`
|
||||||
|
RevokedTimestamp *int64 `json:"revoked_timestamp" bson:"revoked_timestamp" cql:"revoked_timestamp"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (user *User) AsAPIUser() *model.User {
|
func (user *User) AsAPIUser() *model.User {
|
||||||
|
@ -35,6 +36,7 @@ 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,
|
||||||
|
@ -53,5 +55,6 @@ func (user *User) AsAPIUser() *model.User {
|
||||||
Roles: strings.Split(user.Roles, ","),
|
Roles: strings.Split(user.Roles, ","),
|
||||||
CreatedAt: &createdAt,
|
CreatedAt: &createdAt,
|
||||||
UpdatedAt: &updatedAt,
|
UpdatedAt: &updatedAt,
|
||||||
|
RevokedTimestamp: revokedTimestamp,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,14 +4,14 @@ import "github.com/authorizerdev/authorizer/server/graph/model"
|
||||||
|
|
||||||
// VerificationRequest model for db
|
// VerificationRequest model for db
|
||||||
type VerificationRequest struct {
|
type VerificationRequest struct {
|
||||||
Key string `json:"_key,omitempty" bson:"_key,omitempty" cql:"_key,omitempty"` // for arangodb
|
Key string `json:"_key,omitempty" bson:"_key" cql:"_key,omitempty"` // for arangodb
|
||||||
ID string `gorm:"primaryKey;type:char(36)" json:"_id" bson:"_id" cql:"id"`
|
ID string `gorm:"primaryKey;type:char(36)" json:"_id" bson:"_id" cql:"id"`
|
||||||
Token string `gorm:"type:text" json:"token" bson:"token" cql:"token"`
|
Token string `gorm:"type:text" json:"token" bson:"token" cql:"token"`
|
||||||
Identifier string `gorm:"uniqueIndex:idx_email_identifier" json:"identifier" bson:"identifier" cql:"identifier"`
|
Identifier string `gorm:"uniqueIndex:idx_email_identifier;type:varchar(64)" json:"identifier" bson:"identifier" cql:"identifier"`
|
||||||
ExpiresAt int64 `json:"expires_at" bson:"expires_at" cql:"expires_at"`
|
ExpiresAt int64 `json:"expires_at" bson:"expires_at" cql:"expires_at"`
|
||||||
CreatedAt int64 `json:"created_at" bson:"created_at" cql:"created_at"`
|
CreatedAt int64 `json:"created_at" bson:"created_at" cql:"created_at"`
|
||||||
UpdatedAt int64 `json:"updated_at" bson:"updated_at" cql:"updated_at"`
|
UpdatedAt int64 `json:"updated_at" bson:"updated_at" cql:"updated_at"`
|
||||||
Email string `gorm:"uniqueIndex:idx_email_identifier" json:"email" bson:"email" cql:"email"`
|
Email string `gorm:"uniqueIndex:idx_email_identifier;type:varchar(256)" json:"email" bson:"email" cql:"email"`
|
||||||
Nonce string `gorm:"type:text" json:"nonce" bson:"nonce" cql:"nonce"`
|
Nonce string `gorm:"type:text" json:"nonce" bson:"nonce" cql:"nonce"`
|
||||||
RedirectURI string `gorm:"type:text" json:"redirect_uri" bson:"redirect_uri" cql:"redirect_uri"`
|
RedirectURI string `gorm:"type:text" json:"redirect_uri" bson:"redirect_uri" cql:"redirect_uri"`
|
||||||
}
|
}
|
||||||
|
|
11
server/env/env.go
vendored
11
server/env/env.go
vendored
|
@ -113,6 +113,10 @@ func InitAllEnv() error {
|
||||||
envData.StringEnv[constants.EnvKeyAppURL] = os.Getenv(constants.EnvKeyAppURL)
|
envData.StringEnv[constants.EnvKeyAppURL] = os.Getenv(constants.EnvKeyAppURL)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if envData.StringEnv[constants.EnvKeyAuthorizerURL] == "" {
|
||||||
|
envData.StringEnv[constants.EnvKeyAuthorizerURL] = os.Getenv(constants.EnvKeyAuthorizerURL)
|
||||||
|
}
|
||||||
|
|
||||||
if envData.StringEnv[constants.EnvKeyPort] == "" {
|
if envData.StringEnv[constants.EnvKeyPort] == "" {
|
||||||
envData.StringEnv[constants.EnvKeyPort] = os.Getenv(constants.EnvKeyPort)
|
envData.StringEnv[constants.EnvKeyPort] = os.Getenv(constants.EnvKeyPort)
|
||||||
if envData.StringEnv[constants.EnvKeyPort] == "" {
|
if envData.StringEnv[constants.EnvKeyPort] == "" {
|
||||||
|
@ -120,6 +124,13 @@ func InitAllEnv() error {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if envData.StringEnv[constants.EnvKeyAccessTokenExpiryTime] == "" {
|
||||||
|
envData.StringEnv[constants.EnvKeyAccessTokenExpiryTime] = os.Getenv(constants.EnvKeyAccessTokenExpiryTime)
|
||||||
|
if envData.StringEnv[constants.EnvKeyAccessTokenExpiryTime] == "" {
|
||||||
|
envData.StringEnv[constants.EnvKeyAccessTokenExpiryTime] = "30m"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if envData.StringEnv[constants.EnvKeyAdminSecret] == "" {
|
if envData.StringEnv[constants.EnvKeyAdminSecret] == "" {
|
||||||
envData.StringEnv[constants.EnvKeyAdminSecret] = os.Getenv(constants.EnvKeyAdminSecret)
|
envData.StringEnv[constants.EnvKeyAdminSecret] = os.Getenv(constants.EnvKeyAdminSecret)
|
||||||
}
|
}
|
||||||
|
|
1
server/env/persist_env.go
vendored
1
server/env/persist_env.go
vendored
|
@ -165,6 +165,7 @@ func PersistEnv() error {
|
||||||
hasChanged = true
|
hasChanged = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
envstore.EnvStoreObj.UpdateEnvStore(storeData)
|
envstore.EnvStoreObj.UpdateEnvStore(storeData)
|
||||||
jwk, err := crypto.GenerateJWKBasedOnEnv()
|
jwk, err := crypto.GenerateJWKBasedOnEnv()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -24,6 +24,7 @@ type DeleteUserInput struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
type Env struct {
|
type Env struct {
|
||||||
|
AccessTokenExpiryTime *string `json:"ACCESS_TOKEN_EXPIRY_TIME"`
|
||||||
AdminSecret *string `json:"ADMIN_SECRET"`
|
AdminSecret *string `json:"ADMIN_SECRET"`
|
||||||
DatabaseName string `json:"DATABASE_NAME"`
|
DatabaseName string `json:"DATABASE_NAME"`
|
||||||
DatabaseURL string `json:"DATABASE_URL"`
|
DatabaseURL string `json:"DATABASE_URL"`
|
||||||
|
@ -75,6 +76,16 @@ type ForgotPasswordInput struct {
|
||||||
RedirectURI *string `json:"redirect_uri"`
|
RedirectURI *string `json:"redirect_uri"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type GenerateJWTKeysInput struct {
|
||||||
|
Type string `json:"type"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type GenerateJWTKeysResponse struct {
|
||||||
|
Secret *string `json:"secret"`
|
||||||
|
PublicKey *string `json:"public_key"`
|
||||||
|
PrivateKey *string `json:"private_key"`
|
||||||
|
}
|
||||||
|
|
||||||
type InviteMemberInput struct {
|
type InviteMemberInput struct {
|
||||||
Emails []string `json:"emails"`
|
Emails []string `json:"emails"`
|
||||||
RedirectURI *string `json:"redirect_uri"`
|
RedirectURI *string `json:"redirect_uri"`
|
||||||
|
@ -164,7 +175,12 @@ type SignUpInput struct {
|
||||||
RedirectURI *string `json:"redirect_uri"`
|
RedirectURI *string `json:"redirect_uri"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type UpdateAccessInput struct {
|
||||||
|
UserID string `json:"user_id"`
|
||||||
|
}
|
||||||
|
|
||||||
type UpdateEnvInput struct {
|
type UpdateEnvInput struct {
|
||||||
|
AccessTokenExpiryTime *string `json:"ACCESS_TOKEN_EXPIRY_TIME"`
|
||||||
AdminSecret *string `json:"ADMIN_SECRET"`
|
AdminSecret *string `json:"ADMIN_SECRET"`
|
||||||
CustomAccessTokenScript *string `json:"CUSTOM_ACCESS_TOKEN_SCRIPT"`
|
CustomAccessTokenScript *string `json:"CUSTOM_ACCESS_TOKEN_SCRIPT"`
|
||||||
OldAdminSecret *string `json:"OLD_ADMIN_SECRET"`
|
OldAdminSecret *string `json:"OLD_ADMIN_SECRET"`
|
||||||
|
@ -249,6 +265,7 @@ type User struct {
|
||||||
Roles []string `json:"roles"`
|
Roles []string `json:"roles"`
|
||||||
CreatedAt *int64 `json:"created_at"`
|
CreatedAt *int64 `json:"created_at"`
|
||||||
UpdatedAt *int64 `json:"updated_at"`
|
UpdatedAt *int64 `json:"updated_at"`
|
||||||
|
RevokedTimestamp *int64 `json:"revoked_timestamp"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type Users struct {
|
type Users struct {
|
||||||
|
@ -256,6 +273,16 @@ type Users struct {
|
||||||
Users []*User `json:"users"`
|
Users []*User `json:"users"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type ValidateJWTTokenInput struct {
|
||||||
|
TokenType string `json:"token_type"`
|
||||||
|
Token string `json:"token"`
|
||||||
|
Roles []string `json:"roles"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ValidateJWTTokenResponse struct {
|
||||||
|
IsValid bool `json:"is_valid"`
|
||||||
|
}
|
||||||
|
|
||||||
type VerificationRequest struct {
|
type VerificationRequest struct {
|
||||||
ID string `json:"id"`
|
ID string `json:"id"`
|
||||||
Identifier *string `json:"identifier"`
|
Identifier *string `json:"identifier"`
|
||||||
|
|
|
@ -43,6 +43,7 @@ type User {
|
||||||
roles: [String!]!
|
roles: [String!]!
|
||||||
created_at: Int64
|
created_at: Int64
|
||||||
updated_at: Int64
|
updated_at: Int64
|
||||||
|
revoked_timestamp: Int64
|
||||||
}
|
}
|
||||||
|
|
||||||
type Users {
|
type Users {
|
||||||
|
@ -86,6 +87,7 @@ type Response {
|
||||||
}
|
}
|
||||||
|
|
||||||
type Env {
|
type Env {
|
||||||
|
ACCESS_TOKEN_EXPIRY_TIME: String
|
||||||
ADMIN_SECRET: String
|
ADMIN_SECRET: String
|
||||||
DATABASE_NAME: String!
|
DATABASE_NAME: String!
|
||||||
DATABASE_URL: String!
|
DATABASE_URL: String!
|
||||||
|
@ -126,7 +128,18 @@ type Env {
|
||||||
ORGANIZATION_LOGO: String
|
ORGANIZATION_LOGO: String
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type ValidateJWTTokenResponse {
|
||||||
|
is_valid: Boolean!
|
||||||
|
}
|
||||||
|
|
||||||
|
type GenerateJWTKeysResponse {
|
||||||
|
secret: String
|
||||||
|
public_key: String
|
||||||
|
private_key: String
|
||||||
|
}
|
||||||
|
|
||||||
input UpdateEnvInput {
|
input UpdateEnvInput {
|
||||||
|
ACCESS_TOKEN_EXPIRY_TIME: String
|
||||||
ADMIN_SECRET: String
|
ADMIN_SECRET: String
|
||||||
CUSTOM_ACCESS_TOKEN_SCRIPT: String
|
CUSTOM_ACCESS_TOKEN_SCRIPT: String
|
||||||
OLD_ADMIN_SECRET: String
|
OLD_ADMIN_SECRET: String
|
||||||
|
@ -281,6 +294,20 @@ input InviteMemberInput {
|
||||||
redirect_uri: String
|
redirect_uri: String
|
||||||
}
|
}
|
||||||
|
|
||||||
|
input UpdateAccessInput {
|
||||||
|
user_id: String!
|
||||||
|
}
|
||||||
|
|
||||||
|
input ValidateJWTTokenInput {
|
||||||
|
token_type: String!
|
||||||
|
token: String!
|
||||||
|
roles: [String!]
|
||||||
|
}
|
||||||
|
|
||||||
|
input GenerateJWTKeysInput {
|
||||||
|
type: String!
|
||||||
|
}
|
||||||
|
|
||||||
type Mutation {
|
type Mutation {
|
||||||
signup(params: SignUpInput!): AuthResponse!
|
signup(params: SignUpInput!): AuthResponse!
|
||||||
login(params: LoginInput!): AuthResponse!
|
login(params: LoginInput!): AuthResponse!
|
||||||
|
@ -300,12 +327,16 @@ type Mutation {
|
||||||
_admin_logout: Response!
|
_admin_logout: Response!
|
||||||
_update_env(params: UpdateEnvInput!): Response!
|
_update_env(params: UpdateEnvInput!): Response!
|
||||||
_invite_members(params: InviteMemberInput!): Response!
|
_invite_members(params: InviteMemberInput!): Response!
|
||||||
|
_revoke_access(param: UpdateAccessInput!): Response!
|
||||||
|
_enable_access(param: UpdateAccessInput!): Response!
|
||||||
|
_generate_jwt_keys(params: GenerateJWTKeysInput!): GenerateJWTKeysResponse!
|
||||||
}
|
}
|
||||||
|
|
||||||
type Query {
|
type Query {
|
||||||
meta: Meta!
|
meta: Meta!
|
||||||
session(params: SessionQueryInput): AuthResponse!
|
session(params: SessionQueryInput): AuthResponse!
|
||||||
profile: User!
|
profile: User!
|
||||||
|
validate_jwt_token(params: ValidateJWTTokenInput!): ValidateJWTTokenResponse!
|
||||||
# admin only apis
|
# admin only apis
|
||||||
_users(params: PaginatedInput): Users!
|
_users(params: PaginatedInput): Users!
|
||||||
_verification_requests(params: PaginatedInput): VerificationRequests!
|
_verification_requests(params: PaginatedInput): VerificationRequests!
|
||||||
|
|
|
@ -79,6 +79,18 @@ func (r *mutationResolver) InviteMembers(ctx context.Context, params model.Invit
|
||||||
return resolvers.InviteMembersResolver(ctx, params)
|
return resolvers.InviteMembersResolver(ctx, params)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r *mutationResolver) RevokeAccess(ctx context.Context, param model.UpdateAccessInput) (*model.Response, error) {
|
||||||
|
return resolvers.RevokeAccessResolver(ctx, param)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *mutationResolver) EnableAccess(ctx context.Context, param model.UpdateAccessInput) (*model.Response, error) {
|
||||||
|
return resolvers.EnableAccessResolver(ctx, param)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *mutationResolver) GenerateJwtKeys(ctx context.Context, params model.GenerateJWTKeysInput) (*model.GenerateJWTKeysResponse, error) {
|
||||||
|
return resolvers.GenerateJWTKeysResolver(ctx, params)
|
||||||
|
}
|
||||||
|
|
||||||
func (r *queryResolver) Meta(ctx context.Context) (*model.Meta, error) {
|
func (r *queryResolver) Meta(ctx context.Context) (*model.Meta, error) {
|
||||||
return resolvers.MetaResolver(ctx)
|
return resolvers.MetaResolver(ctx)
|
||||||
}
|
}
|
||||||
|
@ -91,6 +103,10 @@ func (r *queryResolver) Profile(ctx context.Context) (*model.User, error) {
|
||||||
return resolvers.ProfileResolver(ctx)
|
return resolvers.ProfileResolver(ctx)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r *queryResolver) ValidateJwtToken(ctx context.Context, params model.ValidateJWTTokenInput) (*model.ValidateJWTTokenResponse, error) {
|
||||||
|
return resolvers.ValidateJwtTokenResolver(ctx, params)
|
||||||
|
}
|
||||||
|
|
||||||
func (r *queryResolver) Users(ctx context.Context, params *model.PaginatedInput) (*model.Users, error) {
|
func (r *queryResolver) Users(ctx context.Context, params *model.PaginatedInput) (*model.Users, error) {
|
||||||
return resolvers.UsersResolver(ctx, params)
|
return resolvers.UsersResolver(ctx, params)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
package handlers
|
package handlers
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/authorizerdev/authorizer/server/constants"
|
"github.com/authorizerdev/authorizer/server/constants"
|
||||||
"github.com/authorizerdev/authorizer/server/cookie"
|
"github.com/authorizerdev/authorizer/server/cookie"
|
||||||
|
@ -51,8 +51,6 @@ func AuthorizeHandler() gin.HandlerFunc {
|
||||||
gc.JSON(400, gin.H{"error": "invalid response mode"})
|
gc.JSON(400, gin.H{"error": "invalid response mode"})
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Println("=> redirect URI:", redirectURI)
|
|
||||||
fmt.Println("=> state:", state)
|
|
||||||
if redirectURI == "" {
|
if redirectURI == "" {
|
||||||
redirectURI = "/app"
|
redirectURI = "/app"
|
||||||
}
|
}
|
||||||
|
@ -279,7 +277,11 @@ func AuthorizeHandler() gin.HandlerFunc {
|
||||||
sessionstore.SetState(authToken.FingerPrintHash, authToken.FingerPrint+"@"+user.ID)
|
sessionstore.SetState(authToken.FingerPrintHash, authToken.FingerPrint+"@"+user.ID)
|
||||||
sessionstore.SetState(authToken.AccessToken.Token, authToken.FingerPrint+"@"+user.ID)
|
sessionstore.SetState(authToken.AccessToken.Token, authToken.FingerPrint+"@"+user.ID)
|
||||||
cookie.SetSession(gc, authToken.FingerPrintHash)
|
cookie.SetSession(gc, authToken.FingerPrintHash)
|
||||||
expiresIn := int64(1800)
|
|
||||||
|
expiresIn := authToken.AccessToken.ExpiresAt - time.Now().Unix()
|
||||||
|
if expiresIn <= 0 {
|
||||||
|
expiresIn = 1
|
||||||
|
}
|
||||||
|
|
||||||
// used of query mode
|
// used of query mode
|
||||||
params := "access_token=" + authToken.AccessToken.Token + "&token_type=bearer&expires_in=" + strconv.FormatInt(expiresIn, 10) + "&state=" + state + "&id_token=" + authToken.IDToken.Token
|
params := "access_token=" + authToken.AccessToken.Token + "&token_type=bearer&expires_in=" + strconv.FormatInt(expiresIn, 10) + "&state=" + state + "&id_token=" + authToken.IDToken.Token
|
||||||
|
|
|
@ -95,9 +95,12 @@ func OAuthCallbackHandler() gin.HandlerFunc {
|
||||||
user.EmailVerifiedAt = &now
|
user.EmailVerifiedAt = &now
|
||||||
user, _ = db.Provider.AddUser(user)
|
user, _ = db.Provider.AddUser(user)
|
||||||
} else {
|
} else {
|
||||||
|
if user.RevokedTimestamp != nil {
|
||||||
|
c.JSON(400, gin.H{"error": "user access has been revoked"})
|
||||||
|
}
|
||||||
|
|
||||||
// user exists in db, check if method was google
|
// user exists in db, check if method was google
|
||||||
// if not append google to existing signup method and save it
|
// if not append google to existing signup method and save it
|
||||||
|
|
||||||
signupMethod := existingUser.SignupMethods
|
signupMethod := existingUser.SignupMethods
|
||||||
if !strings.Contains(signupMethod, provider) {
|
if !strings.Contains(signupMethod, provider) {
|
||||||
signupMethod = signupMethod + "," + provider
|
signupMethod = signupMethod + "," + provider
|
||||||
|
@ -154,7 +157,12 @@ func OAuthCallbackHandler() gin.HandlerFunc {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.JSON(500, gin.H{"error": err.Error()})
|
c.JSON(500, gin.H{"error": err.Error()})
|
||||||
}
|
}
|
||||||
expiresIn := int64(1800)
|
|
||||||
|
expiresIn := authToken.AccessToken.ExpiresAt - time.Now().Unix()
|
||||||
|
if expiresIn <= 0 {
|
||||||
|
expiresIn = 1
|
||||||
|
}
|
||||||
|
|
||||||
params := "access_token=" + authToken.AccessToken.Token + "&token_type=bearer&expires_in=" + strconv.FormatInt(expiresIn, 10) + "&state=" + stateValue + "&id_token=" + authToken.IDToken.Token
|
params := "access_token=" + authToken.AccessToken.Token + "&token_type=bearer&expires_in=" + strconv.FormatInt(expiresIn, 10) + "&state=" + stateValue + "&id_token=" + authToken.IDToken.Token
|
||||||
|
|
||||||
cookie.SetSession(c, authToken.FingerPrintHash)
|
cookie.SetSession(c, authToken.FingerPrintHash)
|
||||||
|
|
|
@ -5,6 +5,7 @@ import (
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/authorizerdev/authorizer/server/constants"
|
"github.com/authorizerdev/authorizer/server/constants"
|
||||||
"github.com/authorizerdev/authorizer/server/cookie"
|
"github.com/authorizerdev/authorizer/server/cookie"
|
||||||
|
@ -174,7 +175,11 @@ func TokenHandler() gin.HandlerFunc {
|
||||||
sessionstore.SetState(authToken.AccessToken.Token, authToken.FingerPrint+"@"+user.ID)
|
sessionstore.SetState(authToken.AccessToken.Token, authToken.FingerPrint+"@"+user.ID)
|
||||||
cookie.SetSession(gc, authToken.FingerPrintHash)
|
cookie.SetSession(gc, authToken.FingerPrintHash)
|
||||||
|
|
||||||
expiresIn := int64(1800)
|
expiresIn := authToken.AccessToken.ExpiresAt - time.Now().Unix()
|
||||||
|
if expiresIn <= 0 {
|
||||||
|
expiresIn = 1
|
||||||
|
}
|
||||||
|
|
||||||
res := map[string]interface{}{
|
res := map[string]interface{}{
|
||||||
"access_token": authToken.AccessToken.Token,
|
"access_token": authToken.AccessToken.Token,
|
||||||
"id_token": authToken.IDToken.Token,
|
"id_token": authToken.IDToken.Token,
|
||||||
|
|
|
@ -82,7 +82,12 @@ func VerifyEmailHandler() gin.HandlerFunc {
|
||||||
c.JSON(500, errorRes)
|
c.JSON(500, errorRes)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
expiresIn := int64(1800)
|
|
||||||
|
expiresIn := authToken.AccessToken.ExpiresAt - time.Now().Unix()
|
||||||
|
if expiresIn <= 0 {
|
||||||
|
expiresIn = 1
|
||||||
|
}
|
||||||
|
|
||||||
params := "access_token=" + authToken.AccessToken.Token + "&token_type=bearer&expires_in=" + strconv.FormatInt(expiresIn, 10) + "&state=" + state + "&id_token=" + authToken.IDToken.Token
|
params := "access_token=" + authToken.AccessToken.Token + "&token_type=bearer&expires_in=" + strconv.FormatInt(expiresIn, 10) + "&state=" + state + "&id_token=" + authToken.IDToken.Token
|
||||||
|
|
||||||
cookie.SetSession(c, authToken.FingerPrintHash)
|
cookie.SetSession(c, authToken.FingerPrintHash)
|
||||||
|
|
|
@ -15,7 +15,7 @@ func CORSMiddleware() gin.HandlerFunc {
|
||||||
}
|
}
|
||||||
|
|
||||||
c.Writer.Header().Set("Access-Control-Allow-Credentials", "true")
|
c.Writer.Header().Set("Access-Control-Allow-Credentials", "true")
|
||||||
c.Writer.Header().Set("Access-Control-Allow-Headers", "Content-Type, Content-Length, Accept-Encoding, X-CSRF-Token, Authorization, accept, origin, Cache-Control, X-Requested-With")
|
c.Writer.Header().Set("Access-Control-Allow-Headers", "Content-Type, Content-Length, Accept-Encoding, X-CSRF-Token, Authorization, accept, origin, Cache-Control, X-Requested-With, X-authorizer-url")
|
||||||
c.Writer.Header().Set("Access-Control-Allow-Methods", "POST, OPTIONS, GET, PUT")
|
c.Writer.Header().Set("Access-Control-Allow-Methods", "POST, OPTIONS, GET, PUT")
|
||||||
|
|
||||||
if c.Request.Method == "OPTIONS" {
|
if c.Request.Method == "OPTIONS" {
|
||||||
|
|
44
server/resolvers/enable_access.go
Normal file
44
server/resolvers/enable_access.go
Normal file
|
@ -0,0 +1,44 @@
|
||||||
|
package resolvers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
|
||||||
|
"github.com/authorizerdev/authorizer/server/db"
|
||||||
|
"github.com/authorizerdev/authorizer/server/graph/model"
|
||||||
|
"github.com/authorizerdev/authorizer/server/token"
|
||||||
|
"github.com/authorizerdev/authorizer/server/utils"
|
||||||
|
)
|
||||||
|
|
||||||
|
// EnableAccessResolver is a resolver for enabling user access
|
||||||
|
func EnableAccessResolver(ctx context.Context, params model.UpdateAccessInput) (*model.Response, error) {
|
||||||
|
gc, err := utils.GinContextFromContext(ctx)
|
||||||
|
var res *model.Response
|
||||||
|
if err != nil {
|
||||||
|
return res, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if !token.IsSuperAdmin(gc) {
|
||||||
|
return res, fmt.Errorf("unauthorized")
|
||||||
|
}
|
||||||
|
|
||||||
|
user, err := db.Provider.GetUserByID(params.UserID)
|
||||||
|
if err != nil {
|
||||||
|
return res, err
|
||||||
|
}
|
||||||
|
|
||||||
|
user.RevokedTimestamp = nil
|
||||||
|
|
||||||
|
user, err = db.Provider.UpdateUser(user)
|
||||||
|
if err != nil {
|
||||||
|
log.Println("error updating user:", err)
|
||||||
|
return res, err
|
||||||
|
}
|
||||||
|
|
||||||
|
res = &model.Response{
|
||||||
|
Message: `user access enabled successfully`,
|
||||||
|
}
|
||||||
|
|
||||||
|
return res, nil
|
||||||
|
}
|
|
@ -27,6 +27,7 @@ func EnvResolver(ctx context.Context) (*model.Env, error) {
|
||||||
|
|
||||||
// get clone of store
|
// get clone of store
|
||||||
store := envstore.EnvStoreObj.GetEnvStoreClone()
|
store := envstore.EnvStoreObj.GetEnvStoreClone()
|
||||||
|
accessTokenExpiryTime := store.StringEnv[constants.EnvKeyAccessTokenExpiryTime]
|
||||||
adminSecret := store.StringEnv[constants.EnvKeyAdminSecret]
|
adminSecret := store.StringEnv[constants.EnvKeyAdminSecret]
|
||||||
clientID := store.StringEnv[constants.EnvKeyClientID]
|
clientID := store.StringEnv[constants.EnvKeyClientID]
|
||||||
clientSecret := store.StringEnv[constants.EnvKeyClientSecret]
|
clientSecret := store.StringEnv[constants.EnvKeyClientSecret]
|
||||||
|
@ -66,7 +67,12 @@ func EnvResolver(ctx context.Context) (*model.Env, error) {
|
||||||
organizationName := store.StringEnv[constants.EnvKeyOrganizationName]
|
organizationName := store.StringEnv[constants.EnvKeyOrganizationName]
|
||||||
organizationLogo := store.StringEnv[constants.EnvKeyOrganizationLogo]
|
organizationLogo := store.StringEnv[constants.EnvKeyOrganizationLogo]
|
||||||
|
|
||||||
|
if accessTokenExpiryTime == "" {
|
||||||
|
accessTokenExpiryTime = "30m"
|
||||||
|
}
|
||||||
|
|
||||||
res = &model.Env{
|
res = &model.Env{
|
||||||
|
AccessTokenExpiryTime: &accessTokenExpiryTime,
|
||||||
AdminSecret: &adminSecret,
|
AdminSecret: &adminSecret,
|
||||||
DatabaseName: databaseName,
|
DatabaseName: databaseName,
|
||||||
DatabaseURL: databaseURL,
|
DatabaseURL: databaseURL,
|
||||||
|
|
60
server/resolvers/generate_jwt_keys.go
Normal file
60
server/resolvers/generate_jwt_keys.go
Normal file
|
@ -0,0 +1,60 @@
|
||||||
|
package resolvers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/authorizerdev/authorizer/server/constants"
|
||||||
|
"github.com/authorizerdev/authorizer/server/crypto"
|
||||||
|
"github.com/authorizerdev/authorizer/server/envstore"
|
||||||
|
"github.com/authorizerdev/authorizer/server/graph/model"
|
||||||
|
"github.com/authorizerdev/authorizer/server/token"
|
||||||
|
"github.com/authorizerdev/authorizer/server/utils"
|
||||||
|
)
|
||||||
|
|
||||||
|
// GenerateJWTKeysResolver mutation to generate new jwt keys
|
||||||
|
func GenerateJWTKeysResolver(ctx context.Context, params model.GenerateJWTKeysInput) (*model.GenerateJWTKeysResponse, error) {
|
||||||
|
gc, err := utils.GinContextFromContext(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if !token.IsSuperAdmin(gc) {
|
||||||
|
return nil, fmt.Errorf("unauthorized")
|
||||||
|
}
|
||||||
|
|
||||||
|
clientID := envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyClientID)
|
||||||
|
if crypto.IsHMACA(params.Type) {
|
||||||
|
secret, _, err := crypto.NewHMACKey(params.Type, clientID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &model.GenerateJWTKeysResponse{
|
||||||
|
Secret: &secret,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if crypto.IsRSA(params.Type) {
|
||||||
|
_, privateKey, publicKey, _, err := crypto.NewRSAKey(params.Type, clientID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &model.GenerateJWTKeysResponse{
|
||||||
|
PrivateKey: &privateKey,
|
||||||
|
PublicKey: &publicKey,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if crypto.IsECDSA(params.Type) {
|
||||||
|
_, privateKey, publicKey, _, err := crypto.NewECDSAKey(params.Type, clientID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &model.GenerateJWTKeysResponse{
|
||||||
|
PrivateKey: &privateKey,
|
||||||
|
PublicKey: &publicKey,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, fmt.Errorf("invalid algorithm")
|
||||||
|
}
|
|
@ -5,6 +5,7 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
"strings"
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/authorizerdev/authorizer/server/constants"
|
"github.com/authorizerdev/authorizer/server/constants"
|
||||||
"github.com/authorizerdev/authorizer/server/cookie"
|
"github.com/authorizerdev/authorizer/server/cookie"
|
||||||
|
@ -35,6 +36,10 @@ func LoginResolver(ctx context.Context, params model.LoginInput) (*model.AuthRes
|
||||||
return res, fmt.Errorf(`user with this email not found`)
|
return res, fmt.Errorf(`user with this email not found`)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if user.RevokedTimestamp != nil {
|
||||||
|
return res, fmt.Errorf(`user access has been revoked`)
|
||||||
|
}
|
||||||
|
|
||||||
if !strings.Contains(user.SignupMethods, constants.SignupMethodBasicAuth) {
|
if !strings.Contains(user.SignupMethods, constants.SignupMethodBasicAuth) {
|
||||||
return res, fmt.Errorf(`user has not signed up email & password`)
|
return res, fmt.Errorf(`user has not signed up email & password`)
|
||||||
}
|
}
|
||||||
|
@ -69,7 +74,11 @@ func LoginResolver(ctx context.Context, params model.LoginInput) (*model.AuthRes
|
||||||
return res, err
|
return res, err
|
||||||
}
|
}
|
||||||
|
|
||||||
expiresIn := int64(1800)
|
expiresIn := authToken.AccessToken.ExpiresAt - time.Now().Unix()
|
||||||
|
if expiresIn <= 0 {
|
||||||
|
expiresIn = 1
|
||||||
|
}
|
||||||
|
|
||||||
res = &model.AuthResponse{
|
res = &model.AuthResponse{
|
||||||
Message: `Logged in successfully`,
|
Message: `Logged in successfully`,
|
||||||
AccessToken: &authToken.AccessToken.Token,
|
AccessToken: &authToken.AccessToken.Token,
|
||||||
|
|
|
@ -70,6 +70,10 @@ func MagicLinkLoginResolver(ctx context.Context, params model.MagicLinkLoginInpu
|
||||||
// 2. user has not signed up for one of the available role but trying to signup.
|
// 2. user has not signed up for one of the available role but trying to signup.
|
||||||
// Need to modify roles in this case
|
// Need to modify roles in this case
|
||||||
|
|
||||||
|
if user.RevokedTimestamp != nil {
|
||||||
|
return res, fmt.Errorf(`user access has been revoked`)
|
||||||
|
}
|
||||||
|
|
||||||
// find the unassigned roles
|
// find the unassigned roles
|
||||||
if len(params.Roles) <= 0 {
|
if len(params.Roles) <= 0 {
|
||||||
inputRoles = envstore.EnvStoreObj.GetSliceStoreEnvVariable(constants.EnvKeyDefaultRoles)
|
inputRoles = envstore.EnvStoreObj.GetSliceStoreEnvVariable(constants.EnvKeyDefaultRoles)
|
||||||
|
|
49
server/resolvers/revoke_access.go
Normal file
49
server/resolvers/revoke_access.go
Normal file
|
@ -0,0 +1,49 @@
|
||||||
|
package resolvers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/authorizerdev/authorizer/server/db"
|
||||||
|
"github.com/authorizerdev/authorizer/server/graph/model"
|
||||||
|
"github.com/authorizerdev/authorizer/server/sessionstore"
|
||||||
|
"github.com/authorizerdev/authorizer/server/token"
|
||||||
|
"github.com/authorizerdev/authorizer/server/utils"
|
||||||
|
)
|
||||||
|
|
||||||
|
// RevokeAccessResolver is a resolver for revoking user access
|
||||||
|
func RevokeAccessResolver(ctx context.Context, params model.UpdateAccessInput) (*model.Response, error) {
|
||||||
|
gc, err := utils.GinContextFromContext(ctx)
|
||||||
|
var res *model.Response
|
||||||
|
if err != nil {
|
||||||
|
return res, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if !token.IsSuperAdmin(gc) {
|
||||||
|
return res, fmt.Errorf("unauthorized")
|
||||||
|
}
|
||||||
|
|
||||||
|
user, err := db.Provider.GetUserByID(params.UserID)
|
||||||
|
if err != nil {
|
||||||
|
return res, err
|
||||||
|
}
|
||||||
|
|
||||||
|
now := time.Now().Unix()
|
||||||
|
user.RevokedTimestamp = &now
|
||||||
|
|
||||||
|
user, err = db.Provider.UpdateUser(user)
|
||||||
|
if err != nil {
|
||||||
|
log.Println("error updating user:", err)
|
||||||
|
return res, err
|
||||||
|
}
|
||||||
|
|
||||||
|
go sessionstore.DeleteAllUserSession(fmt.Sprintf("%x", user.ID))
|
||||||
|
|
||||||
|
res = &model.Response{
|
||||||
|
Message: `user access revoked successfully`,
|
||||||
|
}
|
||||||
|
|
||||||
|
return res, nil
|
||||||
|
}
|
|
@ -5,6 +5,7 @@ import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/authorizerdev/authorizer/server/cookie"
|
"github.com/authorizerdev/authorizer/server/cookie"
|
||||||
"github.com/authorizerdev/authorizer/server/db"
|
"github.com/authorizerdev/authorizer/server/db"
|
||||||
|
@ -73,7 +74,11 @@ func SessionResolver(ctx context.Context, params *model.SessionQueryInput) (*mod
|
||||||
sessionstore.SetState(authToken.AccessToken.Token, authToken.FingerPrint+"@"+user.ID)
|
sessionstore.SetState(authToken.AccessToken.Token, authToken.FingerPrint+"@"+user.ID)
|
||||||
cookie.SetSession(gc, authToken.FingerPrintHash)
|
cookie.SetSession(gc, authToken.FingerPrintHash)
|
||||||
|
|
||||||
expiresIn := int64(1800)
|
expiresIn := authToken.AccessToken.ExpiresAt - time.Now().Unix()
|
||||||
|
if expiresIn <= 0 {
|
||||||
|
expiresIn = 1
|
||||||
|
}
|
||||||
|
|
||||||
res = &model.AuthResponse{
|
res = &model.AuthResponse{
|
||||||
Message: `Session token refreshed`,
|
Message: `Session token refreshed`,
|
||||||
AccessToken: &authToken.AccessToken.Token,
|
AccessToken: &authToken.AccessToken.Token,
|
||||||
|
|
|
@ -176,7 +176,10 @@ func SignupResolver(ctx context.Context, params model.SignUpInput) (*model.AuthR
|
||||||
cookie.SetSession(gc, authToken.FingerPrintHash)
|
cookie.SetSession(gc, authToken.FingerPrintHash)
|
||||||
go utils.SaveSessionInDB(gc, user.ID)
|
go utils.SaveSessionInDB(gc, user.ID)
|
||||||
|
|
||||||
expiresIn := int64(1800)
|
expiresIn := authToken.AccessToken.ExpiresAt - time.Now().Unix()
|
||||||
|
if expiresIn <= 0 {
|
||||||
|
expiresIn = 1
|
||||||
|
}
|
||||||
|
|
||||||
res = &model.AuthResponse{
|
res = &model.AuthResponse{
|
||||||
Message: `Signed up successfully.`,
|
Message: `Signed up successfully.`,
|
||||||
|
|
|
@ -53,11 +53,19 @@ func UpdateEnvResolver(ctx context.Context, params model.UpdateEnvInput) (*model
|
||||||
}
|
}
|
||||||
|
|
||||||
if isJWTUpdated {
|
if isJWTUpdated {
|
||||||
|
// use to reset when type is changed from rsa, edsa -> hmac or vice a versa
|
||||||
|
defaultSecret := ""
|
||||||
|
defaultPublicKey := ""
|
||||||
|
defaultPrivateKey := ""
|
||||||
// check if jwt secret is provided
|
// check if jwt secret is provided
|
||||||
if crypto.IsHMACA(algo) {
|
if crypto.IsHMACA(algo) {
|
||||||
if params.JwtSecret == nil {
|
if params.JwtSecret == nil {
|
||||||
return res, fmt.Errorf("jwt secret is required for HMAC algorithm")
|
return res, fmt.Errorf("jwt secret is required for HMAC algorithm")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// reset public key and private key
|
||||||
|
params.JwtPrivateKey = &defaultPrivateKey
|
||||||
|
params.JwtPublicKey = &defaultPublicKey
|
||||||
}
|
}
|
||||||
|
|
||||||
if crypto.IsRSA(algo) {
|
if crypto.IsRSA(algo) {
|
||||||
|
@ -65,6 +73,8 @@ func UpdateEnvResolver(ctx context.Context, params model.UpdateEnvInput) (*model
|
||||||
return res, fmt.Errorf("jwt private and public key is required for RSA (PKCS1) / ECDSA algorithm")
|
return res, fmt.Errorf("jwt private and public key is required for RSA (PKCS1) / ECDSA algorithm")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// reset the jwt secret
|
||||||
|
params.JwtSecret = &defaultSecret
|
||||||
_, err = crypto.ParseRsaPrivateKeyFromPemStr(*params.JwtPrivateKey)
|
_, err = crypto.ParseRsaPrivateKeyFromPemStr(*params.JwtPrivateKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return res, err
|
return res, err
|
||||||
|
@ -81,6 +91,8 @@ func UpdateEnvResolver(ctx context.Context, params model.UpdateEnvInput) (*model
|
||||||
return res, fmt.Errorf("jwt private and public key is required for RSA (PKCS1) / ECDSA algorithm")
|
return res, fmt.Errorf("jwt private and public key is required for RSA (PKCS1) / ECDSA algorithm")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// reset the jwt secret
|
||||||
|
params.JwtSecret = &defaultSecret
|
||||||
_, err = crypto.ParseEcdsaPrivateKeyFromPemStr(*params.JwtPrivateKey)
|
_, err = crypto.ParseEcdsaPrivateKeyFromPemStr(*params.JwtPrivateKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return res, err
|
return res, err
|
||||||
|
|
86
server/resolvers/validate_jwt_token.go
Normal file
86
server/resolvers/validate_jwt_token.go
Normal file
|
@ -0,0 +1,86 @@
|
||||||
|
package resolvers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/authorizerdev/authorizer/server/graph/model"
|
||||||
|
"github.com/authorizerdev/authorizer/server/sessionstore"
|
||||||
|
"github.com/authorizerdev/authorizer/server/token"
|
||||||
|
"github.com/authorizerdev/authorizer/server/utils"
|
||||||
|
"github.com/golang-jwt/jwt"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ValidateJwtTokenResolver is used to validate a jwt token without its rotation
|
||||||
|
// this can be used at API level (backend)
|
||||||
|
// it can validate:
|
||||||
|
// access_token
|
||||||
|
// id_token
|
||||||
|
// refresh_token
|
||||||
|
func ValidateJwtTokenResolver(ctx context.Context, params model.ValidateJWTTokenInput) (*model.ValidateJWTTokenResponse, error) {
|
||||||
|
gc, err := utils.GinContextFromContext(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
tokenType := params.TokenType
|
||||||
|
if tokenType != "access_token" && tokenType != "refresh_token" && tokenType != "id_token" {
|
||||||
|
return nil, errors.New("invalid token type")
|
||||||
|
}
|
||||||
|
|
||||||
|
userID := ""
|
||||||
|
nonce := ""
|
||||||
|
// access_token and refresh_token should be validated from session store as well
|
||||||
|
if tokenType == "access_token" || tokenType == "refresh_token" {
|
||||||
|
savedSession := sessionstore.GetState(params.Token)
|
||||||
|
if savedSession == "" {
|
||||||
|
return &model.ValidateJWTTokenResponse{
|
||||||
|
IsValid: false,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
savedSessionSplit := strings.Split(savedSession, "@")
|
||||||
|
nonce = savedSessionSplit[0]
|
||||||
|
userID = savedSessionSplit[1]
|
||||||
|
}
|
||||||
|
|
||||||
|
hostname := utils.GetHost(gc)
|
||||||
|
var claimRoles []string
|
||||||
|
var claims jwt.MapClaims
|
||||||
|
|
||||||
|
// we cannot validate sub and nonce in case of id_token as that token is not persisted in session store
|
||||||
|
if userID != "" && nonce != "" {
|
||||||
|
claims, err = token.ParseJWTToken(params.Token, hostname, nonce, userID)
|
||||||
|
if err != nil {
|
||||||
|
return &model.ValidateJWTTokenResponse{
|
||||||
|
IsValid: false,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
claims, err = token.ParseJWTTokenWithoutNonce(params.Token, hostname)
|
||||||
|
if err != nil {
|
||||||
|
return &model.ValidateJWTTokenResponse{
|
||||||
|
IsValid: false,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
claimRolesInterface := claims["roles"]
|
||||||
|
roleSlice := utils.ConvertInterfaceToSlice(claimRolesInterface)
|
||||||
|
for _, v := range roleSlice {
|
||||||
|
claimRoles = append(claimRoles, v.(string))
|
||||||
|
}
|
||||||
|
|
||||||
|
if params.Roles != nil && len(params.Roles) > 0 {
|
||||||
|
for _, v := range params.Roles {
|
||||||
|
if !utils.StringSliceContains(claimRoles, v) {
|
||||||
|
return nil, fmt.Errorf(`unauthorized`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return &model.ValidateJWTTokenResponse{
|
||||||
|
IsValid: true,
|
||||||
|
}, nil
|
||||||
|
}
|
|
@ -64,7 +64,11 @@ func VerifyEmailResolver(ctx context.Context, params model.VerifyEmailInput) (*m
|
||||||
cookie.SetSession(gc, authToken.FingerPrintHash)
|
cookie.SetSession(gc, authToken.FingerPrintHash)
|
||||||
go utils.SaveSessionInDB(gc, user.ID)
|
go utils.SaveSessionInDB(gc, user.ID)
|
||||||
|
|
||||||
expiresIn := int64(1800)
|
expiresIn := authToken.AccessToken.ExpiresAt - time.Now().Unix()
|
||||||
|
if expiresIn <= 0 {
|
||||||
|
expiresIn = 1
|
||||||
|
}
|
||||||
|
|
||||||
res = &model.AuthResponse{
|
res = &model.AuthResponse{
|
||||||
Message: `Email verified successfully.`,
|
Message: `Email verified successfully.`,
|
||||||
AccessToken: &authToken.AccessToken.Token,
|
AccessToken: &authToken.AccessToken.Token,
|
||||||
|
|
57
server/test/enable_access_test.go
Normal file
57
server/test/enable_access_test.go
Normal file
|
@ -0,0 +1,57 @@
|
||||||
|
package test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/authorizerdev/authorizer/server/constants"
|
||||||
|
"github.com/authorizerdev/authorizer/server/crypto"
|
||||||
|
"github.com/authorizerdev/authorizer/server/db"
|
||||||
|
"github.com/authorizerdev/authorizer/server/envstore"
|
||||||
|
"github.com/authorizerdev/authorizer/server/graph/model"
|
||||||
|
"github.com/authorizerdev/authorizer/server/resolvers"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func enableAccessTest(t *testing.T, s TestSetup) {
|
||||||
|
t.Helper()
|
||||||
|
t.Run(`should revoke access`, func(t *testing.T) {
|
||||||
|
req, ctx := createContext(s)
|
||||||
|
email := "revoke_access." + s.TestInfo.Email
|
||||||
|
_, err := resolvers.MagicLinkLoginResolver(ctx, model.MagicLinkLoginInput{
|
||||||
|
Email: email,
|
||||||
|
})
|
||||||
|
assert.NoError(t, err)
|
||||||
|
verificationRequest, err := db.Provider.GetVerificationRequestByEmail(email, constants.VerificationTypeMagicLinkLogin)
|
||||||
|
verifyRes, err := resolvers.VerifyEmailResolver(ctx, model.VerifyEmailInput{
|
||||||
|
Token: verificationRequest.Token,
|
||||||
|
})
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.NotNil(t, verifyRes.AccessToken)
|
||||||
|
|
||||||
|
h, err := crypto.EncryptPassword(envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyAdminSecret))
|
||||||
|
assert.Nil(t, err)
|
||||||
|
req.Header.Set("Cookie", fmt.Sprintf("%s=%s", envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyAdminCookieName), h))
|
||||||
|
|
||||||
|
res, err := resolvers.RevokeAccessResolver(ctx, model.UpdateAccessInput{
|
||||||
|
UserID: verifyRes.User.ID,
|
||||||
|
})
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.NotEmpty(t, res.Message)
|
||||||
|
|
||||||
|
res, err = resolvers.EnableAccessResolver(ctx, model.UpdateAccessInput{
|
||||||
|
UserID: verifyRes.User.ID,
|
||||||
|
})
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.NotEmpty(t, res.Message)
|
||||||
|
|
||||||
|
// it should allow login with revoked access
|
||||||
|
res, err = resolvers.MagicLinkLoginResolver(ctx, model.MagicLinkLoginInput{
|
||||||
|
Email: email,
|
||||||
|
})
|
||||||
|
assert.Nil(t, err)
|
||||||
|
assert.NotEmpty(t, res.Message)
|
||||||
|
|
||||||
|
cleanData(email)
|
||||||
|
})
|
||||||
|
}
|
62
server/test/generate_jwt_keys_test.go
Normal file
62
server/test/generate_jwt_keys_test.go
Normal file
|
@ -0,0 +1,62 @@
|
||||||
|
package test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/authorizerdev/authorizer/server/constants"
|
||||||
|
"github.com/authorizerdev/authorizer/server/crypto"
|
||||||
|
"github.com/authorizerdev/authorizer/server/envstore"
|
||||||
|
"github.com/authorizerdev/authorizer/server/graph/model"
|
||||||
|
"github.com/authorizerdev/authorizer/server/resolvers"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func generateJWTkeyTest(t *testing.T, s TestSetup) {
|
||||||
|
t.Helper()
|
||||||
|
req, ctx := createContext(s)
|
||||||
|
t.Run(`generate_jwt_keys`, func(t *testing.T) {
|
||||||
|
t.Run(`should throw unauthorized`, func(t *testing.T) {
|
||||||
|
res, err := resolvers.GenerateJWTKeysResolver(ctx, model.GenerateJWTKeysInput{
|
||||||
|
Type: "HS256",
|
||||||
|
})
|
||||||
|
assert.Error(t, err)
|
||||||
|
assert.Nil(t, res)
|
||||||
|
})
|
||||||
|
t.Run(`should throw invalid`, func(t *testing.T) {
|
||||||
|
res, err := resolvers.GenerateJWTKeysResolver(ctx, model.GenerateJWTKeysInput{
|
||||||
|
Type: "test",
|
||||||
|
})
|
||||||
|
assert.Error(t, err)
|
||||||
|
assert.Nil(t, res)
|
||||||
|
})
|
||||||
|
h, err := crypto.EncryptPassword(envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyAdminSecret))
|
||||||
|
assert.Nil(t, err)
|
||||||
|
req.Header.Set("Cookie", fmt.Sprintf("%s=%s", envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyAdminCookieName), h))
|
||||||
|
t.Run(`should generate HS256 secret`, func(t *testing.T) {
|
||||||
|
res, err := resolvers.GenerateJWTKeysResolver(ctx, model.GenerateJWTKeysInput{
|
||||||
|
Type: "HS256",
|
||||||
|
})
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.NotEmpty(t, res.Secret)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run(`should generate RS256 secret`, func(t *testing.T) {
|
||||||
|
res, err := resolvers.GenerateJWTKeysResolver(ctx, model.GenerateJWTKeysInput{
|
||||||
|
Type: "RS256",
|
||||||
|
})
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.NotEmpty(t, res.PrivateKey)
|
||||||
|
assert.NotEmpty(t, res.PublicKey)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run(`should generate ES256 secret`, func(t *testing.T) {
|
||||||
|
res, err := resolvers.GenerateJWTKeysResolver(ctx, model.GenerateJWTKeysInput{
|
||||||
|
Type: "ES256",
|
||||||
|
})
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.NotEmpty(t, res.PrivateKey)
|
||||||
|
assert.NotEmpty(t, res.PublicKey)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
|
@ -48,6 +48,9 @@ func TestResolvers(t *testing.T) {
|
||||||
adminSessionTests(t, s)
|
adminSessionTests(t, s)
|
||||||
updateEnvTests(t, s)
|
updateEnvTests(t, s)
|
||||||
envTests(t, s)
|
envTests(t, s)
|
||||||
|
revokeAccessTest(t, s)
|
||||||
|
enableAccessTest(t, s)
|
||||||
|
generateJWTkeyTest(t, s)
|
||||||
|
|
||||||
// user tests
|
// user tests
|
||||||
loginTests(t, s)
|
loginTests(t, s)
|
||||||
|
@ -63,6 +66,7 @@ func TestResolvers(t *testing.T) {
|
||||||
logoutTests(t, s)
|
logoutTests(t, s)
|
||||||
metaTests(t, s)
|
metaTests(t, s)
|
||||||
inviteUserTest(t, s)
|
inviteUserTest(t, s)
|
||||||
|
validateJwtTokenTest(t, s)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
54
server/test/revoke_access_test.go
Normal file
54
server/test/revoke_access_test.go
Normal file
|
@ -0,0 +1,54 @@
|
||||||
|
package test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/authorizerdev/authorizer/server/constants"
|
||||||
|
"github.com/authorizerdev/authorizer/server/crypto"
|
||||||
|
"github.com/authorizerdev/authorizer/server/db"
|
||||||
|
"github.com/authorizerdev/authorizer/server/envstore"
|
||||||
|
"github.com/authorizerdev/authorizer/server/graph/model"
|
||||||
|
"github.com/authorizerdev/authorizer/server/resolvers"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func revokeAccessTest(t *testing.T, s TestSetup) {
|
||||||
|
t.Helper()
|
||||||
|
t.Run(`should revoke access`, func(t *testing.T) {
|
||||||
|
req, ctx := createContext(s)
|
||||||
|
email := "revoke_access." + s.TestInfo.Email
|
||||||
|
_, err := resolvers.MagicLinkLoginResolver(ctx, model.MagicLinkLoginInput{
|
||||||
|
Email: email,
|
||||||
|
})
|
||||||
|
assert.NoError(t, err)
|
||||||
|
verificationRequest, err := db.Provider.GetVerificationRequestByEmail(email, constants.VerificationTypeMagicLinkLogin)
|
||||||
|
verifyRes, err := resolvers.VerifyEmailResolver(ctx, model.VerifyEmailInput{
|
||||||
|
Token: verificationRequest.Token,
|
||||||
|
})
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.NotNil(t, verifyRes.AccessToken)
|
||||||
|
|
||||||
|
res, err := resolvers.RevokeAccessResolver(ctx, model.UpdateAccessInput{
|
||||||
|
UserID: verifyRes.User.ID,
|
||||||
|
})
|
||||||
|
assert.Error(t, err)
|
||||||
|
|
||||||
|
h, err := crypto.EncryptPassword(envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyAdminSecret))
|
||||||
|
assert.Nil(t, err)
|
||||||
|
req.Header.Set("Cookie", fmt.Sprintf("%s=%s", envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyAdminCookieName), h))
|
||||||
|
|
||||||
|
res, err = resolvers.RevokeAccessResolver(ctx, model.UpdateAccessInput{
|
||||||
|
UserID: verifyRes.User.ID,
|
||||||
|
})
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.NotEmpty(t, res.Message)
|
||||||
|
|
||||||
|
// it should not allow login with revoked access
|
||||||
|
_, err = resolvers.MagicLinkLoginResolver(ctx, model.MagicLinkLoginInput{
|
||||||
|
Email: email,
|
||||||
|
})
|
||||||
|
assert.Error(t, err)
|
||||||
|
cleanData(email)
|
||||||
|
})
|
||||||
|
}
|
90
server/test/validate_jwt_token_test.go
Normal file
90
server/test/validate_jwt_token_test.go
Normal file
|
@ -0,0 +1,90 @@
|
||||||
|
package test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/authorizerdev/authorizer/server/db/models"
|
||||||
|
"github.com/authorizerdev/authorizer/server/graph/model"
|
||||||
|
"github.com/authorizerdev/authorizer/server/resolvers"
|
||||||
|
"github.com/authorizerdev/authorizer/server/sessionstore"
|
||||||
|
"github.com/authorizerdev/authorizer/server/token"
|
||||||
|
"github.com/authorizerdev/authorizer/server/utils"
|
||||||
|
"github.com/google/uuid"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func validateJwtTokenTest(t *testing.T, s TestSetup) {
|
||||||
|
t.Helper()
|
||||||
|
_, ctx := createContext(s)
|
||||||
|
t.Run(`validate params`, func(t *testing.T) {
|
||||||
|
res, err := resolvers.ValidateJwtTokenResolver(ctx, model.ValidateJWTTokenInput{
|
||||||
|
TokenType: "access_token",
|
||||||
|
Token: "",
|
||||||
|
})
|
||||||
|
assert.False(t, res.IsValid)
|
||||||
|
res, err = resolvers.ValidateJwtTokenResolver(ctx, model.ValidateJWTTokenInput{
|
||||||
|
TokenType: "access_token",
|
||||||
|
Token: "invalid",
|
||||||
|
})
|
||||||
|
assert.False(t, res.IsValid)
|
||||||
|
_, err = resolvers.ValidateJwtTokenResolver(ctx, model.ValidateJWTTokenInput{
|
||||||
|
TokenType: "access_token_invalid",
|
||||||
|
Token: "invalid@invalid",
|
||||||
|
})
|
||||||
|
assert.Error(t, err, "invalid token")
|
||||||
|
})
|
||||||
|
|
||||||
|
scope := []string{"openid", "email", "profile", "offline_access"}
|
||||||
|
user := models.User{
|
||||||
|
ID: uuid.New().String(),
|
||||||
|
Email: "jwt_test_" + s.TestInfo.Email,
|
||||||
|
Roles: "user",
|
||||||
|
UpdatedAt: time.Now().Unix(),
|
||||||
|
CreatedAt: time.Now().Unix(),
|
||||||
|
}
|
||||||
|
|
||||||
|
roles := []string{"user"}
|
||||||
|
gc, err := utils.GinContextFromContext(ctx)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
authToken, err := token.CreateAuthToken(gc, user, roles, scope)
|
||||||
|
sessionstore.SetState(authToken.AccessToken.Token, authToken.FingerPrint+"@"+user.ID)
|
||||||
|
sessionstore.SetState(authToken.RefreshToken.Token, authToken.FingerPrint+"@"+user.ID)
|
||||||
|
|
||||||
|
t.Run(`should validate the access token`, func(t *testing.T) {
|
||||||
|
res, err := resolvers.ValidateJwtTokenResolver(ctx, model.ValidateJWTTokenInput{
|
||||||
|
TokenType: "access_token",
|
||||||
|
Token: authToken.AccessToken.Token,
|
||||||
|
Roles: []string{"user"},
|
||||||
|
})
|
||||||
|
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.True(t, res.IsValid)
|
||||||
|
|
||||||
|
res, err = resolvers.ValidateJwtTokenResolver(ctx, model.ValidateJWTTokenInput{
|
||||||
|
TokenType: "access_token",
|
||||||
|
Token: authToken.AccessToken.Token,
|
||||||
|
Roles: []string{"invalid_role"},
|
||||||
|
})
|
||||||
|
|
||||||
|
assert.Error(t, err)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run(`should validate the refresh token`, func(t *testing.T) {
|
||||||
|
res, err := resolvers.ValidateJwtTokenResolver(ctx, model.ValidateJWTTokenInput{
|
||||||
|
TokenType: "refresh_token",
|
||||||
|
Token: authToken.RefreshToken.Token,
|
||||||
|
})
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.True(t, res.IsValid)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run(`should validate the id token`, func(t *testing.T) {
|
||||||
|
res, err := resolvers.ValidateJwtTokenResolver(ctx, model.ValidateJWTTokenInput{
|
||||||
|
TokenType: "id_token",
|
||||||
|
Token: authToken.IDToken.Token,
|
||||||
|
})
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.True(t, res.IsValid)
|
||||||
|
})
|
||||||
|
}
|
|
@ -130,7 +130,11 @@ 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 string) (string, int64, error) {
|
||||||
expiryBound := time.Minute * 30
|
expiryBound, err := utils.ParseDurationInSeconds(envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyAccessTokenExpiryTime))
|
||||||
|
if err != nil {
|
||||||
|
expiryBound = time.Minute * 30
|
||||||
|
}
|
||||||
|
|
||||||
expiresAt := time.Now().Add(expiryBound).Unix()
|
expiresAt := time.Now().Add(expiryBound).Unix()
|
||||||
|
|
||||||
customClaims := jwt.MapClaims{
|
customClaims := jwt.MapClaims{
|
||||||
|
@ -161,7 +165,12 @@ func GetAccessToken(gc *gin.Context) (string, error) {
|
||||||
return "", fmt.Errorf(`unauthorized`)
|
return "", fmt.Errorf(`unauthorized`)
|
||||||
}
|
}
|
||||||
|
|
||||||
if !strings.HasPrefix(auth, "Bearer ") {
|
authSplit := strings.Split(auth, " ")
|
||||||
|
if len(authSplit) != 2 {
|
||||||
|
return "", fmt.Errorf(`unauthorized`)
|
||||||
|
}
|
||||||
|
|
||||||
|
if strings.ToLower(authSplit[0]) != "bearer" {
|
||||||
return "", fmt.Errorf(`not a bearer token`)
|
return "", fmt.Errorf(`not a bearer token`)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -277,7 +286,11 @@ func ValidateBrowserSession(gc *gin.Context, encryptedSession string) (*SessionD
|
||||||
// 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 string) (string, int64, error) {
|
||||||
expiryBound := time.Minute * 30
|
expiryBound, err := utils.ParseDurationInSeconds(envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyAccessTokenExpiryTime))
|
||||||
|
if err != nil {
|
||||||
|
expiryBound = time.Minute * 30
|
||||||
|
}
|
||||||
|
|
||||||
expiresAt := time.Now().Add(expiryBound).Unix()
|
expiresAt := time.Now().Add(expiryBound).Unix()
|
||||||
|
|
||||||
resUser := user.AsAPIUser()
|
resUser := user.AsAPIUser()
|
||||||
|
@ -350,7 +363,12 @@ func GetIDToken(gc *gin.Context) (string, error) {
|
||||||
return "", fmt.Errorf(`unauthorized`)
|
return "", fmt.Errorf(`unauthorized`)
|
||||||
}
|
}
|
||||||
|
|
||||||
if !strings.HasPrefix(auth, "Bearer ") {
|
authSplit := strings.Split(auth, " ")
|
||||||
|
if len(authSplit) != 2 {
|
||||||
|
return "", fmt.Errorf(`unauthorized`)
|
||||||
|
}
|
||||||
|
|
||||||
|
if strings.ToLower(authSplit[0]) != "bearer" {
|
||||||
return "", fmt.Errorf(`not a bearer token`)
|
return "", fmt.Errorf(`not a bearer token`)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -105,3 +105,59 @@ func ParseJWTToken(token, hostname, nonce, subject string) (jwt.MapClaims, error
|
||||||
|
|
||||||
return claims, nil
|
return claims, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ParseJWTTokenWithoutNonce common util to parse jwt token without nonce
|
||||||
|
// used to validate ID token as it is not persisted in store
|
||||||
|
func ParseJWTTokenWithoutNonce(token, hostname string) (jwt.MapClaims, error) {
|
||||||
|
jwtType := envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyJwtType)
|
||||||
|
signingMethod := jwt.GetSigningMethod(jwtType)
|
||||||
|
|
||||||
|
var err error
|
||||||
|
var claims jwt.MapClaims
|
||||||
|
|
||||||
|
switch signingMethod {
|
||||||
|
case jwt.SigningMethodHS256, jwt.SigningMethodHS384, jwt.SigningMethodHS512:
|
||||||
|
_, err = jwt.ParseWithClaims(token, &claims, func(token *jwt.Token) (interface{}, error) {
|
||||||
|
return []byte(envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyJwtSecret)), nil
|
||||||
|
})
|
||||||
|
case jwt.SigningMethodRS256, jwt.SigningMethodRS384, jwt.SigningMethodRS512:
|
||||||
|
_, err = jwt.ParseWithClaims(token, &claims, func(token *jwt.Token) (interface{}, error) {
|
||||||
|
key, err := crypto.ParseRsaPublicKeyFromPemStr(envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyJwtPublicKey))
|
||||||
|
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) {
|
||||||
|
key, err := crypto.ParseEcdsaPublicKeyFromPemStr(envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyJwtPublicKey))
|
||||||
|
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
|
||||||
|
|
||||||
|
if claims["aud"] != envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyClientID) {
|
||||||
|
return claims, errors.New("invalid audience")
|
||||||
|
}
|
||||||
|
|
||||||
|
if claims["iss"] != hostname {
|
||||||
|
return claims, errors.New("invalid issuer")
|
||||||
|
}
|
||||||
|
|
||||||
|
return claims, nil
|
||||||
|
}
|
||||||
|
|
|
@ -2,6 +2,7 @@ package utils
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"log"
|
"log"
|
||||||
|
"reflect"
|
||||||
|
|
||||||
"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"
|
||||||
|
@ -47,3 +48,24 @@ func RemoveDuplicateString(strSlice []string) []string {
|
||||||
}
|
}
|
||||||
return list
|
return list
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ConvertInterfaceToSlice to convert interface to slice interface
|
||||||
|
func ConvertInterfaceToSlice(slice interface{}) []interface{} {
|
||||||
|
s := reflect.ValueOf(slice)
|
||||||
|
if s.Kind() != reflect.Slice {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Keep the distinction between nil and empty slice input
|
||||||
|
if s.IsNil() {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
ret := make([]interface{}, s.Len())
|
||||||
|
|
||||||
|
for i := 0; i < s.Len(); i++ {
|
||||||
|
ret[i] = s.Index(i).Interface()
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret
|
||||||
|
}
|
||||||
|
|
21
server/utils/parser.go
Normal file
21
server/utils/parser.go
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
package utils
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ParseDurationInSeconds parses input s, removes ms/us/ns and returns result duration
|
||||||
|
func ParseDurationInSeconds(s string) (time.Duration, error) {
|
||||||
|
d, err := time.ParseDuration(s)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
d = d.Truncate(time.Second)
|
||||||
|
if d <= 0 {
|
||||||
|
return 0, errors.New(`duration must be greater than 0s`)
|
||||||
|
}
|
||||||
|
|
||||||
|
return d, nil
|
||||||
|
}
|
|
@ -10,7 +10,20 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
// GetHost returns hostname from request context
|
// GetHost returns hostname from request context
|
||||||
|
// if X-Authorizer-URL header is set it is given highest priority
|
||||||
|
// if EnvKeyAuthorizerURL is set it is given second highest priority.
|
||||||
|
// if above 2 are not set the requesting host name is used
|
||||||
func GetHost(c *gin.Context) string {
|
func GetHost(c *gin.Context) string {
|
||||||
|
authorizerURL := c.Request.Header.Get("X-Authorizer-URL")
|
||||||
|
if authorizerURL != "" {
|
||||||
|
return authorizerURL
|
||||||
|
}
|
||||||
|
|
||||||
|
authorizerURL = envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyAuthorizerURL)
|
||||||
|
if authorizerURL != "" {
|
||||||
|
return authorizerURL
|
||||||
|
}
|
||||||
|
|
||||||
scheme := c.Request.Header.Get("X-Forwarded-Proto")
|
scheme := c.Request.Header.Get("X-Forwarded-Proto")
|
||||||
if scheme != "https" {
|
if scheme != "https" {
|
||||||
scheme = "http"
|
scheme = "http"
|
||||||
|
|
Loading…
Reference in New Issue
Block a user