Compare commits

...

21 Commits

Author SHA1 Message Date
Lakhan Samani
41b5f00b83 fix: client id make readonly 2022-03-25 00:32:21 +05:30
Lakhan Samani
3c31b7fdc7 Merge pull request #153 from anik-ghosh-au7/develop
fix: update jwt keys persisting old values
2022-03-24 23:34:20 +05:30
Anik Ghosh
7e91c6ca28 fix: update jwt keys persisting old values 2022-03-24 22:53:54 +05:30
Lakhan Samani
b1b43a41ca fix: resetting the keys 2022-03-24 22:19:30 +05:30
Lakhan Samani
f969495178 fix: generate new keys modal 2022-03-24 22:09:32 +05:30
Lakhan Samani
3c4c128931 Merge branch 'main' of https://github.com/authorizerdev/authorizer 2022-03-24 21:51:00 +05:30
Lakhan Samani
003cec4f48 feat: add tests for revoke and enable access 2022-03-24 21:50:39 +05:30
Lakhan Samani
3e488155dc Merge pull request #152 from anik-ghosh-au7/feat/generate-new-keys
Feat/generate new keys
2022-03-24 21:34:28 +05:30
Anik Ghosh
4f4a3a91e1 generate-keys-modal updated 2022-03-24 21:29:43 +05:30
Anik Ghosh
a3d9783aef mutation to update jwt vars added 2022-03-24 21:08:10 +05:30
Anik Ghosh
7d77396657 Merge branch 'main' of https://github.com/authorizerdev/authorizer into feat/generate-new-keys 2022-03-24 19:30:32 +05:30
Lakhan Samani
7a18fc6312 fix: add test to resolvers test 2022-03-24 19:23:43 +05:30
Lakhan Samani
90e2709eeb feat: add mutation to generate new jwt secret & keys
Resolves: #150
2022-03-24 19:21:52 +05:30
Anik Ghosh
4c4743ac24 generate-keys-modal added in dashboard 2022-03-24 18:23:22 +05:30
Anik Ghosh
b2541c8e9a feat: update user access (#151)
* feat: update user access

* revoked timestamp field updated

* updates

* updates

* updates
2022-03-24 14:13:55 +05:30
Lakhan Samani
1f3dec6ea6 feat: add validate_jwt_token query
Resolves #149
2022-03-24 13:32:30 +05:30
Lakhan Samani
f356b4728d Merge pull request #142 from authorizerdev/feat/add-password-validation
feat: add validation for strong password
2022-03-19 11:31:31 +05:30
Lakhan Samani
ec4ef97766 feat: add validation for strong password 2022-03-17 15:35:07 +05:30
Lakhan Samani
47d67bf3cd fix: update readme.md 2022-03-17 09:33:55 +05:30
Lakhan Samani
0c54da1168 fix: getting started 2022-03-17 09:31:40 +05:30
Lakhan Samani
d6f60ce464 chore: add workflow-dispatch 2022-03-17 00:44:55 +05:30
38 changed files with 2023 additions and 113 deletions

View File

@@ -1,4 +1,19 @@
on:
workflow_dispatch:
inputs:
logLevel:
description: 'Log level'
required: true
default: 'warning'
type: choice
options:
- info
- warning
- debug
tags:
description: 'Tags'
required: false
type: boolean
release:
types: [created]

View File

@@ -59,35 +59,42 @@
# Getting Started
## Trying out Authorizer
## Step 1: Get Authorizer Instance
### Deploy Production Ready Instance
Deploy production ready Authorizer instance using one click deployment options available below
| **Infra provider** | **One-click link** | **Additional information** |
| :----------------: | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :----------------------------------------------------: |
| Railway.app | <a href="https://railway.app/new/template?template=https://github.com/authorizerdev/authorizer-railway&amp;plugins=postgresql,redis"><img src="https://railway.app/button.svg" style="height: 44px" alt="Deploy on Railway"></a> | [docs](https://docs.authorizer.dev/deployment/railway) |
| Heroku | <a href="https://heroku.com/deploy?template=https://github.com/authorizerdev/authorizer-heroku"><img src="https://www.herokucdn.com/deploy/button.svg" alt="Deploy to Heroku" style="height: 44px;"></a> | [docs](https://docs.authorizer.dev/deployment/heroku) |
| Render | [![Deploy to Render](https://render.com/images/deploy-to-render-button.svg)](https://render.com/deploy?repo=https://github.com/authorizerdev/authorizer-render) | [docs](https://docs.authorizer.dev/deployment/render) |
### Deploy Authorizer Using Source Code
This guide helps you practice using Authorizer to evaluate it before you use it in a production environment. It includes instructions for installing the Authorizer server in local or standalone mode.
- [Install using source code](#install-using-source-code)
- [Install using binaries](#install-using-binaries)
- [Install instance on heroku](#install-instance-on-Heroku)
- [Install instance on railway.app](#install-instance-on-railway)
#### Install using source code
## Install using source code
### Prerequisites
#### Prerequisites
- OS: Linux or macOS or windows
- Go: (Golang)(https://golang.org/dl/) >= v1.15
### Project Setup
#### Project Setup
1. Fork the [authorizer](https://github.com/authorizerdev/authorizer) repository (**Skip this step if you have access to repo**)
2. Clone repo: `git clone https://github.com/authorizerdev/authorizer.git` or use the forked url from step 1
3. Change directory to authorizer: `cd authorizer`
5. Create Env file `cp .env.sample .env`. Check all the supported env [here](https://docs.authorizer.dev/core/env/)
6. Build Dashboard `make build-dashboard`
7. Build App `make build-app`
8. Build Server `make clean && make`
4. Create Env file `cp .env.sample .env`. Check all the supported env [here](https://docs.authorizer.dev/core/env/)
5. Build Dashboard `make build-dashboard`
6. Build App `make build-app`
7. Build Server `make clean && make`
> Note: if you don't have [`make`](https://www.ibm.com/docs/en/aix/7.2?topic=concepts-make-command), you can `cd` into `server` dir and build using the `go build` command
9. Run binary `./build/server`
8. Run binary `./build/server`
## Install using binaries
### Deploy Authorizer using binaries
Deploy / Try Authorizer using binaries. With each [Authorizer Release](https://github.com/authorizerdev/authorizer/releases)
binaries are baked with required deployment files and bundled. You can download a specific version of it for the following operating systems:
@@ -95,7 +102,7 @@ binaries are baked with required deployment files and bundled. You can download
- Mac OSX
- Linux
### Step 1: Download and unzip bundle
#### Download and unzip bundle
- Download the Bundle for the specific OS from the [release page](https://github.com/authorizerdev/authorizer/releases)
@@ -115,11 +122,7 @@ binaries are baked with required deployment files and bundled. You can download
cd authorizer
```
### Step 2: Configure environment variables
Required environment variables are pre-configured in `.env` file. But based on the production requirements, please configure more environment variables. You can refer to [environment variables docs](/core/env) for more information.
### Step 3: Start Authorizer
#### Step 3: Start Authorizer
- Run following command to start authorizer
@@ -131,20 +134,20 @@ Required environment variables are pre-configured in `.env` file. But based on t
> Note: For mac users, you might have to give binary the permission to execute. Here is the command you can use to grant permission `xattr -d com.apple.quarantine build/server`
Deploy production ready Authorizer instance using one click deployment options available below
## Step 2: Setup Instance
| **Infra provider** | **One-click link** | **Additional information** |
| :----------------: | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :----------------------------------------------------: |
| Railway.app | <a href="https://railway.app/new/template?template=https://github.com/authorizerdev/authorizer-railway&amp;plugins=postgresql,redis"><img src="https://railway.app/button.svg" style="height: 44px" alt="Deploy on Railway"></a> | [docs](https://docs.authorizer.dev/deployment/railway) |
| Heroku | <a href="https://heroku.com/deploy?template=https://github.com/authorizerdev/authorizer-heroku"><img src="https://www.herokucdn.com/deploy/button.svg" alt="Deploy to Heroku" style="height: 44px;"></a> | [docs](https://docs.authorizer.dev/deployment/heroku) |
| Render | [![Deploy to Render](https://render.com/images/deploy-to-render-button.svg)](https://render.com/deploy?repo=https://github.com/authorizerdev/authorizer-render) | [docs](https://docs.authorizer.dev/deployment/render) |
- Open authorizer instance endpoint in browser
- Sign up as an admin with a secure password
- Configure environment variables from authorizer dashboard. Check env [docs](/core/env) for more information
> Note: `DATABASE_URL`, `DATABASE_TYPE` and `DATABASE_NAME` are only configurable via platform envs
### Things to consider
- For social logins, you will need respective social platform key and secret
- For having verified users, you will need an SMTP server with an email address and password using which system can send emails. The system will send a verification link to an email address. Once an email is verified then, only able to access it.
> Note: One can always disable the email verification to allow open sign up, which is not recommended for production as anyone can use anyone's email address 😅
- For persisting user sessions, you will need Redis URL (not in case of railway.app). If you do not configure a Redis server, sessions will be persisted until the instance is up or not restarted. For better response time on authorization requests/middleware, we recommend deploying Redis on the same infra/network as your authorizer server.
- For persisting user sessions, you will need Redis URL (not in case of railway app). If you do not configure a Redis server, sessions will be persisted until the instance is up or not restarted. For better response time on authorization requests/middleware, we recommend deploying Redis on the same infra/network as your authorizer server.
## Testing
@@ -163,8 +166,9 @@ This example demonstrates how you can use [`@authorizerdev/authorizer-js`](/auth
<script type="text/javascript">
const authorizerRef = new authorizerdev.Authorizer({
authorizerURL: `AUTHORIZER_URL`,
authorizerURL: `YOUR_AUTHORIZER_INSTANCE_URL`,
redirectURL: window.location.origin,
clientID: 'YOUR_CLIENT_ID', // obtain your client id from authorizer dashboard
});
// use the button selector as per your application
@@ -175,15 +179,19 @@ This example demonstrates how you can use [`@authorizerdev/authorizer-js`](/auth
});
async function onLoad() {
const res = await authorizerRef.browserLogin();
if (res && res.user) {
const res = await authorizerRef.authorize({
response_type: 'code',
use_refresh_token: false,
});
if (res && res.access_token) {
// you can use user information here, eg:
/**
const user = await authorizerRef.getProfile({
Authorization: `Bearer ${res.access_token}`,
});
const userSection = document.getElementById('user');
const logoutSection = document.getElementById('logout-section');
logoutSection.classList.toggle('hide');
userSection.innerHTML = `Welcome, ${res.user.email}`;
*/
userSection.innerHTML = `Welcome, ${user.email}`;
}
}
onLoad();

View 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;

View File

@@ -26,7 +26,6 @@ import {
import { useClient } from 'urql';
import { FaUserPlus, FaMinusCircle, FaPlus, FaUpload } from 'react-icons/fa';
import { useDropzone } from 'react-dropzone';
import { escape } from 'lodash';
import { validateEmail, validateURI } from '../utils';
import { InviteMembers } from '../graphql/mutation';
import { ArrayInputOperations } from '../constants';

View File

@@ -89,3 +89,40 @@ export const ECDSAEncryptionType = {
ES384: 'ES384',
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;
}

View File

@@ -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
}
}
`;

View File

@@ -81,6 +81,7 @@ export const UserDetailsQuery = `
signup_methods
roles
created_at
revoked_timestamp
}
}
}

View File

@@ -34,46 +34,11 @@ import {
HMACEncryptionType,
RSAEncryptionType,
ECDSAEncryptionType,
envVarTypes,
} from '../constants';
import { UpdateEnvVariables } from '../graphql/mutation';
import { getObjectDiff, capitalizeFirstLetter } from '../utils';
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;
}
import GenerateKeysModal from '../components/GenerateKeysModal';
export default function Environment() {
const client = useClient();
@@ -134,14 +99,10 @@ export default function Environment() {
OLD_ADMIN_SECRET: false,
});
useEffect(() => {
let isMounted = true;
async function getData() {
const {
data: { _env: envData },
} = await client.query(EnvVariablesQuery).toPromise();
if (isMounted) {
setLoading(false);
setEnvVariables({
...envData,
@@ -153,13 +114,9 @@ export default function Environment() {
disableInputField: true,
});
}
}
useEffect(() => {
getData();
return () => {
isMounted = false;
};
}, []);
const validateAdminSecretHandler = (event: any) => {
@@ -230,6 +187,8 @@ export default function Environment() {
disableInputField: true,
});
getData();
toast({
title: `Successfully updated ${
Object.keys(updatedEnvVariables).length
@@ -256,7 +215,7 @@ export default function Environment() {
setVariables={() => {}}
inputType={TextInputType.CLIENT_ID}
placeholder="Client ID"
isDisabled={true}
readOnly={true}
/>
</Center>
</Flex>
@@ -272,7 +231,7 @@ export default function Environment() {
setFieldVisibility={setFieldVisibility}
inputType={HiddenInputType.CLIENT_SECRET}
placeholder="Client Secret"
isDisabled={true}
readOnly={true}
/>
</Center>
</Flex>
@@ -410,9 +369,22 @@ export default function Environment() {
</Flex>
</Stack>
<Divider marginTop="2%" marginBottom="2%" />
<Text fontSize="md" paddingTop="2%" fontWeight="bold">
<Flex
width="100%"
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%">
<Flex>
<Flex w="30%" justifyContent="start" alignItems="center">

View File

@@ -39,7 +39,7 @@ import {
FaAngleDown,
} from 'react-icons/fa';
import { EmailVerificationQuery, UserDetailsQuery } from '../graphql/queries';
import { UpdateUser } from '../graphql/mutation';
import { EnableAccess, RevokeAccess, UpdateUser } from '../graphql/mutation';
import EditUserModal from '../components/EditUserModal';
import DeleteUserModal from '../components/DeleteUserModal';
import InviteMembersModal from '../components/InviteMembersModal';
@@ -67,6 +67,12 @@ interface userDataTypes {
signup_methods: string;
roles: [string];
created_at: number;
revoked_timestamp: number;
}
const enum updateAccessActions {
REVOKE = 'REVOKE',
ENABLE = 'ENABLE',
}
const getMaxPages = (pagination: paginationPropTypes) => {
@@ -185,6 +191,66 @@ export default function Users() {
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 (
<Box m="5" py="5" px="10" bg="white" rounded="md">
<Flex margin="2% 0" justifyContent="space-between" alignItems="center">
@@ -206,6 +272,7 @@ export default function Users() {
<Th>Signup Methods</Th>
<Th>Roles</Th>
<Th>Verified</Th>
<Th>Access</Th>
<Th>Actions</Th>
</Tr>
</Thead>
@@ -214,7 +281,7 @@ export default function Users() {
const { email_verified, created_at, ...rest }: any = user;
return (
<Tr key={user.id} style={{ fontSize: 14 }}>
<Td>{user.email}</Td>
<Td maxW="300">{user.email}</Td>
<Td>
{dayjs(user.created_at * 1000).format('MMM DD, YYYY')}
</Td>
@@ -229,6 +296,15 @@ export default function Users() {
{user.email_verified.toString()}
</Tag>
</Td>
<Td>
<Tag
size="sm"
variant="outline"
colorScheme={user.revoked_timestamp ? 'red' : 'green'}
>
{user.revoked_timestamp ? 'Revoked' : 'Enabled'}
</Tag>
</Td>
<Td>
<Menu>
<MenuButton as={Button} variant="unstyled" size="sm">
@@ -258,6 +334,29 @@ export default function Users() {
user={rest}
updateUserList={updateUserList}
/>
{user.revoked_timestamp ? (
<MenuItem
onClick={() =>
updateAccessHandler(
user.id,
updateAccessActions.ENABLE
)
}
>
Enable Access
</MenuItem>
) : (
<MenuItem
onClick={() =>
updateAccessHandler(
user.id,
updateAccessActions.REVOKE
)
}
>
Revoke Access
</MenuItem>
)}
</MenuList>
</Menu>
</Td>

View File

@@ -27,6 +27,7 @@ type User struct {
Roles string `json:"roles" bson:"roles"`
UpdatedAt int64 `json:"updated_at" bson:"updated_at"`
CreatedAt int64 `json:"created_at" bson:"created_at"`
RevokedTimestamp *int64 `json:"revoked_timestamp" bson:"revoked_timestamp"`
}
func (user *User) AsAPIUser() *model.User {
@@ -35,6 +36,7 @@ func (user *User) AsAPIUser() *model.User {
email := user.Email
createdAt := user.CreatedAt
updatedAt := user.UpdatedAt
revokedTimestamp := user.RevokedTimestamp
return &model.User{
ID: user.ID,
Email: user.Email,
@@ -53,5 +55,6 @@ func (user *User) AsAPIUser() *model.User {
Roles: strings.Split(user.Roles, ","),
CreatedAt: &createdAt,
UpdatedAt: &updatedAt,
RevokedTimestamp: revokedTimestamp,
}
}

View File

@@ -98,6 +98,12 @@ type ComplexityRoot struct {
Reason func(childComplexity int) int
}
GenerateJWTKeysResponse struct {
PrivateKey func(childComplexity int) int
PublicKey func(childComplexity int) int
Secret func(childComplexity int) int
}
Meta struct {
ClientID func(childComplexity int) int
IsBasicAuthenticationEnabled func(childComplexity int) int
@@ -115,7 +121,9 @@ type ComplexityRoot struct {
AdminLogout func(childComplexity int) int
AdminSignup func(childComplexity int, params model.AdminSignupInput) int
DeleteUser func(childComplexity int, params model.DeleteUserInput) int
EnableAccess func(childComplexity int, param model.UpdateAccessInput) int
ForgotPassword func(childComplexity int, params model.ForgotPasswordInput) int
GenerateJwtKeys func(childComplexity int, params model.GenerateJWTKeysInput) int
InviteMembers func(childComplexity int, params model.InviteMemberInput) int
Login func(childComplexity int, params model.LoginInput) int
Logout func(childComplexity int) int
@@ -123,6 +131,7 @@ type ComplexityRoot struct {
ResendVerifyEmail func(childComplexity int, params model.ResendVerifyEmailInput) int
ResetPassword func(childComplexity int, params model.ResetPasswordInput) int
Revoke func(childComplexity int, params model.OAuthRevokeInput) int
RevokeAccess func(childComplexity int, param model.UpdateAccessInput) int
Signup func(childComplexity int, params model.SignUpInput) int
UpdateEnv func(childComplexity int, params model.UpdateEnvInput) int
UpdateProfile func(childComplexity int, params model.UpdateProfileInput) int
@@ -144,6 +153,7 @@ type ComplexityRoot struct {
Profile func(childComplexity int) int
Session func(childComplexity int, params *model.SessionQueryInput) int
Users func(childComplexity int, params *model.PaginatedInput) int
ValidateJwtToken func(childComplexity int, params model.ValidateJWTTokenInput) int
VerificationRequests func(childComplexity int, params *model.PaginatedInput) int
}
@@ -166,6 +176,7 @@ type ComplexityRoot struct {
PhoneNumberVerified func(childComplexity int) int
Picture func(childComplexity int) int
PreferredUsername func(childComplexity int) int
RevokedTimestamp func(childComplexity int) int
Roles func(childComplexity int) int
SignupMethods func(childComplexity int) int
UpdatedAt func(childComplexity int) int
@@ -176,6 +187,10 @@ type ComplexityRoot struct {
Users func(childComplexity int) int
}
ValidateJWTTokenResponse struct {
IsValid func(childComplexity int) int
}
VerificationRequest struct {
CreatedAt func(childComplexity int) int
Email func(childComplexity int) int
@@ -212,11 +227,15 @@ type MutationResolver interface {
AdminLogout(ctx context.Context) (*model.Response, error)
UpdateEnv(ctx context.Context, params model.UpdateEnvInput) (*model.Response, error)
InviteMembers(ctx context.Context, params model.InviteMemberInput) (*model.Response, error)
RevokeAccess(ctx context.Context, param model.UpdateAccessInput) (*model.Response, error)
EnableAccess(ctx context.Context, param model.UpdateAccessInput) (*model.Response, error)
GenerateJwtKeys(ctx context.Context, params model.GenerateJWTKeysInput) (*model.GenerateJWTKeysResponse, error)
}
type QueryResolver interface {
Meta(ctx context.Context) (*model.Meta, error)
Session(ctx context.Context, params *model.SessionQueryInput) (*model.AuthResponse, error)
Profile(ctx context.Context) (*model.User, error)
ValidateJwtToken(ctx context.Context, params model.ValidateJWTTokenInput) (*model.ValidateJWTTokenResponse, error)
Users(ctx context.Context, params *model.PaginatedInput) (*model.Users, error)
VerificationRequests(ctx context.Context, params *model.PaginatedInput) (*model.VerificationRequests, error)
AdminSession(ctx context.Context) (*model.Response, error)
@@ -560,6 +579,27 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in
return e.complexity.Error.Reason(childComplexity), true
case "GenerateJWTKeysResponse.private_key":
if e.complexity.GenerateJWTKeysResponse.PrivateKey == nil {
break
}
return e.complexity.GenerateJWTKeysResponse.PrivateKey(childComplexity), true
case "GenerateJWTKeysResponse.public_key":
if e.complexity.GenerateJWTKeysResponse.PublicKey == nil {
break
}
return e.complexity.GenerateJWTKeysResponse.PublicKey(childComplexity), true
case "GenerateJWTKeysResponse.secret":
if e.complexity.GenerateJWTKeysResponse.Secret == nil {
break
}
return e.complexity.GenerateJWTKeysResponse.Secret(childComplexity), true
case "Meta.client_id":
if e.complexity.Meta.ClientID == nil {
break
@@ -666,6 +706,18 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in
return e.complexity.Mutation.DeleteUser(childComplexity, args["params"].(model.DeleteUserInput)), true
case "Mutation._enable_access":
if e.complexity.Mutation.EnableAccess == nil {
break
}
args, err := ec.field_Mutation__enable_access_args(context.TODO(), rawArgs)
if err != nil {
return 0, false
}
return e.complexity.Mutation.EnableAccess(childComplexity, args["param"].(model.UpdateAccessInput)), true
case "Mutation.forgot_password":
if e.complexity.Mutation.ForgotPassword == nil {
break
@@ -678,6 +730,18 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in
return e.complexity.Mutation.ForgotPassword(childComplexity, args["params"].(model.ForgotPasswordInput)), true
case "Mutation._generate_jwt_keys":
if e.complexity.Mutation.GenerateJwtKeys == nil {
break
}
args, err := ec.field_Mutation__generate_jwt_keys_args(context.TODO(), rawArgs)
if err != nil {
return 0, false
}
return e.complexity.Mutation.GenerateJwtKeys(childComplexity, args["params"].(model.GenerateJWTKeysInput)), true
case "Mutation._invite_members":
if e.complexity.Mutation.InviteMembers == nil {
break
@@ -757,6 +821,18 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in
return e.complexity.Mutation.Revoke(childComplexity, args["params"].(model.OAuthRevokeInput)), true
case "Mutation._revoke_access":
if e.complexity.Mutation.RevokeAccess == nil {
break
}
args, err := ec.field_Mutation__revoke_access_args(context.TODO(), rawArgs)
if err != nil {
return 0, false
}
return e.complexity.Mutation.RevokeAccess(childComplexity, args["param"].(model.UpdateAccessInput)), true
case "Mutation.signup":
if e.complexity.Mutation.Signup == nil {
break
@@ -897,6 +973,18 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in
return e.complexity.Query.Users(childComplexity, args["params"].(*model.PaginatedInput)), true
case "Query.validate_jwt_token":
if e.complexity.Query.ValidateJwtToken == nil {
break
}
args, err := ec.field_Query_validate_jwt_token_args(context.TODO(), rawArgs)
if err != nil {
return 0, false
}
return e.complexity.Query.ValidateJwtToken(childComplexity, args["params"].(model.ValidateJWTTokenInput)), true
case "Query._verification_requests":
if e.complexity.Query.VerificationRequests == nil {
break
@@ -1014,6 +1102,13 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in
return e.complexity.User.PreferredUsername(childComplexity), true
case "User.revoked_timestamp":
if e.complexity.User.RevokedTimestamp == nil {
break
}
return e.complexity.User.RevokedTimestamp(childComplexity), true
case "User.roles":
if e.complexity.User.Roles == nil {
break
@@ -1049,6 +1144,13 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in
return e.complexity.Users.Users(childComplexity), true
case "ValidateJWTTokenResponse.is_valid":
if e.complexity.ValidateJWTTokenResponse.IsValid == nil {
break
}
return e.complexity.ValidateJWTTokenResponse.IsValid(childComplexity), true
case "VerificationRequest.created_at":
if e.complexity.VerificationRequest.CreatedAt == nil {
break
@@ -1235,6 +1337,7 @@ type User {
roles: [String!]!
created_at: Int64
updated_at: Int64
revoked_timestamp: Int64
}
type Users {
@@ -1318,6 +1421,16 @@ type Env {
ORGANIZATION_LOGO: String
}
type ValidateJWTTokenResponse {
is_valid: Boolean!
}
type GenerateJWTKeysResponse {
secret: String
public_key: String
private_key: String
}
input UpdateEnvInput {
ADMIN_SECRET: String
CUSTOM_ACCESS_TOKEN_SCRIPT: String
@@ -1473,6 +1586,20 @@ input InviteMemberInput {
redirect_uri: String
}
input UpdateAccessInput {
user_id: String!
}
input ValidateJWTTokenInput {
token_type: String!
token: String!
roles: [String!]
}
input GenerateJWTKeysInput {
type: String!
}
type Mutation {
signup(params: SignUpInput!): AuthResponse!
login(params: LoginInput!): AuthResponse!
@@ -1492,12 +1619,16 @@ type Mutation {
_admin_logout: Response!
_update_env(params: UpdateEnvInput!): Response!
_invite_members(params: InviteMemberInput!): Response!
_revoke_access(param: UpdateAccessInput!): Response!
_enable_access(param: UpdateAccessInput!): Response!
_generate_jwt_keys(params: GenerateJWTKeysInput!): GenerateJWTKeysResponse!
}
type Query {
meta: Meta!
session(params: SessionQueryInput): AuthResponse!
profile: User!
validate_jwt_token(params: ValidateJWTTokenInput!): ValidateJWTTokenResponse!
# admin only apis
_users(params: PaginatedInput): Users!
_verification_requests(params: PaginatedInput): VerificationRequests!
@@ -1557,6 +1688,36 @@ func (ec *executionContext) field_Mutation__delete_user_args(ctx context.Context
return args, nil
}
func (ec *executionContext) field_Mutation__enable_access_args(ctx context.Context, rawArgs map[string]interface{}) (map[string]interface{}, error) {
var err error
args := map[string]interface{}{}
var arg0 model.UpdateAccessInput
if tmp, ok := rawArgs["param"]; ok {
ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("param"))
arg0, err = ec.unmarshalNUpdateAccessInput2githubᚗcomᚋauthorizerdevᚋauthorizerᚋserverᚋgraphᚋmodelᚐUpdateAccessInput(ctx, tmp)
if err != nil {
return nil, err
}
}
args["param"] = arg0
return args, nil
}
func (ec *executionContext) field_Mutation__generate_jwt_keys_args(ctx context.Context, rawArgs map[string]interface{}) (map[string]interface{}, error) {
var err error
args := map[string]interface{}{}
var arg0 model.GenerateJWTKeysInput
if tmp, ok := rawArgs["params"]; ok {
ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("params"))
arg0, err = ec.unmarshalNGenerateJWTKeysInput2githubᚗcomᚋauthorizerdevᚋauthorizerᚋserverᚋgraphᚋmodelᚐGenerateJWTKeysInput(ctx, tmp)
if err != nil {
return nil, err
}
}
args["params"] = arg0
return args, nil
}
func (ec *executionContext) field_Mutation__invite_members_args(ctx context.Context, rawArgs map[string]interface{}) (map[string]interface{}, error) {
var err error
args := map[string]interface{}{}
@@ -1572,6 +1733,21 @@ func (ec *executionContext) field_Mutation__invite_members_args(ctx context.Cont
return args, nil
}
func (ec *executionContext) field_Mutation__revoke_access_args(ctx context.Context, rawArgs map[string]interface{}) (map[string]interface{}, error) {
var err error
args := map[string]interface{}{}
var arg0 model.UpdateAccessInput
if tmp, ok := rawArgs["param"]; ok {
ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("param"))
arg0, err = ec.unmarshalNUpdateAccessInput2githubᚗcomᚋauthorizerdevᚋauthorizerᚋserverᚋgraphᚋmodelᚐUpdateAccessInput(ctx, tmp)
if err != nil {
return nil, err
}
}
args["param"] = arg0
return args, nil
}
func (ec *executionContext) field_Mutation__update_env_args(ctx context.Context, rawArgs map[string]interface{}) (map[string]interface{}, error) {
var err error
args := map[string]interface{}{}
@@ -1797,6 +1973,21 @@ func (ec *executionContext) field_Query_session_args(ctx context.Context, rawArg
return args, nil
}
func (ec *executionContext) field_Query_validate_jwt_token_args(ctx context.Context, rawArgs map[string]interface{}) (map[string]interface{}, error) {
var err error
args := map[string]interface{}{}
var arg0 model.ValidateJWTTokenInput
if tmp, ok := rawArgs["params"]; ok {
ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("params"))
arg0, err = ec.unmarshalNValidateJWTTokenInput2githubᚗcomᚋauthorizerdevᚋauthorizerᚋserverᚋgraphᚋmodelᚐValidateJWTTokenInput(ctx, tmp)
if err != nil {
return nil, err
}
}
args["params"] = arg0
return args, nil
}
func (ec *executionContext) field___Type_enumValues_args(ctx context.Context, rawArgs map[string]interface{}) (map[string]interface{}, error) {
var err error
args := map[string]interface{}{}
@@ -3331,6 +3522,102 @@ func (ec *executionContext) _Error_reason(ctx context.Context, field graphql.Col
return ec.marshalNString2string(ctx, field.Selections, res)
}
func (ec *executionContext) _GenerateJWTKeysResponse_secret(ctx context.Context, field graphql.CollectedField, obj *model.GenerateJWTKeysResponse) (ret graphql.Marshaler) {
defer func() {
if r := recover(); r != nil {
ec.Error(ctx, ec.Recover(ctx, r))
ret = graphql.Null
}
}()
fc := &graphql.FieldContext{
Object: "GenerateJWTKeysResponse",
Field: field,
Args: nil,
IsMethod: false,
IsResolver: false,
}
ctx = graphql.WithFieldContext(ctx, fc)
resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) {
ctx = rctx // use context from middleware stack in children
return obj.Secret, nil
})
if err != nil {
ec.Error(ctx, err)
return graphql.Null
}
if resTmp == nil {
return graphql.Null
}
res := resTmp.(*string)
fc.Result = res
return ec.marshalOString2ᚖstring(ctx, field.Selections, res)
}
func (ec *executionContext) _GenerateJWTKeysResponse_public_key(ctx context.Context, field graphql.CollectedField, obj *model.GenerateJWTKeysResponse) (ret graphql.Marshaler) {
defer func() {
if r := recover(); r != nil {
ec.Error(ctx, ec.Recover(ctx, r))
ret = graphql.Null
}
}()
fc := &graphql.FieldContext{
Object: "GenerateJWTKeysResponse",
Field: field,
Args: nil,
IsMethod: false,
IsResolver: false,
}
ctx = graphql.WithFieldContext(ctx, fc)
resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) {
ctx = rctx // use context from middleware stack in children
return obj.PublicKey, nil
})
if err != nil {
ec.Error(ctx, err)
return graphql.Null
}
if resTmp == nil {
return graphql.Null
}
res := resTmp.(*string)
fc.Result = res
return ec.marshalOString2ᚖstring(ctx, field.Selections, res)
}
func (ec *executionContext) _GenerateJWTKeysResponse_private_key(ctx context.Context, field graphql.CollectedField, obj *model.GenerateJWTKeysResponse) (ret graphql.Marshaler) {
defer func() {
if r := recover(); r != nil {
ec.Error(ctx, ec.Recover(ctx, r))
ret = graphql.Null
}
}()
fc := &graphql.FieldContext{
Object: "GenerateJWTKeysResponse",
Field: field,
Args: nil,
IsMethod: false,
IsResolver: false,
}
ctx = graphql.WithFieldContext(ctx, fc)
resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) {
ctx = rctx // use context from middleware stack in children
return obj.PrivateKey, nil
})
if err != nil {
ec.Error(ctx, err)
return graphql.Null
}
if resTmp == nil {
return graphql.Null
}
res := resTmp.(*string)
fc.Result = res
return ec.marshalOString2ᚖstring(ctx, field.Selections, res)
}
func (ec *executionContext) _Meta_version(ctx context.Context, field graphql.CollectedField, obj *model.Meta) (ret graphql.Marshaler) {
defer func() {
if r := recover(); r != nil {
@@ -4346,6 +4633,132 @@ func (ec *executionContext) _Mutation__invite_members(ctx context.Context, field
return ec.marshalNResponse2ᚖgithubᚗcomᚋauthorizerdevᚋauthorizerᚋserverᚋgraphᚋmodelᚐResponse(ctx, field.Selections, res)
}
func (ec *executionContext) _Mutation__revoke_access(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) {
defer func() {
if r := recover(); r != nil {
ec.Error(ctx, ec.Recover(ctx, r))
ret = graphql.Null
}
}()
fc := &graphql.FieldContext{
Object: "Mutation",
Field: field,
Args: nil,
IsMethod: true,
IsResolver: true,
}
ctx = graphql.WithFieldContext(ctx, fc)
rawArgs := field.ArgumentMap(ec.Variables)
args, err := ec.field_Mutation__revoke_access_args(ctx, rawArgs)
if err != nil {
ec.Error(ctx, err)
return graphql.Null
}
fc.Args = args
resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) {
ctx = rctx // use context from middleware stack in children
return ec.resolvers.Mutation().RevokeAccess(rctx, args["param"].(model.UpdateAccessInput))
})
if err != nil {
ec.Error(ctx, err)
return graphql.Null
}
if resTmp == nil {
if !graphql.HasFieldError(ctx, fc) {
ec.Errorf(ctx, "must not be null")
}
return graphql.Null
}
res := resTmp.(*model.Response)
fc.Result = res
return ec.marshalNResponse2ᚖgithubᚗcomᚋauthorizerdevᚋauthorizerᚋserverᚋgraphᚋmodelᚐResponse(ctx, field.Selections, res)
}
func (ec *executionContext) _Mutation__enable_access(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) {
defer func() {
if r := recover(); r != nil {
ec.Error(ctx, ec.Recover(ctx, r))
ret = graphql.Null
}
}()
fc := &graphql.FieldContext{
Object: "Mutation",
Field: field,
Args: nil,
IsMethod: true,
IsResolver: true,
}
ctx = graphql.WithFieldContext(ctx, fc)
rawArgs := field.ArgumentMap(ec.Variables)
args, err := ec.field_Mutation__enable_access_args(ctx, rawArgs)
if err != nil {
ec.Error(ctx, err)
return graphql.Null
}
fc.Args = args
resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) {
ctx = rctx // use context from middleware stack in children
return ec.resolvers.Mutation().EnableAccess(rctx, args["param"].(model.UpdateAccessInput))
})
if err != nil {
ec.Error(ctx, err)
return graphql.Null
}
if resTmp == nil {
if !graphql.HasFieldError(ctx, fc) {
ec.Errorf(ctx, "must not be null")
}
return graphql.Null
}
res := resTmp.(*model.Response)
fc.Result = res
return ec.marshalNResponse2ᚖgithubᚗcomᚋauthorizerdevᚋauthorizerᚋserverᚋgraphᚋmodelᚐResponse(ctx, field.Selections, res)
}
func (ec *executionContext) _Mutation__generate_jwt_keys(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) {
defer func() {
if r := recover(); r != nil {
ec.Error(ctx, ec.Recover(ctx, r))
ret = graphql.Null
}
}()
fc := &graphql.FieldContext{
Object: "Mutation",
Field: field,
Args: nil,
IsMethod: true,
IsResolver: true,
}
ctx = graphql.WithFieldContext(ctx, fc)
rawArgs := field.ArgumentMap(ec.Variables)
args, err := ec.field_Mutation__generate_jwt_keys_args(ctx, rawArgs)
if err != nil {
ec.Error(ctx, err)
return graphql.Null
}
fc.Args = args
resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) {
ctx = rctx // use context from middleware stack in children
return ec.resolvers.Mutation().GenerateJwtKeys(rctx, args["params"].(model.GenerateJWTKeysInput))
})
if err != nil {
ec.Error(ctx, err)
return graphql.Null
}
if resTmp == nil {
if !graphql.HasFieldError(ctx, fc) {
ec.Errorf(ctx, "must not be null")
}
return graphql.Null
}
res := resTmp.(*model.GenerateJWTKeysResponse)
fc.Result = res
return ec.marshalNGenerateJWTKeysResponse2ᚖgithubᚗcomᚋauthorizerdevᚋauthorizerᚋserverᚋgraphᚋmodelᚐGenerateJWTKeysResponse(ctx, field.Selections, res)
}
func (ec *executionContext) _Pagination_limit(ctx context.Context, field graphql.CollectedField, obj *model.Pagination) (ret graphql.Marshaler) {
defer func() {
if r := recover(); r != nil {
@@ -4598,6 +5011,48 @@ func (ec *executionContext) _Query_profile(ctx context.Context, field graphql.Co
return ec.marshalNUser2ᚖgithubᚗcomᚋauthorizerdevᚋauthorizerᚋserverᚋgraphᚋmodelᚐUser(ctx, field.Selections, res)
}
func (ec *executionContext) _Query_validate_jwt_token(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) {
defer func() {
if r := recover(); r != nil {
ec.Error(ctx, ec.Recover(ctx, r))
ret = graphql.Null
}
}()
fc := &graphql.FieldContext{
Object: "Query",
Field: field,
Args: nil,
IsMethod: true,
IsResolver: true,
}
ctx = graphql.WithFieldContext(ctx, fc)
rawArgs := field.ArgumentMap(ec.Variables)
args, err := ec.field_Query_validate_jwt_token_args(ctx, rawArgs)
if err != nil {
ec.Error(ctx, err)
return graphql.Null
}
fc.Args = args
resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) {
ctx = rctx // use context from middleware stack in children
return ec.resolvers.Query().ValidateJwtToken(rctx, args["params"].(model.ValidateJWTTokenInput))
})
if err != nil {
ec.Error(ctx, err)
return graphql.Null
}
if resTmp == nil {
if !graphql.HasFieldError(ctx, fc) {
ec.Errorf(ctx, "must not be null")
}
return graphql.Null
}
res := resTmp.(*model.ValidateJWTTokenResponse)
fc.Result = res
return ec.marshalNValidateJWTTokenResponse2ᚖgithubᚗcomᚋauthorizerdevᚋauthorizerᚋserverᚋgraphᚋmodelᚐValidateJWTTokenResponse(ctx, field.Selections, res)
}
func (ec *executionContext) _Query__users(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) {
defer func() {
if r := recover(); r != nil {
@@ -5417,6 +5872,38 @@ func (ec *executionContext) _User_updated_at(ctx context.Context, field graphql.
return ec.marshalOInt642ᚖint64(ctx, field.Selections, res)
}
func (ec *executionContext) _User_revoked_timestamp(ctx context.Context, field graphql.CollectedField, obj *model.User) (ret graphql.Marshaler) {
defer func() {
if r := recover(); r != nil {
ec.Error(ctx, ec.Recover(ctx, r))
ret = graphql.Null
}
}()
fc := &graphql.FieldContext{
Object: "User",
Field: field,
Args: nil,
IsMethod: false,
IsResolver: false,
}
ctx = graphql.WithFieldContext(ctx, fc)
resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) {
ctx = rctx // use context from middleware stack in children
return obj.RevokedTimestamp, nil
})
if err != nil {
ec.Error(ctx, err)
return graphql.Null
}
if resTmp == nil {
return graphql.Null
}
res := resTmp.(*int64)
fc.Result = res
return ec.marshalOInt642ᚖint64(ctx, field.Selections, res)
}
func (ec *executionContext) _Users_pagination(ctx context.Context, field graphql.CollectedField, obj *model.Users) (ret graphql.Marshaler) {
defer func() {
if r := recover(); r != nil {
@@ -5487,6 +5974,41 @@ func (ec *executionContext) _Users_users(ctx context.Context, field graphql.Coll
return ec.marshalNUser2ᚕᚖgithubᚗcomᚋauthorizerdevᚋauthorizerᚋserverᚋgraphᚋmodelᚐUserᚄ(ctx, field.Selections, res)
}
func (ec *executionContext) _ValidateJWTTokenResponse_is_valid(ctx context.Context, field graphql.CollectedField, obj *model.ValidateJWTTokenResponse) (ret graphql.Marshaler) {
defer func() {
if r := recover(); r != nil {
ec.Error(ctx, ec.Recover(ctx, r))
ret = graphql.Null
}
}()
fc := &graphql.FieldContext{
Object: "ValidateJWTTokenResponse",
Field: field,
Args: nil,
IsMethod: false,
IsResolver: false,
}
ctx = graphql.WithFieldContext(ctx, fc)
resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) {
ctx = rctx // use context from middleware stack in children
return obj.IsValid, nil
})
if err != nil {
ec.Error(ctx, err)
return graphql.Null
}
if resTmp == nil {
if !graphql.HasFieldError(ctx, fc) {
ec.Errorf(ctx, "must not be null")
}
return graphql.Null
}
res := resTmp.(bool)
fc.Result = res
return ec.marshalNBoolean2bool(ctx, field.Selections, res)
}
func (ec *executionContext) _VerificationRequest_id(ctx context.Context, field graphql.CollectedField, obj *model.VerificationRequest) (ret graphql.Marshaler) {
defer func() {
if r := recover(); r != nil {
@@ -7078,6 +7600,29 @@ func (ec *executionContext) unmarshalInputForgotPasswordInput(ctx context.Contex
return it, nil
}
func (ec *executionContext) unmarshalInputGenerateJWTKeysInput(ctx context.Context, obj interface{}) (model.GenerateJWTKeysInput, error) {
var it model.GenerateJWTKeysInput
asMap := map[string]interface{}{}
for k, v := range obj.(map[string]interface{}) {
asMap[k] = v
}
for k, v := range asMap {
switch k {
case "type":
var err error
ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("type"))
it.Type, err = ec.unmarshalNString2string(ctx, v)
if err != nil {
return it, err
}
}
}
return it, nil
}
func (ec *executionContext) unmarshalInputInviteMemberInput(ctx context.Context, obj interface{}) (model.InviteMemberInput, error) {
var it model.InviteMemberInput
asMap := map[string]interface{}{}
@@ -7516,6 +8061,29 @@ func (ec *executionContext) unmarshalInputSignUpInput(ctx context.Context, obj i
return it, nil
}
func (ec *executionContext) unmarshalInputUpdateAccessInput(ctx context.Context, obj interface{}) (model.UpdateAccessInput, error) {
var it model.UpdateAccessInput
asMap := map[string]interface{}{}
for k, v := range obj.(map[string]interface{}) {
asMap[k] = v
}
for k, v := range asMap {
switch k {
case "user_id":
var err error
ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("user_id"))
it.UserID, err = ec.unmarshalNString2string(ctx, v)
if err != nil {
return it, err
}
}
}
return it, nil
}
func (ec *executionContext) unmarshalInputUpdateEnvInput(ctx context.Context, obj interface{}) (model.UpdateEnvInput, error) {
var it model.UpdateEnvInput
asMap := map[string]interface{}{}
@@ -8025,6 +8593,45 @@ func (ec *executionContext) unmarshalInputUpdateUserInput(ctx context.Context, o
return it, nil
}
func (ec *executionContext) unmarshalInputValidateJWTTokenInput(ctx context.Context, obj interface{}) (model.ValidateJWTTokenInput, error) {
var it model.ValidateJWTTokenInput
asMap := map[string]interface{}{}
for k, v := range obj.(map[string]interface{}) {
asMap[k] = v
}
for k, v := range asMap {
switch k {
case "token_type":
var err error
ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("token_type"))
it.TokenType, err = ec.unmarshalNString2string(ctx, v)
if err != nil {
return it, err
}
case "token":
var err error
ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("token"))
it.Token, err = ec.unmarshalNString2string(ctx, v)
if err != nil {
return it, err
}
case "roles":
var err error
ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("roles"))
it.Roles, err = ec.unmarshalOString2ᚕstringᚄ(ctx, v)
if err != nil {
return it, err
}
}
}
return it, nil
}
func (ec *executionContext) unmarshalInputVerifyEmailInput(ctx context.Context, obj interface{}) (model.VerifyEmailInput, error) {
var it model.VerifyEmailInput
asMap := map[string]interface{}{}
@@ -8238,6 +8845,34 @@ func (ec *executionContext) _Error(ctx context.Context, sel ast.SelectionSet, ob
return out
}
var generateJWTKeysResponseImplementors = []string{"GenerateJWTKeysResponse"}
func (ec *executionContext) _GenerateJWTKeysResponse(ctx context.Context, sel ast.SelectionSet, obj *model.GenerateJWTKeysResponse) graphql.Marshaler {
fields := graphql.CollectFields(ec.OperationContext, sel, generateJWTKeysResponseImplementors)
out := graphql.NewFieldSet(fields)
var invalids uint32
for i, field := range fields {
switch field.Name {
case "__typename":
out.Values[i] = graphql.MarshalString("GenerateJWTKeysResponse")
case "secret":
out.Values[i] = ec._GenerateJWTKeysResponse_secret(ctx, field, obj)
case "public_key":
out.Values[i] = ec._GenerateJWTKeysResponse_public_key(ctx, field, obj)
case "private_key":
out.Values[i] = ec._GenerateJWTKeysResponse_private_key(ctx, field, obj)
default:
panic("unknown field " + strconv.Quote(field.Name))
}
}
out.Dispatch()
if invalids > 0 {
return graphql.Null
}
return out
}
var metaImplementors = []string{"Meta"}
func (ec *executionContext) _Meta(ctx context.Context, sel ast.SelectionSet, obj *model.Meta) graphql.Marshaler {
@@ -8405,6 +9040,21 @@ func (ec *executionContext) _Mutation(ctx context.Context, sel ast.SelectionSet)
if out.Values[i] == graphql.Null {
invalids++
}
case "_revoke_access":
out.Values[i] = ec._Mutation__revoke_access(ctx, field)
if out.Values[i] == graphql.Null {
invalids++
}
case "_enable_access":
out.Values[i] = ec._Mutation__enable_access(ctx, field)
if out.Values[i] == graphql.Null {
invalids++
}
case "_generate_jwt_keys":
out.Values[i] = ec._Mutation__generate_jwt_keys(ctx, field)
if out.Values[i] == graphql.Null {
invalids++
}
default:
panic("unknown field " + strconv.Quote(field.Name))
}
@@ -8515,6 +9165,20 @@ func (ec *executionContext) _Query(ctx context.Context, sel ast.SelectionSet) gr
}
return res
})
case "validate_jwt_token":
field := field
out.Concurrently(i, func() (res graphql.Marshaler) {
defer func() {
if r := recover(); r != nil {
ec.Error(ctx, ec.Recover(ctx, r))
}
}()
res = ec._Query_validate_jwt_token(ctx, field)
if res == graphql.Null {
atomic.AddUint32(&invalids, 1)
}
return res
})
case "_users":
field := field
out.Concurrently(i, func() (res graphql.Marshaler) {
@@ -8673,6 +9337,8 @@ func (ec *executionContext) _User(ctx context.Context, sel ast.SelectionSet, obj
out.Values[i] = ec._User_created_at(ctx, field, obj)
case "updated_at":
out.Values[i] = ec._User_updated_at(ctx, field, obj)
case "revoked_timestamp":
out.Values[i] = ec._User_revoked_timestamp(ctx, field, obj)
default:
panic("unknown field " + strconv.Quote(field.Name))
}
@@ -8716,6 +9382,33 @@ func (ec *executionContext) _Users(ctx context.Context, sel ast.SelectionSet, ob
return out
}
var validateJWTTokenResponseImplementors = []string{"ValidateJWTTokenResponse"}
func (ec *executionContext) _ValidateJWTTokenResponse(ctx context.Context, sel ast.SelectionSet, obj *model.ValidateJWTTokenResponse) graphql.Marshaler {
fields := graphql.CollectFields(ec.OperationContext, sel, validateJWTTokenResponseImplementors)
out := graphql.NewFieldSet(fields)
var invalids uint32
for i, field := range fields {
switch field.Name {
case "__typename":
out.Values[i] = graphql.MarshalString("ValidateJWTTokenResponse")
case "is_valid":
out.Values[i] = ec._ValidateJWTTokenResponse_is_valid(ctx, field, obj)
if out.Values[i] == graphql.Null {
invalids++
}
default:
panic("unknown field " + strconv.Quote(field.Name))
}
}
out.Dispatch()
if invalids > 0 {
return graphql.Null
}
return out
}
var verificationRequestImplementors = []string{"VerificationRequest"}
func (ec *executionContext) _VerificationRequest(ctx context.Context, sel ast.SelectionSet, obj *model.VerificationRequest) graphql.Marshaler {
@@ -9104,6 +9797,25 @@ func (ec *executionContext) unmarshalNForgotPasswordInput2githubᚗcomᚋauthori
return res, graphql.ErrorOnPath(ctx, err)
}
func (ec *executionContext) unmarshalNGenerateJWTKeysInput2githubᚗcomᚋauthorizerdevᚋauthorizerᚋserverᚋgraphᚋmodelᚐGenerateJWTKeysInput(ctx context.Context, v interface{}) (model.GenerateJWTKeysInput, error) {
res, err := ec.unmarshalInputGenerateJWTKeysInput(ctx, v)
return res, graphql.ErrorOnPath(ctx, err)
}
func (ec *executionContext) marshalNGenerateJWTKeysResponse2githubᚗcomᚋauthorizerdevᚋauthorizerᚋserverᚋgraphᚋmodelᚐGenerateJWTKeysResponse(ctx context.Context, sel ast.SelectionSet, v model.GenerateJWTKeysResponse) graphql.Marshaler {
return ec._GenerateJWTKeysResponse(ctx, sel, &v)
}
func (ec *executionContext) marshalNGenerateJWTKeysResponse2ᚖgithubᚗcomᚋauthorizerdevᚋauthorizerᚋserverᚋgraphᚋmodelᚐGenerateJWTKeysResponse(ctx context.Context, sel ast.SelectionSet, v *model.GenerateJWTKeysResponse) graphql.Marshaler {
if v == nil {
if !graphql.HasFieldError(ctx, graphql.GetFieldContext(ctx)) {
ec.Errorf(ctx, "must not be null")
}
return graphql.Null
}
return ec._GenerateJWTKeysResponse(ctx, sel, v)
}
func (ec *executionContext) unmarshalNID2string(ctx context.Context, v interface{}) (string, error) {
res, err := graphql.UnmarshalID(v)
return res, graphql.ErrorOnPath(ctx, err)
@@ -9258,6 +9970,11 @@ func (ec *executionContext) marshalNString2ᚕstringᚄ(ctx context.Context, sel
return ret
}
func (ec *executionContext) unmarshalNUpdateAccessInput2githubᚗcomᚋauthorizerdevᚋauthorizerᚋserverᚋgraphᚋmodelᚐUpdateAccessInput(ctx context.Context, v interface{}) (model.UpdateAccessInput, error) {
res, err := ec.unmarshalInputUpdateAccessInput(ctx, v)
return res, graphql.ErrorOnPath(ctx, err)
}
func (ec *executionContext) unmarshalNUpdateEnvInput2githubᚗcomᚋauthorizerdevᚋauthorizerᚋserverᚋgraphᚋmodelᚐUpdateEnvInput(ctx context.Context, v interface{}) (model.UpdateEnvInput, error) {
res, err := ec.unmarshalInputUpdateEnvInput(ctx, v)
return res, graphql.ErrorOnPath(ctx, err)
@@ -9345,6 +10062,25 @@ func (ec *executionContext) marshalNUsers2ᚖgithubᚗcomᚋauthorizerdevᚋauth
return ec._Users(ctx, sel, v)
}
func (ec *executionContext) unmarshalNValidateJWTTokenInput2githubᚗcomᚋauthorizerdevᚋauthorizerᚋserverᚋgraphᚋmodelᚐValidateJWTTokenInput(ctx context.Context, v interface{}) (model.ValidateJWTTokenInput, error) {
res, err := ec.unmarshalInputValidateJWTTokenInput(ctx, v)
return res, graphql.ErrorOnPath(ctx, err)
}
func (ec *executionContext) marshalNValidateJWTTokenResponse2githubᚗcomᚋauthorizerdevᚋauthorizerᚋserverᚋgraphᚋmodelᚐValidateJWTTokenResponse(ctx context.Context, sel ast.SelectionSet, v model.ValidateJWTTokenResponse) graphql.Marshaler {
return ec._ValidateJWTTokenResponse(ctx, sel, &v)
}
func (ec *executionContext) marshalNValidateJWTTokenResponse2ᚖgithubᚗcomᚋauthorizerdevᚋauthorizerᚋserverᚋgraphᚋmodelᚐValidateJWTTokenResponse(ctx context.Context, sel ast.SelectionSet, v *model.ValidateJWTTokenResponse) graphql.Marshaler {
if v == nil {
if !graphql.HasFieldError(ctx, graphql.GetFieldContext(ctx)) {
ec.Errorf(ctx, "must not be null")
}
return graphql.Null
}
return ec._ValidateJWTTokenResponse(ctx, sel, v)
}
func (ec *executionContext) marshalNVerificationRequest2ᚕᚖgithubᚗcomᚋauthorizerdevᚋauthorizerᚋserverᚋgraphᚋmodelᚐVerificationRequestᚄ(ctx context.Context, sel ast.SelectionSet, v []*model.VerificationRequest) graphql.Marshaler {
ret := make(graphql.Array, len(v))
var wg sync.WaitGroup

View File

@@ -75,6 +75,16 @@ type ForgotPasswordInput struct {
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 {
Emails []string `json:"emails"`
RedirectURI *string `json:"redirect_uri"`
@@ -164,6 +174,10 @@ type SignUpInput struct {
RedirectURI *string `json:"redirect_uri"`
}
type UpdateAccessInput struct {
UserID string `json:"user_id"`
}
type UpdateEnvInput struct {
AdminSecret *string `json:"ADMIN_SECRET"`
CustomAccessTokenScript *string `json:"CUSTOM_ACCESS_TOKEN_SCRIPT"`
@@ -249,6 +263,7 @@ type User struct {
Roles []string `json:"roles"`
CreatedAt *int64 `json:"created_at"`
UpdatedAt *int64 `json:"updated_at"`
RevokedTimestamp *int64 `json:"revoked_timestamp"`
}
type Users struct {
@@ -256,6 +271,16 @@ type Users struct {
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 {
ID string `json:"id"`
Identifier *string `json:"identifier"`

View File

@@ -43,6 +43,7 @@ type User {
roles: [String!]!
created_at: Int64
updated_at: Int64
revoked_timestamp: Int64
}
type Users {
@@ -126,6 +127,16 @@ type Env {
ORGANIZATION_LOGO: String
}
type ValidateJWTTokenResponse {
is_valid: Boolean!
}
type GenerateJWTKeysResponse {
secret: String
public_key: String
private_key: String
}
input UpdateEnvInput {
ADMIN_SECRET: String
CUSTOM_ACCESS_TOKEN_SCRIPT: String
@@ -281,6 +292,20 @@ input InviteMemberInput {
redirect_uri: String
}
input UpdateAccessInput {
user_id: String!
}
input ValidateJWTTokenInput {
token_type: String!
token: String!
roles: [String!]
}
input GenerateJWTKeysInput {
type: String!
}
type Mutation {
signup(params: SignUpInput!): AuthResponse!
login(params: LoginInput!): AuthResponse!
@@ -300,12 +325,16 @@ type Mutation {
_admin_logout: Response!
_update_env(params: UpdateEnvInput!): Response!
_invite_members(params: InviteMemberInput!): Response!
_revoke_access(param: UpdateAccessInput!): Response!
_enable_access(param: UpdateAccessInput!): Response!
_generate_jwt_keys(params: GenerateJWTKeysInput!): GenerateJWTKeysResponse!
}
type Query {
meta: Meta!
session(params: SessionQueryInput): AuthResponse!
profile: User!
validate_jwt_token(params: ValidateJWTTokenInput!): ValidateJWTTokenResponse!
# admin only apis
_users(params: PaginatedInput): Users!
_verification_requests(params: PaginatedInput): VerificationRequests!

View File

@@ -79,6 +79,18 @@ func (r *mutationResolver) InviteMembers(ctx context.Context, params model.Invit
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) {
return resolvers.MetaResolver(ctx)
}
@@ -91,6 +103,10 @@ func (r *queryResolver) Profile(ctx context.Context) (*model.User, error) {
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) {
return resolvers.UsersResolver(ctx, params)
}

View File

@@ -95,9 +95,12 @@ func OAuthCallbackHandler() gin.HandlerFunc {
user.EmailVerifiedAt = &now
user, _ = db.Provider.AddUser(user)
} 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
// if not append google to existing signup method and save it
signupMethod := existingUser.SignupMethods
if !strings.Contains(signupMethod, provider) {
signupMethod = signupMethod + "," + provider

View 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
}

View 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")
}

View File

@@ -35,6 +35,10 @@ func LoginResolver(ctx context.Context, params model.LoginInput) (*model.AuthRes
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) {
return res, fmt.Errorf(`user has not signed up email & password`)
}

View File

@@ -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.
// Need to modify roles in this case
if user.RevokedTimestamp != nil {
return res, fmt.Errorf(`user access has been revoked`)
}
// find the unassigned roles
if len(params.Roles) <= 0 {
inputRoles = envstore.EnvStoreObj.GetSliceStoreEnvVariable(constants.EnvKeyDefaultRoles)

View File

@@ -35,6 +35,10 @@ func ResetPasswordResolver(ctx context.Context, params model.ResetPasswordInput)
return res, fmt.Errorf(`passwords don't match`)
}
if !utils.IsValidPassword(params.Password) {
return res, fmt.Errorf(`password is not valid. It needs to be at least 6 characters long and contain at least one number, one uppercase letter, one lowercase letter and one special character`)
}
// verify if token exists in db
hostname := utils.GetHost(gc)
claim, err := token.ParseJWTToken(params.Token, hostname, verificationRequest.Nonce, verificationRequest.Email)

View 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
}

View File

@@ -40,6 +40,10 @@ func SignupResolver(ctx context.Context, params model.SignUpInput) (*model.AuthR
return res, fmt.Errorf(`password and confirm password does not match`)
}
if !utils.IsValidPassword(params.Password) {
return res, fmt.Errorf(`password is not valid. It needs to be at least 6 characters long and contain at least one number, one uppercase letter, one lowercase letter and one special character`)
}
params.Email = strings.ToLower(params.Email)
if !utils.IsValidEmail(params.Email) {

View File

@@ -53,11 +53,19 @@ func UpdateEnvResolver(ctx context.Context, params model.UpdateEnvInput) (*model
}
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
if crypto.IsHMACA(algo) {
if params.JwtSecret == nil {
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) {
@@ -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")
}
// reset the jwt secret
params.JwtSecret = &defaultSecret
_, err = crypto.ParseRsaPrivateKeyFromPemStr(*params.JwtPrivateKey)
if err != nil {
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")
}
// reset the jwt secret
params.JwtSecret = &defaultSecret
_, err = crypto.ParseEcdsaPrivateKeyFromPemStr(*params.JwtPrivateKey)
if err != nil {
return res, err

View 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
}

View 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)
})
}

View 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)
})
})
}

View File

@@ -6,6 +6,7 @@ import (
"github.com/authorizerdev/authorizer/server/constants"
"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"
@@ -16,11 +17,17 @@ func magicLinkLoginTests(t *testing.T, s TestSetup) {
t.Run(`should login with magic link`, func(t *testing.T) {
req, ctx := createContext(s)
email := "magic_link_login." + s.TestInfo.Email
envstore.EnvStoreObj.UpdateEnvVariable(constants.BoolStoreIdentifier, constants.EnvKeyDisableSignUp, true)
_, err := resolvers.MagicLinkLoginResolver(ctx, model.MagicLinkLoginInput{
Email: email,
})
assert.Nil(t, err)
assert.NotNil(t, err, "signup disabled")
envstore.EnvStoreObj.UpdateEnvVariable(constants.BoolStoreIdentifier, constants.EnvKeyDisableSignUp, false)
_, err = resolvers.MagicLinkLoginResolver(ctx, model.MagicLinkLoginInput{
Email: email,
})
assert.Nil(t, err, "signup should be successful")
verificationRequest, err := db.Provider.GetVerificationRequestByEmail(email, constants.VerificationTypeMagicLinkLogin)
verifyRes, err := resolvers.VerifyEmailResolver(ctx, model.VerifyEmailInput{

View File

@@ -43,6 +43,14 @@ func resetPasswordTest(t *testing.T, s TestSetup) {
ConfirmPassword: "test1",
})
assert.NotNil(t, err, "invalid password")
_, err = resolvers.ResetPasswordResolver(ctx, model.ResetPasswordInput{
Token: verificationRequest.Token,
Password: "Test@1234",
ConfirmPassword: "Test@1234",
})
assert.Nil(t, err, "password changed successfully")
cleanData(email)

View File

@@ -48,6 +48,9 @@ func TestResolvers(t *testing.T) {
adminSessionTests(t, s)
updateEnvTests(t, s)
envTests(t, s)
revokeAccessTest(t, s)
enableAccessTest(t, s)
generateJWTkeyTest(t, s)
// user tests
loginTests(t, s)
@@ -63,6 +66,7 @@ func TestResolvers(t *testing.T) {
logoutTests(t, s)
metaTests(t, s)
inviteUserTest(t, s)
validateJwtTokenTest(t, s)
})
}
}

View 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)
})
}

View File

@@ -5,6 +5,7 @@ import (
"github.com/authorizerdev/authorizer/server/constants"
"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"
@@ -20,14 +21,30 @@ func signupTests(t *testing.T, s TestSetup) {
Password: s.TestInfo.Password,
ConfirmPassword: s.TestInfo.Password + "s",
})
assert.NotNil(t, err, "invalid password errors")
assert.NotNil(t, err, "invalid password")
res, err = resolvers.SignupResolver(ctx, model.SignUpInput{
Email: email,
Password: "test",
ConfirmPassword: "test",
})
assert.NotNil(t, err, "invalid password")
envstore.EnvStoreObj.UpdateEnvVariable(constants.BoolStoreIdentifier, constants.EnvKeyDisableSignUp, true)
res, err = resolvers.SignupResolver(ctx, model.SignUpInput{
Email: email,
Password: s.TestInfo.Password,
ConfirmPassword: s.TestInfo.Password,
})
assert.NotNil(t, err, "singup disabled")
envstore.EnvStoreObj.UpdateEnvVariable(constants.BoolStoreIdentifier, constants.EnvKeyDisableSignUp, false)
res, err = resolvers.SignupResolver(ctx, model.SignUpInput{
Email: email,
Password: s.TestInfo.Password,
ConfirmPassword: s.TestInfo.Password,
})
assert.Nil(t, err, "signup should be successful")
user := *res.User
assert.Equal(t, email, user.Email)
assert.Nil(t, res.AccessToken, "access token should be nil")

View File

@@ -68,7 +68,7 @@ func createContext(s TestSetup) (*http.Request, context.Context) {
func testSetup() TestSetup {
testData := TestData{
Email: fmt.Sprintf("%d_authorizer_tester@yopmail.com", time.Now().Unix()),
Password: "test",
Password: "Test@123",
}
envstore.EnvStoreObj.UpdateEnvVariable(constants.StringStoreIdentifier, constants.EnvKeyEnvPath, "../../.env.sample")

View 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)
})
}

View File

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

View File

@@ -161,7 +161,12 @@ func GetAccessToken(gc *gin.Context) (string, error) {
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`)
}
@@ -350,7 +355,12 @@ func GetIDToken(gc *gin.Context) (string, error) {
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`)
}

View File

@@ -105,3 +105,59 @@ func ParseJWTToken(token, hostname, nonce, subject string) (jwt.MapClaims, error
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
}

View File

@@ -2,6 +2,7 @@ package utils
import (
"log"
"reflect"
"github.com/authorizerdev/authorizer/server/db"
"github.com/authorizerdev/authorizer/server/db/models"
@@ -47,3 +48,24 @@ func RemoveDuplicateString(strSlice []string) []string {
}
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
}

View File

@@ -86,3 +86,35 @@ func IsStringArrayEqual(a, b []string) bool {
}
return true
}
// ValidatePassword to validate the password against the following policy
// min char length: 6
// max char length: 36
// at least one upper case letter
// at least one lower case letter
// at least one digit
// at least one special character
func IsValidPassword(password string) bool {
if len(password) < 6 || len(password) > 36 {
return false
}
hasUpperCase := false
hasLowerCase := false
hasDigit := false
hasSpecialChar := false
for _, char := range password {
if char >= 'A' && char <= 'Z' {
hasUpperCase = true
} else if char >= 'a' && char <= 'z' {
hasLowerCase = true
} else if char >= '0' && char <= '9' {
hasDigit = true
} else {
hasSpecialChar = true
}
}
return hasUpperCase && hasLowerCase && hasDigit && hasSpecialChar
}