Compare commits

...

17 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
28 changed files with 1879 additions and 72 deletions

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,32 +99,24 @@ export default function Environment() {
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(() => {
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();
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">
JWT (JSON Web Tokens) Configurations
</Text>
<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

@@ -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

@@ -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

@@ -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

@@ -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

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