Compare commits

...

37 Commits

Author SHA1 Message Date
Lakhan Samani
57bc091499 fix state management 2022-03-07 23:44:19 +05:30
Lakhan Samani
128a2a8f75 feat: add support for response mode 2022-03-07 18:49:18 +05:30
Lakhan Samani
7b09a8817c fix: env encryption 2022-03-07 16:16:54 +05:30
Lakhan Samani
1d61840c6d fix: env decryption + remove log 2022-03-07 15:35:33 +05:30
Lakhan Samani
4b25e8941c fix: env decryption 2022-03-07 15:33:39 +05:30
Lakhan Samani
136eda15bf fix: env encryption 2022-03-07 15:29:37 +05:30
Lakhan Samani
eea6349318 chore: update app version 2022-03-07 12:33:42 +05:30
Lakhan Samani
513b5d2948 fix: env client secret 2022-03-07 12:23:45 +05:30
Lakhan Samani
e61dc2f08a fix: oauth login 2022-03-07 08:31:39 +05:30
Lakhan Samani
07552bc0b1 fix: use url safe code verifier 2022-03-05 13:50:59 +05:30
Lakhan Samani
0787a3b494 feat: add token endpoint 2022-03-04 12:56:11 +05:30
Lakhan Samani
2946428ab8 feat: add userinfo + logout 2022-03-04 00:36:27 +05:30
Lakhan Samani
5c7d32ec16 fix: remove compat cookie 2022-03-03 09:21:48 +05:30
Lakhan Samani
f0f2e0b6c8 fix: auth flow 2022-03-02 17:42:31 +05:30
Lakhan Samani
5399ea8f32 feat: add session token 2022-02-28 21:26:49 +05:30
Lakhan Samani
4830a7e9ac feat: add client secret 2022-02-28 13:14:16 +05:30
Lakhan Samani
df1c56bb1c fix: tests 2022-02-28 07:55:01 +05:30
Lakhan Samani
b68d9ce661 fix: update_env resolver 2022-02-26 20:36:22 +05:30
Lakhan Samani
145091dce1 feat: add well-known jwks.json endpoint 2022-02-26 18:14:43 +05:30
Lakhan Samani
ad46210112 fix: report error on initialization 2022-02-26 10:06:26 +05:30
Lakhan Samani
4e19f73845 fix: segregate env setup 2022-02-26 09:44:55 +05:30
Lakhan Samani
332269ecf9 feat: add well-known config endpoint 2022-02-23 11:24:52 +05:30
Lakhan Samani
dfa96f09a0 feat: add required jwt claims 2022-02-22 11:06:47 +05:30
Lakhan Samani
5bf26f7385 fix: setting custom access token script env 2022-02-18 16:45:12 +05:30
Lakhan Samani
1b269dc6db fix: rename session_client -> redis_client 2022-02-18 09:21:02 +05:30
Lakhan Samani
ce9a115a14 Merge pull request #128 from agarwal-nitesh/feat/redis_cluster_client
Add redis cluster client as a session store.
2022-02-18 09:19:24 +05:30
Nitesh Agarwal
f2f4c72aa6 Add redis cluster client as a session store. 2022-02-17 20:49:54 +05:30
Samyak Bhuta
9970eb16c9 Update README
Minor changes.
2022-02-17 15:04:05 +05:30
Lakhan Samani
23e53286bd Merge pull request #124 from anik-ghosh-au7/feat/jwt-types
support for more jwt encryption types added
2022-02-14 18:56:06 +05:30
Anik Ghosh
47acff05e2 support for more jwt encryption types added 2022-02-14 16:00:35 +05:30
Lakhan Samani
5572928619 fix: remove redundunt break statement 2022-02-12 22:55:33 +05:30
Lakhan Samani
85b4cd6339 Add support for maria db 2022-02-12 22:49:53 +05:30
Lakhan Samani
f0d38ab260 Merge pull request #121 from authorizerdev/feat/add-jwt-algos
feat: add jwt algos
2022-02-12 19:37:28 +05:30
Lakhan Samani
1276af43ef Add new line char 2022-02-12 19:36:29 +05:30
Lakhan Samani
66d42fc2bc Add support for public private key from admin apis 2022-02-12 19:34:22 +05:30
Lakhan Samani
1f058f954d Add test for jwt tokens 2022-02-12 19:26:37 +05:30
Lakhan Samani
8259fb515c Add support for more JWT algo methods 2022-02-12 15:54:23 +05:30
111 changed files with 3266 additions and 1434 deletions

3
.gitignore vendored
View File

@@ -10,4 +10,5 @@ build
data.db data.db
.DS_Store .DS_Store
.env.local .env.local
*.tar.gz *.tar.gz
.vscode/

View File

@@ -11,3 +11,6 @@ clean:
rm -rf build rm -rf build
test: test:
cd server && go clean --testcache && go test -v ./test cd server && go clean --testcache && go test -v ./test
generate:
cd server && go get github.com/99designs/gqlgen/cmd@v0.14.0 && go run github.com/99designs/gqlgen generate

View File

@@ -7,7 +7,7 @@
Authorizer Authorizer
</h1> </h1>
**Authorizer** is an open-source authentication and authorization solution for your applications. Bring your database and have complete control over the user information. You can self-host authorizer instances and connect to any database (Currently supports [Postgres](https://www.postgresql.org/), [MySQL](https://www.mysql.com/), [SQLite](https://www.sqlite.org/index.html), [SQLServer](https://www.microsoft.com/en-us/sql-server/), [MongoDB](https://mongodb.com/),[ArangoDB](https://www.arangodb.com/)). **Authorizer** is an open-source authentication and authorization solution for your applications. Bring your database and have complete control over the user information. You can self-host authorizer instances and connect to any database (Currently supports [Postgres](https://www.postgresql.org/), [MySQL](https://www.mysql.com/), [SQLite](https://www.sqlite.org/index.html), [SQLServer](https://www.microsoft.com/en-us/sql-server/), [MongoDB](https://mongodb.com/), [ArangoDB](https://www.arangodb.com/)).
## Table of contents ## Table of contents

30
app/package-lock.json generated
View File

@@ -9,7 +9,7 @@
"version": "1.0.0", "version": "1.0.0",
"license": "ISC", "license": "ISC",
"dependencies": { "dependencies": {
"@authorizerdev/authorizer-react": "latest", "@authorizerdev/authorizer-react": "0.8.0",
"@types/react": "^17.0.15", "@types/react": "^17.0.15",
"@types/react-dom": "^17.0.9", "@types/react-dom": "^17.0.9",
"esbuild": "^0.12.17", "esbuild": "^0.12.17",
@@ -24,9 +24,9 @@
} }
}, },
"node_modules/@authorizerdev/authorizer-js": { "node_modules/@authorizerdev/authorizer-js": {
"version": "0.2.1", "version": "0.3.0",
"resolved": "https://registry.npmjs.org/@authorizerdev/authorizer-js/-/authorizer-js-0.2.1.tgz", "resolved": "https://registry.npmjs.org/@authorizerdev/authorizer-js/-/authorizer-js-0.3.0.tgz",
"integrity": "sha512-5lQlh+nc5xTsPongfTyCSX24A1WESu/BjhmZwUNuScEOGady0qPoDHE3RBf46dpi5v05wbHCDN1IFEalX5zssQ==", "integrity": "sha512-KCE5Dw5MUnEgstBUayBriDQAOjqbxU7ixC00rTHAE6aD6TxJkeSls0vCTXpvt4iiKhFK6q9BhHwa/5NwWYpDBQ==",
"dependencies": { "dependencies": {
"node-fetch": "^2.6.1" "node-fetch": "^2.6.1"
}, },
@@ -35,11 +35,11 @@
} }
}, },
"node_modules/@authorizerdev/authorizer-react": { "node_modules/@authorizerdev/authorizer-react": {
"version": "0.4.3", "version": "0.8.0",
"resolved": "https://registry.npmjs.org/@authorizerdev/authorizer-react/-/authorizer-react-0.4.3.tgz", "resolved": "https://registry.npmjs.org/@authorizerdev/authorizer-react/-/authorizer-react-0.8.0.tgz",
"integrity": "sha512-o/wWe9zZ3ARYdjbDfhGfvOxe1YQrE1YQ+UN9pcq85YSDkbfBkOfcnJ4YxlxWdL0Obd/ErDIeQ3vskyrfvRf3sA==", "integrity": "sha512-178XWGEPsovy3f6Yi2Llh6kFmjdf3ZrkIsqIAKEGPhZawV/1sA6v+4FZp7ReuCxsCelckFFQUnPR8P7od+2HeA==",
"dependencies": { "dependencies": {
"@authorizerdev/authorizer-js": "^0.2.1", "@authorizerdev/authorizer-js": "^0.3.0",
"final-form": "^4.20.2", "final-form": "^4.20.2",
"react-final-form": "^6.5.3", "react-final-form": "^6.5.3",
"styled-components": "^5.3.0" "styled-components": "^5.3.0"
@@ -829,19 +829,19 @@
}, },
"dependencies": { "dependencies": {
"@authorizerdev/authorizer-js": { "@authorizerdev/authorizer-js": {
"version": "0.2.1", "version": "0.3.0",
"resolved": "https://registry.npmjs.org/@authorizerdev/authorizer-js/-/authorizer-js-0.2.1.tgz", "resolved": "https://registry.npmjs.org/@authorizerdev/authorizer-js/-/authorizer-js-0.3.0.tgz",
"integrity": "sha512-5lQlh+nc5xTsPongfTyCSX24A1WESu/BjhmZwUNuScEOGady0qPoDHE3RBf46dpi5v05wbHCDN1IFEalX5zssQ==", "integrity": "sha512-KCE5Dw5MUnEgstBUayBriDQAOjqbxU7ixC00rTHAE6aD6TxJkeSls0vCTXpvt4iiKhFK6q9BhHwa/5NwWYpDBQ==",
"requires": { "requires": {
"node-fetch": "^2.6.1" "node-fetch": "^2.6.1"
} }
}, },
"@authorizerdev/authorizer-react": { "@authorizerdev/authorizer-react": {
"version": "0.4.3", "version": "0.8.0",
"resolved": "https://registry.npmjs.org/@authorizerdev/authorizer-react/-/authorizer-react-0.4.3.tgz", "resolved": "https://registry.npmjs.org/@authorizerdev/authorizer-react/-/authorizer-react-0.8.0.tgz",
"integrity": "sha512-o/wWe9zZ3ARYdjbDfhGfvOxe1YQrE1YQ+UN9pcq85YSDkbfBkOfcnJ4YxlxWdL0Obd/ErDIeQ3vskyrfvRf3sA==", "integrity": "sha512-178XWGEPsovy3f6Yi2Llh6kFmjdf3ZrkIsqIAKEGPhZawV/1sA6v+4FZp7ReuCxsCelckFFQUnPR8P7od+2HeA==",
"requires": { "requires": {
"@authorizerdev/authorizer-js": "^0.2.1", "@authorizerdev/authorizer-js": "^0.3.0",
"final-form": "^4.20.2", "final-form": "^4.20.2",
"react-final-form": "^6.5.3", "react-final-form": "^6.5.3",
"styled-components": "^5.3.0" "styled-components": "^5.3.0"

View File

@@ -6,6 +6,9 @@ import Root from './Root';
export default function App() { export default function App() {
// @ts-ignore // @ts-ignore
const globalState: Record<string, string> = window['__authorizer__']; const globalState: Record<string, string> = window['__authorizer__'];
if (globalState.state) {
sessionStorage.setItem('authorizer_state', globalState.state);
}
return ( return (
<div <div
style={{ style={{

View File

@@ -11,9 +11,14 @@ export default function Root() {
useEffect(() => { useEffect(() => {
if (token) { if (token) {
const state = sessionStorage.getItem('authorizer_state')?.trim();
const url = new URL(config.redirectURL || '/app'); const url = new URL(config.redirectURL || '/app');
if (url.origin !== window.location.origin) { if (url.origin !== window.location.origin) {
window.location.href = config.redirectURL || '/app'; console.log({ x: `${config.redirectURL || '/app'}?state=${state}` });
sessionStorage.removeItem('authorizer_state');
window.location.replace(
`${config.redirectURL || '/app'}?state=${state}`
);
} }
} }
return () => {}; return () => {};

View File

@@ -259,17 +259,6 @@ const InputField = ({
); );
} }
if (Object.values(SelectInputType).includes(inputType)) { if (Object.values(SelectInputType).includes(inputType)) {
if (inputType === SelectInputType.JWT_TYPE) {
return (
<Select size="sm" {...props}>
{[variables[inputType]].map((value: string) => (
<option value="value" key={value}>
{value}
</option>
))}
</Select>
);
}
const { options, ...rest } = props; const { options, ...rest } = props;
return ( return (
<Select <Select
@@ -293,10 +282,18 @@ const InputField = ({
<Textarea <Textarea
{...props} {...props}
size="lg" size="lg"
value={inputData[inputType]} fontSize={14}
onChange={(e: any) => { value={variables[inputType] ? variables[inputType] : ''}
setInputData({ ...inputData, [inputType]: e.target.value }); onChange={(
}} event: Event & {
target: HTMLInputElement;
}
) =>
setVariables({
...variables,
[inputType]: event.target.value,
})
}
/> />
); );
} }

View File

@@ -2,6 +2,7 @@ export const LOGO_URL =
'https://user-images.githubusercontent.com/6964334/147834043-fc384cab-e7ca-40f8-9663-38fc25fd5f3a.png'; 'https://user-images.githubusercontent.com/6964334/147834043-fc384cab-e7ca-40f8-9663-38fc25fd5f3a.png';
export const TextInputType = { export const TextInputType = {
CLIENT_ID: 'CLIENT_ID',
GOOGLE_CLIENT_ID: 'GOOGLE_CLIENT_ID', GOOGLE_CLIENT_ID: 'GOOGLE_CLIENT_ID',
GITHUB_CLIENT_ID: 'GITHUB_CLIENT_ID', GITHUB_CLIENT_ID: 'GITHUB_CLIENT_ID',
FACEBOOK_CLIENT_ID: 'FACEBOOK_CLIENT_ID', FACEBOOK_CLIENT_ID: 'FACEBOOK_CLIENT_ID',
@@ -25,6 +26,7 @@ export const TextInputType = {
}; };
export const HiddenInputType = { export const HiddenInputType = {
CLIENT_SECRET: 'CLIENT_SECRET',
GOOGLE_CLIENT_SECRET: 'GOOGLE_CLIENT_SECRET', GOOGLE_CLIENT_SECRET: 'GOOGLE_CLIENT_SECRET',
GITHUB_CLIENT_SECRET: 'GITHUB_CLIENT_SECRET', GITHUB_CLIENT_SECRET: 'GITHUB_CLIENT_SECRET',
FACEBOOK_CLIENT_SECRET: 'FACEBOOK_CLIENT_SECRET', FACEBOOK_CLIENT_SECRET: 'FACEBOOK_CLIENT_SECRET',
@@ -49,6 +51,8 @@ export const SelectInputType = {
export const TextAreaInputType = { export const TextAreaInputType = {
CUSTOM_ACCESS_TOKEN_SCRIPT: 'CUSTOM_ACCESS_TOKEN_SCRIPT', CUSTOM_ACCESS_TOKEN_SCRIPT: 'CUSTOM_ACCESS_TOKEN_SCRIPT',
JWT_PRIVATE_KEY: 'JWT_PRIVATE_KEY',
JWT_PUBLIC_KEY: 'JWT_PUBLIC_KEY',
}; };
export const SwitchInputType = { export const SwitchInputType = {
@@ -66,3 +70,21 @@ export const ArrayInputOperations = {
APPEND: 'APPEND', APPEND: 'APPEND',
REMOVE: 'REMOVE', REMOVE: 'REMOVE',
}; };
export const HMACEncryptionType = {
HS256: 'HS256',
HS384: 'HS384',
HS512: 'HS512',
};
export const RSAEncryptionType = {
RS256: 'RS256',
RS384: 'RS384',
RS512: 'RS512',
};
export const ECDSAEncryptionType = {
ES256: 'ES256',
ES384: 'ES384',
ES512: 'ES512',
};

View File

@@ -9,6 +9,8 @@ export const AdminSessionQuery = `
export const EnvVariablesQuery = ` export const EnvVariablesQuery = `
query { query {
_env{ _env{
CLIENT_ID,
CLIENT_SECRET,
GOOGLE_CLIENT_ID, GOOGLE_CLIENT_ID,
GOOGLE_CLIENT_SECRET, GOOGLE_CLIENT_SECRET,
GITHUB_CLIENT_ID, GITHUB_CLIENT_ID,
@@ -21,6 +23,8 @@ export const EnvVariablesQuery = `
JWT_TYPE, JWT_TYPE,
JWT_SECRET, JWT_SECRET,
JWT_ROLE_CLAIM, JWT_ROLE_CLAIM,
JWT_PRIVATE_KEY,
JWT_PUBLIC_KEY,
REDIS_URL, REDIS_URL,
SMTP_HOST, SMTP_HOST,
SMTP_PORT, SMTP_PORT,

View File

@@ -31,6 +31,9 @@ import {
TextInputType, TextInputType,
TextAreaInputType, TextAreaInputType,
SwitchInputType, SwitchInputType,
HMACEncryptionType,
RSAEncryptionType,
ECDSAEncryptionType,
} from '../constants'; } from '../constants';
import { UpdateEnvVariables } from '../graphql/mutation'; import { UpdateEnvVariables } from '../graphql/mutation';
import { getObjectDiff, capitalizeFirstLetter } from '../utils'; import { getObjectDiff, capitalizeFirstLetter } from '../utils';
@@ -48,6 +51,8 @@ interface envVarTypes {
JWT_TYPE: string; JWT_TYPE: string;
JWT_SECRET: string; JWT_SECRET: string;
JWT_ROLE_CLAIM: string; JWT_ROLE_CLAIM: string;
JWT_PRIVATE_KEY: string;
JWT_PUBLIC_KEY: string;
REDIS_URL: string; REDIS_URL: string;
SMTP_HOST: string; SMTP_HOST: string;
SMTP_PORT: string; SMTP_PORT: string;
@@ -92,6 +97,8 @@ export default function Environment() {
JWT_TYPE: '', JWT_TYPE: '',
JWT_SECRET: '', JWT_SECRET: '',
JWT_ROLE_CLAIM: '', JWT_ROLE_CLAIM: '',
JWT_PRIVATE_KEY: '',
JWT_PUBLIC_KEY: '',
REDIS_URL: '', REDIS_URL: '',
SMTP_HOST: '', SMTP_HOST: '',
SMTP_PORT: '', SMTP_PORT: '',
@@ -177,7 +184,6 @@ export default function Environment() {
const { const {
data: { _env: envData }, data: { _env: envData },
} = await client.query(EnvVariablesQuery).toPromise(); } = await client.query(EnvVariablesQuery).toPromise();
const diff = getObjectDiff(envVariables, envData); const diff = getObjectDiff(envVariables, envData);
const updatedEnvVariables = diff.reduce( const updatedEnvVariables = diff.reduce(
(acc: any, property: string) => ({ (acc: any, property: string) => ({
@@ -234,6 +240,42 @@ export default function Environment() {
return ( return (
<Box m="5" py="5" px="10" bg="white" rounded="md"> <Box m="5" py="5" px="10" bg="white" rounded="md">
<Text fontSize="md" paddingTop="2%" fontWeight="bold">
Your instance information
</Text>
<Stack spacing={6} padding="2% 0%">
<Flex>
<Flex w="30%" justifyContent="start" alignItems="center">
<Text fontSize="sm">Client ID</Text>
</Flex>
<Center w="70%">
<InputField
variables={envVariables}
setVariables={() => {}}
inputType={TextInputType.CLIENT_ID}
placeholder="Client ID"
isDisabled={true}
/>
</Center>
</Flex>
<Flex>
<Flex w="30%" justifyContent="start" alignItems="center">
<Text fontSize="sm">Client Secret</Text>
</Flex>
<Center w="70%">
<InputField
variables={envVariables}
setVariables={setEnvVariables}
fieldVisibility={fieldVisibility}
setFieldVisibility={setFieldVisibility}
inputType={HiddenInputType.CLIENT_SECRET}
placeholder="Client Secret"
isDisabled={true}
/>
</Center>
</Flex>
</Stack>
<Divider marginTop="2%" marginBottom="2%" />
<Text fontSize="md" paddingTop="2%" fontWeight="bold"> <Text fontSize="md" paddingTop="2%" fontWeight="bold">
Social Media Logins Social Media Logins
</Text> </Text>
@@ -374,39 +416,67 @@ export default function Environment() {
<Flex w="30%" justifyContent="start" alignItems="center"> <Flex w="30%" justifyContent="start" alignItems="center">
<Text fontSize="sm">JWT Type:</Text> <Text fontSize="sm">JWT Type:</Text>
</Flex> </Flex>
<Center w="70%"> <Flex w="70%">
<Flex w="100%" justifyContent="space-between">
<Flex flex="2">
<InputField
variables={envVariables}
setVariables={setEnvVariables}
inputType={SelectInputType.JWT_TYPE}
isDisabled={true}
defaultValue={SelectInputType.JWT_TYPE}
/>
</Flex>
<Flex flex="3" justifyContent="center" alignItems="center">
<Text fontSize="sm">
More JWT types will be enabled in upcoming releases.
</Text>
</Flex>
</Flex>
</Center>
</Flex>
<Flex>
<Flex w="30%" justifyContent="start" alignItems="center">
<Text fontSize="sm">JWT Secret</Text>
</Flex>
<Center w="70%">
<InputField <InputField
variables={envVariables} variables={envVariables}
setVariables={setEnvVariables} setVariables={setEnvVariables}
fieldVisibility={fieldVisibility} inputType={SelectInputType.JWT_TYPE}
setFieldVisibility={setFieldVisibility} value={SelectInputType.JWT_TYPE}
inputType={HiddenInputType.JWT_SECRET} options={{
...HMACEncryptionType,
...RSAEncryptionType,
...ECDSAEncryptionType,
}}
/> />
</Center> </Flex>
</Flex> </Flex>
{Object.values(HMACEncryptionType).includes(envVariables.JWT_TYPE) ? (
<Flex>
<Flex w="30%" justifyContent="start" alignItems="center">
<Text fontSize="sm">JWT Secret</Text>
</Flex>
<Center w="70%">
<InputField
variables={envVariables}
setVariables={setEnvVariables}
fieldVisibility={fieldVisibility}
setFieldVisibility={setFieldVisibility}
inputType={HiddenInputType.JWT_SECRET}
/>
</Center>
</Flex>
) : (
<>
<Flex>
<Flex w="30%" justifyContent="start" alignItems="center">
<Text fontSize="sm">Public Key</Text>
</Flex>
<Center w="70%">
<InputField
variables={envVariables}
setVariables={setEnvVariables}
inputType={TextAreaInputType.JWT_PUBLIC_KEY}
placeholder="Add public key here"
minH="25vh"
/>
</Center>
</Flex>
<Flex>
<Flex w="30%" justifyContent="start" alignItems="center">
<Text fontSize="sm">Private Key</Text>
</Flex>
<Center w="70%">
<InputField
variables={envVariables}
setVariables={setEnvVariables}
inputType={TextAreaInputType.JWT_PRIVATE_KEY}
placeholder="Add private key here"
minH="25vh"
/>
</Center>
</Flex>
</>
)}
<Flex> <Flex>
<Flex w="30%" justifyContent="start" alignItems="center"> <Flex w="30%" justifyContent="start" alignItems="center">
<Text fontSize="sm">JWT Role Claim:</Text> <Text fontSize="sm">JWT Role Claim:</Text>

View File

@@ -15,4 +15,6 @@ const (
DbTypeMongodb = "mongodb" DbTypeMongodb = "mongodb"
// DbTypeYugabyte is the yugabyte database type // DbTypeYugabyte is the yugabyte database type
DbTypeYugabyte = "yugabyte" DbTypeYugabyte = "yugabyte"
// DbTypeMariaDB is the mariadb database type
DbTypeMariaDB = "mariadb"
) )

View File

@@ -43,6 +43,10 @@ const (
EnvKeyJwtType = "JWT_TYPE" EnvKeyJwtType = "JWT_TYPE"
// EnvKeyJwtSecret key for env variable JWT_SECRET // EnvKeyJwtSecret key for env variable JWT_SECRET
EnvKeyJwtSecret = "JWT_SECRET" EnvKeyJwtSecret = "JWT_SECRET"
// EnvKeyJwtPrivateKey key for env variable JWT_PRIVATE_KEY
EnvKeyJwtPrivateKey = "JWT_PRIVATE_KEY"
// EnvKeyJwtPublicKey key for env variable JWT_PUBLIC_KEY
EnvKeyJwtPublicKey = "JWT_PUBLIC_KEY"
// EnvKeyAllowedOrigins key for env variable ALLOWED_ORIGINS // EnvKeyAllowedOrigins key for env variable ALLOWED_ORIGINS
EnvKeyAllowedOrigins = "ALLOWED_ORIGINS" EnvKeyAllowedOrigins = "ALLOWED_ORIGINS"
// EnvKeyAppURL key for env variable APP_URL // EnvKeyAppURL key for env variable APP_URL
@@ -55,8 +59,6 @@ const (
EnvKeyAdminCookieName = "ADMIN_COOKIE_NAME" EnvKeyAdminCookieName = "ADMIN_COOKIE_NAME"
// EnvKeyResetPasswordURL key for env variable RESET_PASSWORD_URL // EnvKeyResetPasswordURL key for env variable RESET_PASSWORD_URL
EnvKeyResetPasswordURL = "RESET_PASSWORD_URL" EnvKeyResetPasswordURL = "RESET_PASSWORD_URL"
// EnvKeyEncryptionKey key for env variable ENCRYPTION_KEY
EnvKeyEncryptionKey = "ENCRYPTION_KEY"
// EnvKeyDisableEmailVerification key for env variable DISABLE_EMAIL_VERIFICATION // EnvKeyDisableEmailVerification key for env variable DISABLE_EMAIL_VERIFICATION
EnvKeyDisableEmailVerification = "DISABLE_EMAIL_VERIFICATION" EnvKeyDisableEmailVerification = "DISABLE_EMAIL_VERIFICATION"
// EnvKeyDisableBasicAuthentication key for env variable DISABLE_BASIC_AUTH // EnvKeyDisableBasicAuthentication key for env variable DISABLE_BASIC_AUTH
@@ -89,8 +91,18 @@ const (
EnvKeyOrganizationName = "ORGANIZATION_NAME" EnvKeyOrganizationName = "ORGANIZATION_NAME"
// EnvKeyOrganizationLogo key for env variable ORGANIZATION_LOGO // EnvKeyOrganizationLogo key for env variable ORGANIZATION_LOGO
EnvKeyOrganizationLogo = "ORGANIZATION_LOGO" EnvKeyOrganizationLogo = "ORGANIZATION_LOGO"
// EnvKeyIsProd key for env variable IS_PROD
EnvKeyIsProd = "IS_PROD"
// EnvKeyCustomAccessTokenScript key for env variable CUSTOM_ACCESS_TOKEN_SCRIPT // EnvKeyCustomAccessTokenScript key for env variable CUSTOM_ACCESS_TOKEN_SCRIPT
EnvKeyCustomAccessTokenScript = "CUSTOM_ACCESS_TOKEN_SCRIPT" EnvKeyCustomAccessTokenScript = "CUSTOM_ACCESS_TOKEN_SCRIPT"
// Not Exposed Keys
// EnvKeyClientID key for env variable CLIENT_ID
EnvKeyClientID = "CLIENT_ID"
// EnvKeyClientSecret key for env variable CLIENT_SECRET
EnvKeyClientSecret = "CLIENT_SECRET"
// EnvKeyEncryptionKey key for env variable ENCRYPTION_KEY
EnvKeyEncryptionKey = "ENCRYPTION_KEY"
// EnvKeyJWK key for env variable JWK
EnvKeyJWK = "JWK"
// EnvKeyIsProd key for env variable IS_PROD
EnvKeyIsProd = "IS_PROD"
) )

View File

@@ -5,4 +5,6 @@ const (
TokenTypeRefreshToken = "refresh_token" TokenTypeRefreshToken = "refresh_token"
// TokenTypeAccessToken is the access_token token type // TokenTypeAccessToken is the access_token token type
TokenTypeAccessToken = "access_token" TokenTypeAccessToken = "access_token"
// TokenTypeIdentityToken is the identity_token token type
TokenTypeIdentityToken = "id_token"
) )

View File

@@ -16,12 +16,12 @@ func SetAdminCookie(gc *gin.Context, token string) {
hostname := utils.GetHost(gc) hostname := utils.GetHost(gc)
host, _ := utils.GetHostParts(hostname) host, _ := utils.GetHostParts(hostname)
gc.SetCookie(envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyAdminCookieName), token, 3600, "/", host, secure, httpOnly) gc.SetCookie(envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyAdminCookieName), token, 3600, "/", host, secure, httpOnly)
} }
// GetAdminCookie gets the admin cookie from the request // GetAdminCookie gets the admin cookie from the request
func GetAdminCookie(gc *gin.Context) (string, error) { func GetAdminCookie(gc *gin.Context) (string, error) {
cookie, err := gc.Request.Cookie(envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyAdminCookieName)) cookie, err := gc.Request.Cookie(envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyAdminCookieName))
if err != nil { if err != nil {
return "", err return "", err
} }
@@ -42,5 +42,5 @@ func DeleteAdminCookie(gc *gin.Context) {
hostname := utils.GetHost(gc) hostname := utils.GetHost(gc)
host, _ := utils.GetHostParts(hostname) host, _ := utils.GetHostParts(hostname)
gc.SetCookie(envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyAdminCookieName), "", -1, "/", host, secure, httpOnly) gc.SetCookie(envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyAdminCookieName), "", -1, "/", host, secure, httpOnly)
} }

View File

@@ -10,13 +10,8 @@ import (
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
) )
// SetCookie sets the cookie in the response. It sets 4 cookies // SetSession sets the session cookie in the response
// 1 COOKIE_NAME.access_token jwt token for the host (temp.abc.com) func SetSession(gc *gin.Context, sessionID string) {
// 2 COOKIE_NAME.access_token.domain jwt token for the domain (abc.com).
// 3 COOKIE_NAME.fingerprint fingerprint hash for the refresh token verification.
// 4 COOKIE_NAME.refresh_token refresh token
// Note all sites don't allow 2nd type of cookie
func SetCookie(gc *gin.Context, accessToken, refreshToken, fingerprintHash string) {
secure := true secure := true
httpOnly := true httpOnly := true
hostname := utils.GetHost(gc) hostname := utils.GetHost(gc)
@@ -26,77 +21,45 @@ func SetCookie(gc *gin.Context, accessToken, refreshToken, fingerprintHash strin
domain = "." + domain domain = "." + domain
} }
// TODO allow configuring from dashboard
year := 60 * 60 * 24 * 365 year := 60 * 60 * 24 * 365
thirtyMin := 60 * 30
gc.SetSameSite(http.SameSiteNoneMode) gc.SetSameSite(http.SameSiteNoneMode)
// set cookie for host gc.SetCookie(envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyCookieName)+"_session", sessionID, year, "/", host, secure, httpOnly)
gc.SetCookie(envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyCookieName)+".access_token", accessToken, thirtyMin, "/", host, secure, httpOnly) gc.SetCookie(envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyCookieName)+"_session_domain", sessionID, year, "/", domain, secure, httpOnly)
// in case of subdomain, set cookie for domain
gc.SetCookie(envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyCookieName)+".access_token.domain", accessToken, thirtyMin, "/", domain, secure, httpOnly)
// set finger print cookie (this should be accessed via cookie only)
gc.SetCookie(envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyCookieName)+".fingerprint", fingerprintHash, year, "/", host, secure, httpOnly)
// set refresh token cookie (this should be accessed via cookie only)
gc.SetCookie(envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyCookieName)+".refresh_token", refreshToken, year, "/", host, secure, httpOnly)
} }
// GetAccessTokenCookie to get access token cookie from the request // DeleteSession sets session cookies to expire
func GetAccessTokenCookie(gc *gin.Context) (string, error) { func DeleteSession(gc *gin.Context) {
cookie, err := gc.Request.Cookie(envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyCookieName) + ".access_token") secure := true
httpOnly := true
hostname := utils.GetHost(gc)
host, _ := utils.GetHostParts(hostname)
domain := utils.GetDomainName(hostname)
if domain != "localhost" {
domain = "." + domain
}
gc.SetSameSite(http.SameSiteNoneMode)
gc.SetCookie(envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyCookieName)+"_session", "", -1, "/", host, secure, httpOnly)
gc.SetCookie(envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyCookieName)+"_session_domain", "", -1, "/", domain, secure, httpOnly)
}
// GetSession gets the session cookie from context
func GetSession(gc *gin.Context) (string, error) {
var cookie *http.Cookie
var err error
cookie, err = gc.Request.Cookie(envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyCookieName) + "_session")
if err != nil { if err != nil {
cookie, err = gc.Request.Cookie(envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyCookieName) + ".access_token.domain") cookie, err = gc.Request.Cookie(envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyCookieName) + "_session_domain")
if err != nil { if err != nil {
return "", err return "", err
} }
} }
return cookie.Value, nil decodedValue, err := url.PathUnescape(cookie.Value)
}
// GetRefreshTokenCookie to get refresh token cookie
func GetRefreshTokenCookie(gc *gin.Context) (string, error) {
cookie, err := gc.Request.Cookie(envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyCookieName) + ".refresh_token")
if err != nil { if err != nil {
return "", err return "", err
} }
return cookie.Value, nil
}
// GetFingerPrintCookie to get fingerprint cookie
func GetFingerPrintCookie(gc *gin.Context) (string, error) {
cookie, err := gc.Request.Cookie(envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyCookieName) + ".fingerprint")
if err != nil {
return "", err
}
// cookie escapes special characters like $
// hence we need to unescape before comparing
decodedValue, err := url.QueryUnescape(cookie.Value)
if err != nil {
return "", err
}
return decodedValue, nil return decodedValue, nil
} }
// DeleteCookie sets response cookies to expire
func DeleteCookie(gc *gin.Context) {
secure := true
httpOnly := true
hostname := utils.GetHost(gc)
host, _ := utils.GetHostParts(hostname)
domain := utils.GetDomainName(hostname)
if domain != "localhost" {
domain = "." + domain
}
gc.SetSameSite(http.SameSiteNoneMode)
gc.SetCookie(envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyCookieName)+".access_token", "", -1, "/", host, secure, httpOnly)
gc.SetCookie(envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyCookieName)+".access_token.domain", "", -1, "/", domain, secure, httpOnly)
gc.SetCookie(envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyCookieName)+".fingerprint", "", -1, "/", host, secure, httpOnly)
gc.SetCookie(envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyCookieName)+".refresh_token", "", -1, "/", host, secure, httpOnly)
}

View File

@@ -1,35 +1,52 @@
package utils package crypto
import ( import (
"crypto/aes" "crypto/aes"
"crypto/cipher" "crypto/cipher"
"crypto/rand" "crypto/rand"
"encoding/base64"
"encoding/json"
"io" "io"
"github.com/authorizerdev/authorizer/server/constants" "github.com/authorizerdev/authorizer/server/constants"
"github.com/authorizerdev/authorizer/server/envstore" "github.com/authorizerdev/authorizer/server/envstore"
"golang.org/x/crypto/bcrypt"
) )
// EncryptB64 encrypts data into base64 string var bytes = []byte{35, 46, 57, 24, 85, 35, 24, 74, 87, 35, 88, 98, 66, 32, 14, 0o5}
func EncryptB64(text string) string {
return base64.StdEncoding.EncodeToString([]byte(text))
}
// DecryptB64 decrypts from base64 string to readable string // EncryptAES method is to encrypt or hide any classified text
func DecryptB64(s string) (string, error) { func EncryptAES(text string) (string, error) {
data, err := base64.StdEncoding.DecodeString(s) key := []byte(envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyEncryptionKey))
block, err := aes.NewCipher(key)
if err != nil { if err != nil {
return "", err return "", err
} }
return string(data), nil plainText := []byte(text)
cfb := cipher.NewCFBEncrypter(block, bytes)
cipherText := make([]byte, len(plainText))
cfb.XORKeyStream(cipherText, plainText)
return EncryptB64(string(cipherText)), nil
} }
// EncryptAES encrypts data using AES algorithm // DecryptAES method is to extract back the encrypted text
func EncryptAES(text []byte) ([]byte, error) { func DecryptAES(text string) (string, error) {
key := []byte(envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyEncryptionKey)) key := []byte(envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyEncryptionKey))
block, err := aes.NewCipher(key)
if err != nil {
return "", err
}
cipherText, err := DecryptB64(text)
if err != nil {
return "", err
}
cfb := cipher.NewCFBDecrypter(block, bytes)
plainText := make([]byte, len(cipherText))
cfb.XORKeyStream(plainText, []byte(cipherText))
return string(plainText), nil
}
// EncryptAESEnv encrypts data using AES algorithm
// kept for the backward compatibility of env data encryption
func EncryptAESEnv(text []byte) ([]byte, error) {
key := []byte(envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyEncryptionKey))
c, err := aes.NewCipher(key) c, err := aes.NewCipher(key)
var res []byte var res []byte
if err != nil { if err != nil {
@@ -62,8 +79,9 @@ func EncryptAES(text []byte) ([]byte, error) {
} }
// DecryptAES decrypts data using AES algorithm // DecryptAES decrypts data using AES algorithm
func DecryptAES(ciphertext []byte) ([]byte, error) { // Kept for the backward compatibility of env data decryption
key := []byte(envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyEncryptionKey)) func DecryptAESEnv(ciphertext []byte) ([]byte, error) {
key := []byte(envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyEncryptionKey))
c, err := aes.NewCipher(key) c, err := aes.NewCipher(key)
var res []byte var res []byte
if err != nil { if err != nil {
@@ -88,39 +106,3 @@ func DecryptAES(ciphertext []byte) ([]byte, error) {
return plaintext, nil return plaintext, nil
} }
// EncryptEnvData is used to encrypt the env data
func EncryptEnvData(data envstore.Store) (string, error) {
jsonBytes, err := json.Marshal(data)
if err != nil {
return "", err
}
envStoreObj := envstore.EnvInMemoryStoreObj.GetEnvStoreClone()
err = json.Unmarshal(jsonBytes, &envStoreObj)
if err != nil {
return "", err
}
configData, err := json.Marshal(envStoreObj)
if err != nil {
return "", err
}
encryptedConfig, err := EncryptAES(configData)
if err != nil {
return "", err
}
return EncryptB64(string(encryptedConfig)), nil
}
// EncryptPassword is used for encrypting password
func EncryptPassword(password string) (string, error) {
pw, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
if err != nil {
return "", err
}
return string(pw), nil
}

17
server/crypto/b64.go Normal file
View File

@@ -0,0 +1,17 @@
package crypto
import "encoding/base64"
// EncryptB64 encrypts data into base64 string
func EncryptB64(text string) string {
return base64.StdEncoding.EncodeToString([]byte(text))
}
// DecryptB64 decrypts from base64 string to readable string
func DecryptB64(s string) (string, error) {
data, err := base64.StdEncoding.DecodeString(s)
if err != nil {
return "", err
}
return string(data), nil
}

114
server/crypto/common.go Normal file
View File

@@ -0,0 +1,114 @@
package crypto
import (
"crypto/x509"
"encoding/json"
"github.com/authorizerdev/authorizer/server/constants"
"github.com/authorizerdev/authorizer/server/envstore"
"golang.org/x/crypto/bcrypt"
"gopkg.in/square/go-jose.v2"
)
// GetPubJWK returns JWK for given keys
func GetPubJWK(algo, keyID string, publicKey interface{}) (string, error) {
jwk := &jose.JSONWebKeySet{
Keys: []jose.JSONWebKey{
{
Algorithm: algo,
Key: publicKey,
Use: "sig",
KeyID: keyID,
Certificates: []*x509.Certificate{},
CertificateThumbprintSHA1: []uint8{},
CertificateThumbprintSHA256: []uint8{},
},
},
}
jwkPublicKey, err := jwk.Keys[0].MarshalJSON()
if err != nil {
return "", err
}
return string(jwkPublicKey), nil
}
// GenerateJWKBasedOnEnv generates JWK based on env
// make sure clientID, jwtType, jwtSecret / public & private key pair is set
// this is called while initializing app / when env is updated
func GenerateJWKBasedOnEnv() (string, error) {
jwk := ""
algo := envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyJwtType)
clientID := envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyClientID)
var err error
// check if jwt secret is provided
if IsHMACA(algo) {
jwk, err = GetPubJWK(algo, clientID, []byte(envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyJwtSecret)))
if err != nil {
return "", err
}
}
if IsRSA(algo) {
publicKeyInstance, err := ParseRsaPublicKeyFromPemStr(envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyJwtPublicKey))
if err != nil {
return "", err
}
jwk, err = GetPubJWK(algo, clientID, publicKeyInstance)
if err != nil {
return "", err
}
}
if IsECDSA(algo) {
publicKeyInstance, err := ParseEcdsaPublicKeyFromPemStr(envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyJwtPublicKey))
if err != nil {
return "", err
}
jwk, err = GetPubJWK(algo, clientID, publicKeyInstance)
if err != nil {
return "", err
}
}
return jwk, nil
}
// EncryptEnvData is used to encrypt the env data
func EncryptEnvData(data envstore.Store) (string, error) {
jsonBytes, err := json.Marshal(data)
if err != nil {
return "", err
}
storeData := envstore.EnvStoreObj.GetEnvStoreClone()
err = json.Unmarshal(jsonBytes, &storeData)
if err != nil {
return "", err
}
configData, err := json.Marshal(storeData)
if err != nil {
return "", err
}
encryptedConfig, err := EncryptAESEnv(configData)
if err != nil {
return "", err
}
return EncryptB64(string(encryptedConfig)), nil
}
// EncryptPassword is used for encrypting password
func EncryptPassword(password string) (string, error) {
pw, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
if err != nil {
return "", err
}
return string(pw), nil
}

154
server/crypto/ecdsa.go Normal file
View File

@@ -0,0 +1,154 @@
package crypto
import (
"crypto/ecdsa"
"crypto/elliptic"
"crypto/rand"
"crypto/x509"
"encoding/pem"
"errors"
)
// NewECDSAKey to generate new ECDSA Key if env is not set
// returns key instance, private key string, public key string, jwk string, error
func NewECDSAKey(algo, keyID string) (*ecdsa.PrivateKey, string, string, string, error) {
var curve elliptic.Curve
switch algo {
case "ES256":
curve = elliptic.P256()
case "ES384":
curve = elliptic.P384()
case "ES512":
curve = elliptic.P521()
default:
return nil, "", "", "", errors.New("Invalid algo")
}
key, err := ecdsa.GenerateKey(curve, rand.Reader)
if err != nil {
return nil, "", "", "", err
}
privateKey, publicKey, err := AsECDSAStr(key, &key.PublicKey)
if err != nil {
return nil, "", "", "", err
}
jwkPublicKey, err := GetPubJWK(algo, keyID, &key.PublicKey)
if err != nil {
return nil, "", "", "", err
}
return key, privateKey, publicKey, string(jwkPublicKey), err
}
// IsECDSA checks if given string is valid ECDSA algo
func IsECDSA(algo string) bool {
switch algo {
case "ES256", "ES384", "ES512":
return true
default:
return false
}
}
// ExportEcdsaPrivateKeyAsPemStr to get ECDSA private key as pem string
func ExportEcdsaPrivateKeyAsPemStr(privkey *ecdsa.PrivateKey) (string, error) {
privkeyBytes, err := x509.MarshalECPrivateKey(privkey)
if err != nil {
return "", err
}
privkeyPem := pem.EncodeToMemory(
&pem.Block{
Type: "ECDSA PRIVATE KEY",
Bytes: privkeyBytes,
},
)
return string(privkeyPem), nil
}
// ExportEcdsaPublicKeyAsPemStr to get ECDSA public key as pem string
func ExportEcdsaPublicKeyAsPemStr(pubkey *ecdsa.PublicKey) (string, error) {
pubkeyBytes, err := x509.MarshalPKIXPublicKey(pubkey)
if err != nil {
return "", err
}
pubkeyPem := pem.EncodeToMemory(
&pem.Block{
Type: "ECDSA PUBLIC KEY",
Bytes: pubkeyBytes,
},
)
return string(pubkeyPem), nil
}
// ParseEcdsaPrivateKeyFromPemStr to parse ECDSA private key from pem string
func ParseEcdsaPrivateKeyFromPemStr(privPEM string) (*ecdsa.PrivateKey, error) {
block, _ := pem.Decode([]byte(privPEM))
if block == nil {
return nil, errors.New("failed to parse PEM block containing the key")
}
priv, err := x509.ParseECPrivateKey(block.Bytes)
if err != nil {
return nil, err
}
return priv, nil
}
// ParseEcdsaPublicKeyFromPemStr to parse ECDSA public key from pem string
func ParseEcdsaPublicKeyFromPemStr(pubPEM string) (*ecdsa.PublicKey, error) {
block, _ := pem.Decode([]byte(pubPEM))
if block == nil {
return nil, errors.New("failed to parse PEM block containing the key")
}
pub, err := x509.ParsePKIXPublicKey(block.Bytes)
if err != nil {
return nil, err
}
switch pub := pub.(type) {
case *ecdsa.PublicKey:
return pub, nil
default:
break // fall through
}
return nil, errors.New("Key type is not ECDSA")
}
// AsECDSAStr returns private, public key string or error
func AsECDSAStr(privateKey *ecdsa.PrivateKey, publicKey *ecdsa.PublicKey) (string, string, error) {
// Export the keys to pem string
privPem, err := ExportEcdsaPrivateKeyAsPemStr(privateKey)
if err != nil {
return "", "", err
}
pubPem, err := ExportEcdsaPublicKeyAsPemStr(publicKey)
if err != nil {
return "", "", err
}
// Import the keys from pem string
privParsed, err := ParseEcdsaPrivateKeyFromPemStr(privPem)
if err != nil {
return "", "", err
}
pubParsed, err := ParseEcdsaPublicKeyFromPemStr(pubPem)
if err != nil {
return "", "", err
}
// Export the newly imported keys
privParsedPem, err := ExportEcdsaPrivateKeyAsPemStr(privParsed)
if err != nil {
return "", "", err
}
pubParsedPem, err := ExportEcdsaPublicKeyAsPemStr(pubParsed)
if err != nil {
return "", "", err
}
return privParsedPem, pubParsedPem, nil
}

26
server/crypto/hmac.go Normal file
View File

@@ -0,0 +1,26 @@
package crypto
import (
"github.com/google/uuid"
)
// NewHMAC key returns new key that can be used to ecnrypt data using HMAC algo
// returns key, string, error
func NewHMACKey(algo, keyID string) (string, string, error) {
key := uuid.New().String()
jwkPublicKey, err := GetPubJWK(algo, keyID, []byte(key))
if err != nil {
return "", "", err
}
return key, string(jwkPublicKey), nil
}
// IsHMACValid checks if given string is valid HMCA algo
func IsHMACA(algo string) bool {
switch algo {
case "HS256", "HS384", "HS512":
return true
default:
return false
}
}

118
server/crypto/rsa.go Normal file
View File

@@ -0,0 +1,118 @@
package crypto
import (
"crypto/rand"
"crypto/rsa"
"crypto/x509"
"encoding/pem"
"errors"
)
// NewRSAKey to generate new RSA Key if env is not set
// returns key instance, private key string, public key string, jwk string, error
func NewRSAKey(algo, keyID string) (*rsa.PrivateKey, string, string, string, error) {
key, err := rsa.GenerateKey(rand.Reader, 2048)
if err != nil {
return nil, "", "", "", err
}
privateKey, publicKey, err := AsRSAStr(key, &key.PublicKey)
if err != nil {
return nil, "", "", "", err
}
jwkPublicKey, err := GetPubJWK(algo, keyID, &key.PublicKey)
if err != nil {
return nil, "", "", "", err
}
return key, privateKey, publicKey, string(jwkPublicKey), err
}
// IsRSA checks if given string is valid RSA algo
func IsRSA(algo string) bool {
switch algo {
case "RS256", "RS384", "RS512":
return true
default:
return false
}
}
// ExportRsaPrivateKeyAsPemStr to get RSA private key as pem string
func ExportRsaPrivateKeyAsPemStr(privkey *rsa.PrivateKey) string {
privkeyBytes := x509.MarshalPKCS1PrivateKey(privkey)
privkeyPem := pem.EncodeToMemory(
&pem.Block{
Type: "RSA PRIVATE KEY",
Bytes: privkeyBytes,
},
)
return string(privkeyPem)
}
// ExportRsaPublicKeyAsPemStr to get RSA public key as pem string
func ExportRsaPublicKeyAsPemStr(pubkey *rsa.PublicKey) string {
pubkeyBytes := x509.MarshalPKCS1PublicKey(pubkey)
pubkeyPem := pem.EncodeToMemory(
&pem.Block{
Type: "RSA PUBLIC KEY",
Bytes: pubkeyBytes,
},
)
return string(pubkeyPem)
}
// ParseRsaPrivateKeyFromPemStr to parse RSA private key from pem string
func ParseRsaPrivateKeyFromPemStr(privPEM string) (*rsa.PrivateKey, error) {
block, _ := pem.Decode([]byte(privPEM))
if block == nil {
return nil, errors.New("failed to parse PEM block containing the key")
}
priv, err := x509.ParsePKCS1PrivateKey(block.Bytes)
if err != nil {
return nil, err
}
return priv, nil
}
// ParseRsaPublicKeyFromPemStr to parse RSA public key from pem string
func ParseRsaPublicKeyFromPemStr(pubPEM string) (*rsa.PublicKey, error) {
block, _ := pem.Decode([]byte(pubPEM))
if block == nil {
return nil, errors.New("failed to parse PEM block containing the key")
}
pub, err := x509.ParsePKCS1PublicKey(block.Bytes)
if err != nil {
return nil, err
}
return pub, nil
}
// AsRSAStr returns private, public key string or error
func AsRSAStr(privateKey *rsa.PrivateKey, publickKey *rsa.PublicKey) (string, string, error) {
// Export the keys to pem string
privPem := ExportRsaPrivateKeyAsPemStr(privateKey)
pubPem := ExportRsaPublicKeyAsPemStr(publickKey)
// Import the keys from pem string
privParsed, err := ParseRsaPrivateKeyFromPemStr(privPem)
if err != nil {
return "", "", err
}
pubParsed, err := ParseRsaPublicKeyFromPemStr(pubPem)
if err != nil {
return "", "", err
}
// Export the newly imported keys
privParsedPem := ExportRsaPrivateKeyAsPemStr(privParsed)
pubParsedPem := ExportRsaPublicKeyAsPemStr(pubParsed)
return privParsedPem, pubParsedPem, nil
}

View File

@@ -1,8 +1,6 @@
package db package db
import ( import (
"log"
"github.com/authorizerdev/authorizer/server/constants" "github.com/authorizerdev/authorizer/server/constants"
"github.com/authorizerdev/authorizer/server/db/providers" "github.com/authorizerdev/authorizer/server/db/providers"
"github.com/authorizerdev/authorizer/server/db/providers/arangodb" "github.com/authorizerdev/authorizer/server/db/providers/arangodb"
@@ -14,31 +12,33 @@ import (
// Provider returns the current database provider // Provider returns the current database provider
var Provider providers.Provider var Provider providers.Provider
func InitDB() { func InitDB() error {
var err error var err error
isSQL := envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyDatabaseType) != constants.DbTypeArangodb && envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyDatabaseType) != constants.DbTypeMongodb isSQL := envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyDatabaseType) != constants.DbTypeArangodb && envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyDatabaseType) != constants.DbTypeMongodb
isArangoDB := envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyDatabaseType) == constants.DbTypeArangodb isArangoDB := envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyDatabaseType) == constants.DbTypeArangodb
isMongoDB := envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyDatabaseType) == constants.DbTypeMongodb isMongoDB := envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyDatabaseType) == constants.DbTypeMongodb
if isSQL { if isSQL {
Provider, err = sql.NewProvider() Provider, err = sql.NewProvider()
if err != nil { if err != nil {
log.Fatal("=> error setting sql provider:", err) return err
} }
} }
if isArangoDB { if isArangoDB {
Provider, err = arangodb.NewProvider() Provider, err = arangodb.NewProvider()
if err != nil { if err != nil {
log.Fatal("=> error setting arangodb provider:", err) return err
} }
} }
if isMongoDB { if isMongoDB {
Provider, err = mongodb.NewProvider() Provider, err = mongodb.NewProvider()
if err != nil { if err != nil {
log.Fatal("=> error setting arangodb provider:", err) return err
} }
} }
return nil
} }

View File

@@ -12,6 +12,7 @@ type VerificationRequest struct {
CreatedAt int64 `json:"created_at" bson:"created_at"` CreatedAt int64 `json:"created_at" bson:"created_at"`
UpdatedAt int64 `json:"updated_at" bson:"updated_at"` UpdatedAt int64 `json:"updated_at" bson:"updated_at"`
Email string `gorm:"uniqueIndex:idx_email_identifier" json:"email" bson:"email"` Email string `gorm:"uniqueIndex:idx_email_identifier" json:"email" bson:"email"`
Nonce string `gorm:"type:char(36)" json:"nonce" bson:"nonce"`
} }
func (v *VerificationRequest) AsAPIVerificationRequest() *model.VerificationRequest { func (v *VerificationRequest) AsAPIVerificationRequest() *model.VerificationRequest {

View File

@@ -24,7 +24,7 @@ type provider struct {
func NewProvider() (*provider, error) { func NewProvider() (*provider, error) {
ctx := context.Background() ctx := context.Background()
conn, err := http.NewConnection(http.ConnectionConfig{ conn, err := http.NewConnection(http.ConnectionConfig{
Endpoints: []string{envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyDatabaseURL)}, Endpoints: []string{envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyDatabaseURL)},
}) })
if err != nil { if err != nil {
return nil, err return nil, err
@@ -39,16 +39,16 @@ func NewProvider() (*provider, error) {
var arangodb driver.Database var arangodb driver.Database
arangodb_exists, err := arangoClient.DatabaseExists(nil, envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyDatabaseName)) arangodb_exists, err := arangoClient.DatabaseExists(nil, envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyDatabaseName))
if arangodb_exists { if arangodb_exists {
log.Println(envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyDatabaseName) + " db exists already") log.Println(envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyDatabaseName) + " db exists already")
arangodb, err = arangoClient.Database(nil, envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyDatabaseName)) arangodb, err = arangoClient.Database(nil, envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyDatabaseName))
if err != nil { if err != nil {
return nil, err return nil, err
} }
} else { } else {
arangodb, err = arangoClient.CreateDatabase(nil, envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyDatabaseName), nil) arangodb, err = arangoClient.CreateDatabase(nil, envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyDatabaseName), nil)
if err != nil { if err != nil {
return nil, err return nil, err
} }

View File

@@ -34,7 +34,6 @@ func (p *provider) DeleteSession(userId string) error {
} }
cursor, err := p.db.Query(nil, query, bindVars) cursor, err := p.db.Query(nil, query, bindVars)
if err != nil { if err != nil {
log.Println("=> error deleting arangodb session:", err)
return err return err
} }
defer cursor.Close() defer cursor.Close()

View File

@@ -23,7 +23,7 @@ func (p *provider) AddUser(user models.User) (models.User, error) {
} }
if user.Roles == "" { if user.Roles == "" {
user.Roles = strings.Join(envstore.EnvInMemoryStoreObj.GetSliceStoreEnvVariable(constants.EnvKeyDefaultRoles), ",") user.Roles = strings.Join(envstore.EnvStoreObj.GetSliceStoreEnvVariable(constants.EnvKeyDefaultRoles), ",")
} }
user.CreatedAt = time.Now().Unix() user.CreatedAt = time.Now().Unix()

View File

@@ -19,7 +19,7 @@ type provider struct {
// NewProvider to initialize mongodb connection // NewProvider to initialize mongodb connection
func NewProvider() (*provider, error) { func NewProvider() (*provider, error) {
mongodbOptions := options.Client().ApplyURI(envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyDatabaseURL)) mongodbOptions := options.Client().ApplyURI(envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyDatabaseURL))
maxWait := time.Duration(5 * time.Second) maxWait := time.Duration(5 * time.Second)
mongodbOptions.ConnectTimeout = &maxWait mongodbOptions.ConnectTimeout = &maxWait
mongoClient, err := mongo.NewClient(mongodbOptions) mongoClient, err := mongo.NewClient(mongodbOptions)
@@ -37,7 +37,7 @@ func NewProvider() (*provider, error) {
return nil, err return nil, err
} }
mongodb := mongoClient.Database(envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyDatabaseName), options.Database()) mongodb := mongoClient.Database(envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyDatabaseName), options.Database())
mongodb.CreateCollection(ctx, models.Collections.User, options.CreateCollection()) mongodb.CreateCollection(ctx, models.Collections.User, options.CreateCollection())
userCollection := mongodb.Collection(models.Collections.User, options.Collection()) userCollection := mongodb.Collection(models.Collections.User, options.Collection())

View File

@@ -21,7 +21,7 @@ func (p *provider) AddUser(user models.User) (models.User, error) {
} }
if user.Roles == "" { if user.Roles == "" {
user.Roles = strings.Join(envstore.EnvInMemoryStoreObj.GetSliceStoreEnvVariable(constants.EnvKeyDefaultRoles), ",") user.Roles = strings.Join(envstore.EnvStoreObj.GetSliceStoreEnvVariable(constants.EnvKeyDefaultRoles), ",")
} }
user.CreatedAt = time.Now().Unix() user.CreatedAt = time.Now().Unix()
user.UpdatedAt = time.Now().Unix() user.UpdatedAt = time.Now().Unix()

View File

@@ -41,26 +41,25 @@ func NewProvider() (*provider, error) {
TablePrefix: models.Prefix, TablePrefix: models.Prefix,
}, },
} }
switch envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyDatabaseType) { switch envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyDatabaseType) {
case constants.DbTypePostgres, constants.DbTypeYugabyte: case constants.DbTypePostgres, constants.DbTypeYugabyte:
sqlDB, err = gorm.Open(postgres.Open(envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyDatabaseURL)), ormConfig) sqlDB, err = gorm.Open(postgres.Open(envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyDatabaseURL)), ormConfig)
break
case constants.DbTypeSqlite: case constants.DbTypeSqlite:
sqlDB, err = gorm.Open(sqlite.Open(envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyDatabaseURL)), ormConfig) sqlDB, err = gorm.Open(sqlite.Open(envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyDatabaseURL)), ormConfig)
break case constants.DbTypeMysql, constants.DbTypeMariaDB:
case constants.DbTypeMysql: sqlDB, err = gorm.Open(mysql.Open(envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyDatabaseURL)), ormConfig)
sqlDB, err = gorm.Open(mysql.Open(envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyDatabaseURL)), ormConfig)
break
case constants.DbTypeSqlserver: case constants.DbTypeSqlserver:
sqlDB, err = gorm.Open(sqlserver.Open(envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyDatabaseURL)), ormConfig) sqlDB, err = gorm.Open(sqlserver.Open(envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyDatabaseURL)), ormConfig)
break
} }
if err != nil { if err != nil {
return nil, err return nil, err
} }
sqlDB.AutoMigrate(&models.User{}, &models.VerificationRequest{}, &models.Session{}, &models.Env{}) err = sqlDB.AutoMigrate(&models.User{}, &models.VerificationRequest{}, &models.Session{}, &models.Env{})
if err != nil {
return nil, err
}
return &provider{ return &provider{
db: sqlDB, db: sqlDB,
}, nil }, nil

View File

@@ -20,7 +20,7 @@ func (p *provider) AddUser(user models.User) (models.User, error) {
} }
if user.Roles == "" { if user.Roles == "" {
user.Roles = strings.Join(envstore.EnvInMemoryStoreObj.GetSliceStoreEnvVariable(constants.EnvKeyDefaultRoles), ",") user.Roles = strings.Join(envstore.EnvStoreObj.GetSliceStoreEnvVariable(constants.EnvKeyDefaultRoles), ",")
} }
user.CreatedAt = time.Now().Unix() user.CreatedAt = time.Now().Unix()

View File

@@ -31,14 +31,18 @@ func addEmailTemplate(a string, b map[string]interface{}, templateName string) s
// SendMail function to send mail // SendMail function to send mail
func SendMail(to []string, Subject, bodyMessage string) error { func SendMail(to []string, Subject, bodyMessage string) error {
// dont trigger email sending in case of test
if envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyEnv) == "test" {
return nil
}
m := gomail.NewMessage() m := gomail.NewMessage()
m.SetHeader("From", envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeySenderEmail)) m.SetHeader("From", envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeySenderEmail))
m.SetHeader("To", to...) m.SetHeader("To", to...)
m.SetHeader("Subject", Subject) m.SetHeader("Subject", Subject)
m.SetBody("text/html", bodyMessage) m.SetBody("text/html", bodyMessage)
port, _ := strconv.Atoi(envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeySmtpPort)) port, _ := strconv.Atoi(envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeySmtpPort))
d := gomail.NewDialer(envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeySmtpHost), port, envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeySmtpUsername), envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeySmtpPassword)) d := gomail.NewDialer(envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeySmtpHost), port, envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeySmtpUsername), envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeySmtpPassword))
if envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyEnv) == "development" { if envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyEnv) == "development" {
d.TLSConfig = &tls.Config{InsecureSkipVerify: true} d.TLSConfig = &tls.Config{InsecureSkipVerify: true}
} }
if err := d.DialAndSend(m); err != nil { if err := d.DialAndSend(m); err != nil {

View File

@@ -7,9 +7,9 @@ import (
// SendForgotPasswordMail to send forgot password email // SendForgotPasswordMail to send forgot password email
func SendForgotPasswordMail(toEmail, token, hostname string) error { func SendForgotPasswordMail(toEmail, token, hostname string) error {
resetPasswordUrl := envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyResetPasswordURL) resetPasswordUrl := envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyResetPasswordURL)
if resetPasswordUrl == "" { if resetPasswordUrl == "" {
envstore.EnvInMemoryStoreObj.UpdateEnvVariable(constants.StringStoreIdentifier, constants.EnvKeyResetPasswordURL, hostname+"/app/reset-password") envstore.EnvStoreObj.UpdateEnvVariable(constants.StringStoreIdentifier, constants.EnvKeyResetPasswordURL, hostname+"/app/reset-password")
} }
// The receiver needs to be in slice as the receive supports multiple receiver // The receiver needs to be in slice as the receive supports multiple receiver
@@ -103,8 +103,8 @@ func SendForgotPasswordMail(toEmail, token, hostname string) error {
` `
data := make(map[string]interface{}, 3) data := make(map[string]interface{}, 3)
data["org_logo"] = envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyOrganizationLogo) data["org_logo"] = envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyOrganizationLogo)
data["org_name"] = envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyOrganizationName) data["org_name"] = envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyOrganizationName)
data["verification_url"] = resetPasswordUrl + "?token=" + token data["verification_url"] = resetPasswordUrl + "?token=" + token
message = addEmailTemplate(message, data, "reset_password_email.tmpl") message = addEmailTemplate(message, data, "reset_password_email.tmpl")

View File

@@ -97,8 +97,8 @@ func SendVerificationMail(toEmail, token, hostname string) error {
</html> </html>
` `
data := make(map[string]interface{}, 3) data := make(map[string]interface{}, 3)
data["org_logo"] = envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyOrganizationLogo) data["org_logo"] = envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyOrganizationLogo)
data["org_name"] = envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyOrganizationName) data["org_name"] = envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyOrganizationName)
data["verification_url"] = hostname + "/verify_email?token=" + token data["verification_url"] = hostname + "/verify_email?token=" + token
message = addEmailTemplate(message, data, "verify_email.tmpl") message = addEmailTemplate(message, data, "verify_email.tmpl")
// bodyMessage := sender.WriteHTMLEmail(Receiver, Subject, message) // bodyMessage := sender.WriteHTMLEmail(Receiver, Subject, message)

284
server/env/env.go vendored
View File

@@ -1,11 +1,13 @@
package env package env
import ( import (
"errors"
"log" "log"
"os" "os"
"strings" "strings"
"github.com/authorizerdev/authorizer/server/constants" "github.com/authorizerdev/authorizer/server/constants"
"github.com/authorizerdev/authorizer/server/crypto"
"github.com/authorizerdev/authorizer/server/envstore" "github.com/authorizerdev/authorizer/server/envstore"
"github.com/authorizerdev/authorizer/server/utils" "github.com/authorizerdev/authorizer/server/utils"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
@@ -13,13 +15,88 @@ import (
"github.com/joho/godotenv" "github.com/joho/godotenv"
) )
// InitRequiredEnv to initialize EnvData and through error if required env are not present
func InitRequiredEnv() error {
envPath := os.Getenv(constants.EnvKeyEnvPath)
if envPath == "" {
envPath = envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyEnvPath)
if envPath == "" {
envPath = `.env`
}
}
if envstore.ARG_ENV_FILE != nil && *envstore.ARG_ENV_FILE != "" {
envPath = *envstore.ARG_ENV_FILE
}
err := godotenv.Load(envPath)
if err != nil {
log.Printf("using OS env instead of %s file", envPath)
}
dbURL := os.Getenv(constants.EnvKeyDatabaseURL)
dbType := os.Getenv(constants.EnvKeyDatabaseType)
dbName := os.Getenv(constants.EnvKeyDatabaseName)
if strings.TrimSpace(dbType) == "" {
if envstore.ARG_DB_TYPE != nil && *envstore.ARG_DB_TYPE != "" {
dbType = strings.TrimSpace(*envstore.ARG_DB_TYPE)
}
if dbType == "" {
return errors.New("invalid database type. DATABASE_TYPE is empty")
}
}
if strings.TrimSpace(dbURL) == "" && envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyDatabaseURL) == "" {
if envstore.ARG_DB_URL != nil && *envstore.ARG_DB_URL != "" {
dbURL = strings.TrimSpace(*envstore.ARG_DB_URL)
}
if dbURL == "" {
return errors.New("invalid database url. DATABASE_URL is required")
}
}
if dbName == "" {
if dbName == "" {
dbName = "authorizer"
}
}
envstore.EnvStoreObj.UpdateEnvVariable(constants.StringStoreIdentifier, constants.EnvKeyEnvPath, envPath)
envstore.EnvStoreObj.UpdateEnvVariable(constants.StringStoreIdentifier, constants.EnvKeyDatabaseURL, dbURL)
envstore.EnvStoreObj.UpdateEnvVariable(constants.StringStoreIdentifier, constants.EnvKeyDatabaseType, dbType)
envstore.EnvStoreObj.UpdateEnvVariable(constants.StringStoreIdentifier, constants.EnvKeyDatabaseName, dbName)
return nil
}
// InitEnv to initialize EnvData and through error if required env are not present // InitEnv to initialize EnvData and through error if required env are not present
func InitEnv() { func InitAllEnv() error {
// get clone of current store envData, err := GetEnvData()
envData := envstore.EnvInMemoryStoreObj.GetEnvStoreClone() if err != nil {
log.Println("No env data found in db, using local clone of env data")
// get clone of current store
envData = envstore.EnvStoreObj.GetEnvStoreClone()
}
clientID := envData.StringEnv[constants.EnvKeyClientID]
// unique client id for each instance
if clientID == "" {
clientID = uuid.New().String()
envData.StringEnv[constants.EnvKeyClientID] = clientID
}
clientSecret := envData.StringEnv[constants.EnvKeyClientSecret]
// unique client id for each instance
if clientSecret == "" {
clientSecret = uuid.New().String()
envData.StringEnv[constants.EnvKeyClientSecret] = clientSecret
}
if envData.StringEnv[constants.EnvKeyEnv] == "" { if envData.StringEnv[constants.EnvKeyEnv] == "" {
envData.StringEnv[constants.EnvKeyEnv] = os.Getenv("ENV") envData.StringEnv[constants.EnvKeyEnv] = os.Getenv(constants.EnvKeyEnv)
if envData.StringEnv[constants.EnvKeyEnv] == "" { if envData.StringEnv[constants.EnvKeyEnv] == "" {
envData.StringEnv[constants.EnvKeyEnv] = "production" envData.StringEnv[constants.EnvKeyEnv] = "production"
} }
@@ -36,146 +113,174 @@ func InitEnv() {
envData.StringEnv[constants.EnvKeyAppURL] = os.Getenv(constants.EnvKeyAppURL) envData.StringEnv[constants.EnvKeyAppURL] = os.Getenv(constants.EnvKeyAppURL)
} }
if envData.StringEnv[constants.EnvKeyEnvPath] == "" {
envData.StringEnv[constants.EnvKeyEnvPath] = `.env`
}
if envstore.ARG_ENV_FILE != nil && *envstore.ARG_ENV_FILE != "" {
envData.StringEnv[constants.EnvKeyEnvPath] = *envstore.ARG_ENV_FILE
}
err := godotenv.Load(envData.StringEnv[constants.EnvKeyEnvPath])
if err != nil {
log.Printf("using OS env instead of %s file", envData.StringEnv[constants.EnvKeyEnvPath])
}
if envData.StringEnv[constants.EnvKeyPort] == "" { if envData.StringEnv[constants.EnvKeyPort] == "" {
envData.StringEnv[constants.EnvKeyPort] = os.Getenv("PORT") envData.StringEnv[constants.EnvKeyPort] = os.Getenv(constants.EnvKeyPort)
if envData.StringEnv[constants.EnvKeyPort] == "" { if envData.StringEnv[constants.EnvKeyPort] == "" {
envData.StringEnv[constants.EnvKeyPort] = "8080" envData.StringEnv[constants.EnvKeyPort] = "8080"
} }
} }
if envData.StringEnv[constants.EnvKeyAdminSecret] == "" { if envData.StringEnv[constants.EnvKeyAdminSecret] == "" {
envData.StringEnv[constants.EnvKeyAdminSecret] = os.Getenv("ADMIN_SECRET") envData.StringEnv[constants.EnvKeyAdminSecret] = os.Getenv(constants.EnvKeyAdminSecret)
}
if envData.StringEnv[constants.EnvKeyDatabaseType] == "" {
envData.StringEnv[constants.EnvKeyDatabaseType] = os.Getenv("DATABASE_TYPE")
if envstore.ARG_DB_TYPE != nil && *envstore.ARG_DB_TYPE != "" {
envData.StringEnv[constants.EnvKeyDatabaseType] = *envstore.ARG_DB_TYPE
}
if envData.StringEnv[constants.EnvKeyDatabaseType] == "" {
panic("DATABASE_TYPE is required")
}
}
if envData.StringEnv[constants.EnvKeyDatabaseURL] == "" {
envData.StringEnv[constants.EnvKeyDatabaseURL] = os.Getenv("DATABASE_URL")
if envstore.ARG_DB_URL != nil && *envstore.ARG_DB_URL != "" {
envData.StringEnv[constants.EnvKeyDatabaseURL] = *envstore.ARG_DB_URL
}
if envData.StringEnv[constants.EnvKeyDatabaseURL] == "" {
panic("DATABASE_URL is required")
}
}
if envData.StringEnv[constants.EnvKeyDatabaseName] == "" {
envData.StringEnv[constants.EnvKeyDatabaseName] = os.Getenv("DATABASE_NAME")
if envData.StringEnv[constants.EnvKeyDatabaseName] == "" {
envData.StringEnv[constants.EnvKeyDatabaseName] = "authorizer"
}
} }
if envData.StringEnv[constants.EnvKeySmtpHost] == "" { if envData.StringEnv[constants.EnvKeySmtpHost] == "" {
envData.StringEnv[constants.EnvKeySmtpHost] = os.Getenv("SMTP_HOST") envData.StringEnv[constants.EnvKeySmtpHost] = os.Getenv(constants.EnvKeySmtpHost)
} }
if envData.StringEnv[constants.EnvKeySmtpPort] == "" { if envData.StringEnv[constants.EnvKeySmtpPort] == "" {
envData.StringEnv[constants.EnvKeySmtpPort] = os.Getenv("SMTP_PORT") envData.StringEnv[constants.EnvKeySmtpPort] = os.Getenv(constants.EnvKeySmtpPort)
} }
if envData.StringEnv[constants.EnvKeySmtpUsername] == "" { if envData.StringEnv[constants.EnvKeySmtpUsername] == "" {
envData.StringEnv[constants.EnvKeySmtpUsername] = os.Getenv("SMTP_USERNAME") envData.StringEnv[constants.EnvKeySmtpUsername] = os.Getenv(constants.EnvKeySmtpUsername)
} }
if envData.StringEnv[constants.EnvKeySmtpPassword] == "" { if envData.StringEnv[constants.EnvKeySmtpPassword] == "" {
envData.StringEnv[constants.EnvKeySmtpPassword] = os.Getenv("SMTP_PASSWORD") envData.StringEnv[constants.EnvKeySmtpPassword] = os.Getenv(constants.EnvKeySmtpPassword)
} }
if envData.StringEnv[constants.EnvKeySenderEmail] == "" { if envData.StringEnv[constants.EnvKeySenderEmail] == "" {
envData.StringEnv[constants.EnvKeySenderEmail] = os.Getenv("SENDER_EMAIL") envData.StringEnv[constants.EnvKeySenderEmail] = os.Getenv(constants.EnvKeySenderEmail)
} }
if envData.StringEnv[constants.EnvKeyJwtSecret] == "" { algo := envData.StringEnv[constants.EnvKeyJwtType]
envData.StringEnv[constants.EnvKeyJwtSecret] = os.Getenv("JWT_SECRET") if algo == "" {
if envData.StringEnv[constants.EnvKeyJwtSecret] == "" { envData.StringEnv[constants.EnvKeyJwtType] = os.Getenv(constants.EnvKeyJwtType)
envData.StringEnv[constants.EnvKeyJwtSecret] = uuid.New().String()
}
}
if envData.StringEnv[constants.EnvKeyJwtType] == "" {
envData.StringEnv[constants.EnvKeyJwtType] = os.Getenv("JWT_TYPE")
if envData.StringEnv[constants.EnvKeyJwtType] == "" { if envData.StringEnv[constants.EnvKeyJwtType] == "" {
envData.StringEnv[constants.EnvKeyJwtType] = "HS256" envData.StringEnv[constants.EnvKeyJwtType] = "RS256"
algo = envData.StringEnv[constants.EnvKeyJwtType]
} else {
algo = envData.StringEnv[constants.EnvKeyJwtType]
if !crypto.IsHMACA(algo) && !crypto.IsRSA(algo) && !crypto.IsECDSA(algo) {
return errors.New("invalid JWT_TYPE")
}
} }
} }
if crypto.IsHMACA(algo) {
if envData.StringEnv[constants.EnvKeyJwtSecret] == "" {
envData.StringEnv[constants.EnvKeyJwtSecret] = os.Getenv(constants.EnvKeyJwtSecret)
if envData.StringEnv[constants.EnvKeyJwtSecret] == "" {
envData.StringEnv[constants.EnvKeyJwtSecret], _, err = crypto.NewHMACKey(algo, clientID)
if err != nil {
return err
}
}
}
}
if crypto.IsRSA(algo) || crypto.IsECDSA(algo) {
privateKey, publicKey := "", ""
if envData.StringEnv[constants.EnvKeyJwtPrivateKey] == "" {
privateKey = os.Getenv(constants.EnvKeyJwtPrivateKey)
}
if envData.StringEnv[constants.EnvKeyJwtPublicKey] == "" {
publicKey = os.Getenv(constants.EnvKeyJwtPublicKey)
}
// if algo is RSA / ECDSA, then we need to have both private and public key
// if either of them is not present generate new keys
if privateKey == "" || publicKey == "" {
if crypto.IsRSA(algo) {
_, privateKey, publicKey, _, err = crypto.NewRSAKey(algo, clientID)
if err != nil {
return err
}
} else if crypto.IsECDSA(algo) {
_, privateKey, publicKey, _, err = crypto.NewECDSAKey(algo, clientID)
if err != nil {
return err
}
}
} else {
// parse keys to make sure they are valid
if crypto.IsRSA(algo) {
_, err = crypto.ParseRsaPrivateKeyFromPemStr(privateKey)
if err != nil {
return err
}
_, err := crypto.ParseRsaPublicKeyFromPemStr(publicKey)
if err != nil {
return err
}
} else if crypto.IsECDSA(algo) {
_, err = crypto.ParseEcdsaPrivateKeyFromPemStr(privateKey)
if err != nil {
return err
}
_, err := crypto.ParseEcdsaPublicKeyFromPemStr(publicKey)
if err != nil {
return err
}
}
}
envData.StringEnv[constants.EnvKeyJwtPrivateKey] = privateKey
envData.StringEnv[constants.EnvKeyJwtPublicKey] = publicKey
}
if envData.StringEnv[constants.EnvKeyJwtRoleClaim] == "" { if envData.StringEnv[constants.EnvKeyJwtRoleClaim] == "" {
envData.StringEnv[constants.EnvKeyJwtRoleClaim] = os.Getenv("JWT_ROLE_CLAIM") envData.StringEnv[constants.EnvKeyJwtRoleClaim] = os.Getenv(constants.EnvKeyJwtRoleClaim)
if envData.StringEnv[constants.EnvKeyJwtRoleClaim] == "" { if envData.StringEnv[constants.EnvKeyJwtRoleClaim] == "" {
envData.StringEnv[constants.EnvKeyJwtRoleClaim] = "role" envData.StringEnv[constants.EnvKeyJwtRoleClaim] = "role"
} }
} }
if envData.StringEnv[constants.EnvKeyCustomAccessTokenScript] == "" {
envData.StringEnv[constants.EnvKeyCustomAccessTokenScript] = os.Getenv(constants.EnvKeyCustomAccessTokenScript)
}
if envData.StringEnv[constants.EnvKeyRedisURL] == "" { if envData.StringEnv[constants.EnvKeyRedisURL] == "" {
envData.StringEnv[constants.EnvKeyRedisURL] = os.Getenv("REDIS_URL") envData.StringEnv[constants.EnvKeyRedisURL] = os.Getenv(constants.EnvKeyRedisURL)
} }
if envData.StringEnv[constants.EnvKeyCookieName] == "" { if envData.StringEnv[constants.EnvKeyCookieName] == "" {
envData.StringEnv[constants.EnvKeyCookieName] = os.Getenv("COOKIE_NAME") envData.StringEnv[constants.EnvKeyCookieName] = os.Getenv(constants.EnvKeyCookieName)
if envData.StringEnv[constants.EnvKeyCookieName] == "" { if envData.StringEnv[constants.EnvKeyCookieName] == "" {
envData.StringEnv[constants.EnvKeyCookieName] = "authorizer" envData.StringEnv[constants.EnvKeyCookieName] = "authorizer"
} }
} }
if envData.StringEnv[constants.EnvKeyGoogleClientID] == "" { if envData.StringEnv[constants.EnvKeyGoogleClientID] == "" {
envData.StringEnv[constants.EnvKeyGoogleClientID] = os.Getenv("GOOGLE_CLIENT_ID") envData.StringEnv[constants.EnvKeyGoogleClientID] = os.Getenv(constants.EnvKeyGoogleClientID)
} }
if envData.StringEnv[constants.EnvKeyGoogleClientSecret] == "" { if envData.StringEnv[constants.EnvKeyGoogleClientSecret] == "" {
envData.StringEnv[constants.EnvKeyGoogleClientSecret] = os.Getenv("GOOGLE_CLIENT_SECRET") envData.StringEnv[constants.EnvKeyGoogleClientSecret] = os.Getenv(constants.EnvKeyGoogleClientSecret)
} }
if envData.StringEnv[constants.EnvKeyGithubClientID] == "" { if envData.StringEnv[constants.EnvKeyGithubClientID] == "" {
envData.StringEnv[constants.EnvKeyGithubClientID] = os.Getenv("GITHUB_CLIENT_ID") envData.StringEnv[constants.EnvKeyGithubClientID] = os.Getenv(constants.EnvKeyGithubClientID)
} }
if envData.StringEnv[constants.EnvKeyGithubClientSecret] == "" { if envData.StringEnv[constants.EnvKeyGithubClientSecret] == "" {
envData.StringEnv[constants.EnvKeyGithubClientSecret] = os.Getenv("GITHUB_CLIENT_SECRET") envData.StringEnv[constants.EnvKeyGithubClientSecret] = os.Getenv(constants.EnvKeyGithubClientSecret)
} }
if envData.StringEnv[constants.EnvKeyFacebookClientID] == "" { if envData.StringEnv[constants.EnvKeyFacebookClientID] == "" {
envData.StringEnv[constants.EnvKeyFacebookClientID] = os.Getenv("FACEBOOK_CLIENT_ID") envData.StringEnv[constants.EnvKeyFacebookClientID] = os.Getenv(constants.EnvKeyFacebookClientID)
} }
if envData.StringEnv[constants.EnvKeyFacebookClientSecret] == "" { if envData.StringEnv[constants.EnvKeyFacebookClientSecret] == "" {
envData.StringEnv[constants.EnvKeyFacebookClientSecret] = os.Getenv("FACEBOOK_CLIENT_SECRET") envData.StringEnv[constants.EnvKeyFacebookClientSecret] = os.Getenv(constants.EnvKeyFacebookClientSecret)
} }
if envData.StringEnv[constants.EnvKeyResetPasswordURL] == "" { if envData.StringEnv[constants.EnvKeyResetPasswordURL] == "" {
envData.StringEnv[constants.EnvKeyResetPasswordURL] = strings.TrimPrefix(os.Getenv("RESET_PASSWORD_URL"), "/") envData.StringEnv[constants.EnvKeyResetPasswordURL] = strings.TrimPrefix(os.Getenv(constants.EnvKeyResetPasswordURL), "/")
} }
envData.BoolEnv[constants.EnvKeyDisableBasicAuthentication] = os.Getenv("DISABLE_BASIC_AUTHENTICATION") == "true" envData.BoolEnv[constants.EnvKeyDisableBasicAuthentication] = os.Getenv(constants.EnvKeyDisableBasicAuthentication) == "true"
envData.BoolEnv[constants.EnvKeyDisableEmailVerification] = os.Getenv("DISABLE_EMAIL_VERIFICATION") == "true" envData.BoolEnv[constants.EnvKeyDisableEmailVerification] = os.Getenv(constants.EnvKeyDisableEmailVerification) == "true"
envData.BoolEnv[constants.EnvKeyDisableMagicLinkLogin] = os.Getenv("DISABLE_MAGIC_LINK_LOGIN") == "true" envData.BoolEnv[constants.EnvKeyDisableMagicLinkLogin] = os.Getenv(constants.EnvKeyDisableMagicLinkLogin) == "true"
envData.BoolEnv[constants.EnvKeyDisableLoginPage] = os.Getenv("DISABLE_LOGIN_PAGE") == "true" envData.BoolEnv[constants.EnvKeyDisableLoginPage] = os.Getenv(constants.EnvKeyDisableLoginPage) == "true"
// no need to add nil check as its already done above // no need to add nil check as its already done above
if envData.StringEnv[constants.EnvKeySmtpHost] == "" || envData.StringEnv[constants.EnvKeySmtpUsername] == "" || envData.StringEnv[constants.EnvKeySmtpPassword] == "" || envData.StringEnv[constants.EnvKeySenderEmail] == "" && envData.StringEnv[constants.EnvKeySmtpPort] == "" { if envData.StringEnv[constants.EnvKeySmtpHost] == "" || envData.StringEnv[constants.EnvKeySmtpUsername] == "" || envData.StringEnv[constants.EnvKeySmtpPassword] == "" || envData.StringEnv[constants.EnvKeySenderEmail] == "" && envData.StringEnv[constants.EnvKeySmtpPort] == "" {
@@ -187,7 +292,7 @@ func InitEnv() {
envData.BoolEnv[constants.EnvKeyDisableMagicLinkLogin] = true envData.BoolEnv[constants.EnvKeyDisableMagicLinkLogin] = true
} }
allowedOriginsSplit := strings.Split(os.Getenv("ALLOWED_ORIGINS"), ",") allowedOriginsSplit := strings.Split(os.Getenv(constants.EnvKeyAllowedOrigins), ",")
allowedOrigins := []string{} allowedOrigins := []string{}
hasWildCard := false hasWildCard := false
@@ -215,14 +320,14 @@ func InitEnv() {
envData.SliceEnv[constants.EnvKeyAllowedOrigins] = allowedOrigins envData.SliceEnv[constants.EnvKeyAllowedOrigins] = allowedOrigins
rolesEnv := strings.TrimSpace(os.Getenv("ROLES")) rolesEnv := strings.TrimSpace(os.Getenv(constants.EnvKeyRoles))
rolesSplit := strings.Split(rolesEnv, ",") rolesSplit := strings.Split(rolesEnv, ",")
roles := []string{} roles := []string{}
if len(rolesEnv) == 0 { if len(rolesEnv) == 0 {
roles = []string{"user"} roles = []string{"user"}
} }
defaultRolesEnv := strings.TrimSpace(os.Getenv("DEFAULT_ROLES")) defaultRolesEnv := strings.TrimSpace(os.Getenv(constants.EnvKeyDefaultRoles))
defaultRoleSplit := strings.Split(defaultRolesEnv, ",") defaultRoleSplit := strings.Split(defaultRolesEnv, ",")
defaultRoles := []string{} defaultRoles := []string{}
@@ -230,7 +335,7 @@ func InitEnv() {
defaultRoles = []string{"user"} defaultRoles = []string{"user"}
} }
protectedRolesEnv := strings.TrimSpace(os.Getenv("PROTECTED_ROLES")) protectedRolesEnv := strings.TrimSpace(os.Getenv(constants.EnvKeyProtectedRoles))
protectedRolesSplit := strings.Split(protectedRolesEnv, ",") protectedRolesSplit := strings.Split(protectedRolesEnv, ",")
protectedRoles := []string{} protectedRoles := []string{}
@@ -252,20 +357,21 @@ func InitEnv() {
} }
if len(roles) > 0 && len(defaultRoles) == 0 && len(defaultRolesEnv) > 0 { if len(roles) > 0 && len(defaultRoles) == 0 && len(defaultRolesEnv) > 0 {
panic(`Invalid DEFAULT_ROLE environment variable. It can be one from give ROLES environment variable value`) return errors.New(`invalid DEFAULT_ROLE environment variable. It can be one from give ROLES environment variable value`)
} }
envData.SliceEnv[constants.EnvKeyRoles] = roles envData.SliceEnv[constants.EnvKeyRoles] = roles
envData.SliceEnv[constants.EnvKeyDefaultRoles] = defaultRoles envData.SliceEnv[constants.EnvKeyDefaultRoles] = defaultRoles
envData.SliceEnv[constants.EnvKeyProtectedRoles] = protectedRoles envData.SliceEnv[constants.EnvKeyProtectedRoles] = protectedRoles
if os.Getenv("ORGANIZATION_NAME") != "" { if os.Getenv(constants.EnvKeyOrganizationName) != "" {
envData.StringEnv[constants.EnvKeyOrganizationName] = os.Getenv("ORGANIZATION_NAME") envData.StringEnv[constants.EnvKeyOrganizationName] = os.Getenv(constants.EnvKeyOrganizationName)
} }
if os.Getenv("ORGANIZATION_LOGO") != "" { if os.Getenv(constants.EnvKeyOrganizationLogo) != "" {
envData.StringEnv[constants.EnvKeyOrganizationLogo] = os.Getenv("ORGANIZATION_LOGO") envData.StringEnv[constants.EnvKeyOrganizationLogo] = os.Getenv(constants.EnvKeyOrganizationLogo)
} }
envstore.EnvInMemoryStoreObj.UpdateEnvStore(envData) envstore.EnvStoreObj.UpdateEnvStore(envData)
return nil
} }

View File

@@ -7,14 +7,51 @@ import (
"strconv" "strconv"
"strings" "strings"
"github.com/google/uuid"
"github.com/authorizerdev/authorizer/server/constants" "github.com/authorizerdev/authorizer/server/constants"
"github.com/authorizerdev/authorizer/server/crypto"
"github.com/authorizerdev/authorizer/server/db" "github.com/authorizerdev/authorizer/server/db"
"github.com/authorizerdev/authorizer/server/db/models" "github.com/authorizerdev/authorizer/server/db/models"
"github.com/authorizerdev/authorizer/server/envstore" "github.com/authorizerdev/authorizer/server/envstore"
"github.com/authorizerdev/authorizer/server/utils" "github.com/authorizerdev/authorizer/server/utils"
"github.com/google/uuid"
) )
// GetEnvData returns the env data from database
func GetEnvData() (envstore.Store, error) {
var result envstore.Store
env, err := db.Provider.GetEnv()
// config not found in db
if err != nil {
return result, err
}
encryptionKey := env.Hash
decryptedEncryptionKey, err := crypto.DecryptB64(encryptionKey)
if err != nil {
return result, err
}
envstore.EnvStoreObj.UpdateEnvVariable(constants.StringStoreIdentifier, constants.EnvKeyEncryptionKey, decryptedEncryptionKey)
b64DecryptedConfig, err := crypto.DecryptB64(env.EnvData)
if err != nil {
return result, err
}
decryptedConfigs, err := crypto.DecryptAESEnv([]byte(b64DecryptedConfig))
if err != nil {
return result, err
}
err = json.Unmarshal(decryptedConfigs, &result)
if err != nil {
return result, err
}
return result, err
}
// PersistEnv persists the environment variables to the database // PersistEnv persists the environment variables to the database
func PersistEnv() error { func PersistEnv() error {
env, err := db.Provider.GetEnv() env, err := db.Provider.GetEnv()
@@ -22,45 +59,40 @@ func PersistEnv() error {
if err != nil { if err != nil {
// AES encryption needs 32 bit key only, so we chop off last 4 characters from 36 bit uuid // AES encryption needs 32 bit key only, so we chop off last 4 characters from 36 bit uuid
hash := uuid.New().String()[:36-4] hash := uuid.New().String()[:36-4]
envstore.EnvInMemoryStoreObj.UpdateEnvVariable(constants.StringStoreIdentifier, constants.EnvKeyEncryptionKey, hash) envstore.EnvStoreObj.UpdateEnvVariable(constants.StringStoreIdentifier, constants.EnvKeyEncryptionKey, hash)
encodedHash := utils.EncryptB64(hash) encodedHash := crypto.EncryptB64(hash)
encryptedConfig, err := utils.EncryptEnvData(envstore.EnvInMemoryStoreObj.GetEnvStoreClone()) encryptedConfig, err := crypto.EncryptEnvData(envstore.EnvStoreObj.GetEnvStoreClone())
if err != nil { if err != nil {
return err return err
} }
// configData, err := json.Marshal()
// if err != nil {
// return err
// }
// encryptedConfig, err := utils.EncryptAES(configData)
// if err != nil {
// return err
// }
env = models.Env{ env = models.Env{
Hash: encodedHash, Hash: encodedHash,
EnvData: encryptedConfig, EnvData: encryptedConfig,
} }
db.Provider.AddEnv(env) env, err = db.Provider.AddEnv(env)
if err != nil {
return err
}
} else { } else {
// decrypt the config data from db // decrypt the config data from db
// decryption can be done using the hash stored in db // decryption can be done using the hash stored in db
encryptionKey := env.Hash encryptionKey := env.Hash
decryptedEncryptionKey, err := utils.DecryptB64(encryptionKey) decryptedEncryptionKey, err := crypto.DecryptB64(encryptionKey)
if err != nil { if err != nil {
return err return err
} }
envstore.EnvInMemoryStoreObj.UpdateEnvVariable(constants.StringStoreIdentifier, constants.EnvKeyEncryptionKey, decryptedEncryptionKey) envstore.EnvStoreObj.UpdateEnvVariable(constants.StringStoreIdentifier, constants.EnvKeyEncryptionKey, decryptedEncryptionKey)
b64DecryptedConfig, err := utils.DecryptB64(env.EnvData)
b64DecryptedConfig, err := crypto.DecryptB64(env.EnvData)
if err != nil { if err != nil {
return err return err
} }
decryptedConfigs, err := utils.DecryptAES([]byte(b64DecryptedConfig)) decryptedConfigs, err := crypto.DecryptAESEnv([]byte(b64DecryptedConfig))
if err != nil { if err != nil {
return err return err
} }
@@ -79,6 +111,7 @@ func PersistEnv() error {
hasChanged := false hasChanged := false
for key, value := range storeData.StringEnv { for key, value := range storeData.StringEnv {
// don't override unexposed envs
if key != constants.EnvKeyEncryptionKey { if key != constants.EnvKeyEncryptionKey {
// check only for derivative keys // check only for derivative keys
// No need to check for ENCRYPTION_KEY which special key we use for encrypting config data // No need to check for ENCRYPTION_KEY which special key we use for encrypting config data
@@ -132,10 +165,16 @@ func PersistEnv() error {
hasChanged = true hasChanged = true
} }
} }
envstore.EnvStoreObj.UpdateEnvStore(storeData)
jwk, err := crypto.GenerateJWKBasedOnEnv()
if err != nil {
return err
}
// updating jwk
envstore.EnvStoreObj.UpdateEnvVariable(constants.StringStoreIdentifier, constants.EnvKeyJWK, jwk)
envstore.EnvInMemoryStoreObj.UpdateEnvStore(storeData)
if hasChanged { if hasChanged {
encryptedConfig, err := utils.EncryptEnvData(storeData) encryptedConfig, err := crypto.EncryptEnvData(storeData)
if err != nil { if err != nil {
return err return err
} }
@@ -147,7 +186,6 @@ func PersistEnv() error {
return err return err
} }
} }
} }
return nil return nil

View File

@@ -22,14 +22,13 @@ type Store struct {
SliceEnv map[string][]string `json:"slice_env"` SliceEnv map[string][]string `json:"slice_env"`
} }
// EnvInMemoryStore struct // EnvStore struct
type EnvInMemoryStore struct { type EnvStore struct {
mutex sync.Mutex mutex sync.Mutex
store *Store store *Store
} }
// EnvInMemoryStoreObj global variable for EnvInMemoryStore var defaultStore = &EnvStore{
var EnvInMemoryStoreObj = &EnvInMemoryStore{
store: &Store{ store: &Store{
StringEnv: map[string]string{ StringEnv: map[string]string{
constants.EnvKeyAdminCookieName: "authorizer-admin", constants.EnvKeyAdminCookieName: "authorizer-admin",
@@ -47,8 +46,11 @@ var EnvInMemoryStoreObj = &EnvInMemoryStore{
}, },
} }
// EnvStoreObj.GetBoolStoreEnvVariable global variable for EnvStore
var EnvStoreObj = defaultStore
// UpdateEnvStore to update the whole env store object // UpdateEnvStore to update the whole env store object
func (e *EnvInMemoryStore) UpdateEnvStore(store Store) { func (e *EnvStore) UpdateEnvStore(store Store) {
e.mutex.Lock() e.mutex.Lock()
defer e.mutex.Unlock() defer e.mutex.Unlock()
// just override the keys + new keys // just override the keys + new keys
@@ -67,7 +69,7 @@ func (e *EnvInMemoryStore) UpdateEnvStore(store Store) {
} }
// UpdateEnvVariable to update the particular env variable // UpdateEnvVariable to update the particular env variable
func (e *EnvInMemoryStore) UpdateEnvVariable(storeIdentifier, key string, value interface{}) { func (e *EnvStore) UpdateEnvVariable(storeIdentifier, key string, value interface{}) {
e.mutex.Lock() e.mutex.Lock()
defer e.mutex.Unlock() defer e.mutex.Unlock()
switch storeIdentifier { switch storeIdentifier {
@@ -81,31 +83,37 @@ func (e *EnvInMemoryStore) UpdateEnvVariable(storeIdentifier, key string, value
} }
// GetStringStoreEnvVariable to get the env variable from string store object // GetStringStoreEnvVariable to get the env variable from string store object
func (e *EnvInMemoryStore) GetStringStoreEnvVariable(key string) string { func (e *EnvStore) GetStringStoreEnvVariable(key string) string {
// e.mutex.Lock() // e.mutex.Lock()
// defer e.mutex.Unlock() // defer e.mutex.Unlock()
return e.store.StringEnv[key] return e.store.StringEnv[key]
} }
// GetBoolStoreEnvVariable to get the env variable from bool store object // GetBoolStoreEnvVariable to get the env variable from bool store object
func (e *EnvInMemoryStore) GetBoolStoreEnvVariable(key string) bool { func (e *EnvStore) GetBoolStoreEnvVariable(key string) bool {
// e.mutex.Lock() // e.mutex.Lock()
// defer e.mutex.Unlock() // defer e.mutex.Unlock()
return e.store.BoolEnv[key] return e.store.BoolEnv[key]
} }
// GetSliceStoreEnvVariable to get the env variable from slice store object // GetSliceStoreEnvVariable to get the env variable from slice store object
func (e *EnvInMemoryStore) GetSliceStoreEnvVariable(key string) []string { func (e *EnvStore) GetSliceStoreEnvVariable(key string) []string {
// e.mutex.Lock() // e.mutex.Lock()
// defer e.mutex.Unlock() // defer e.mutex.Unlock()
return e.store.SliceEnv[key] return e.store.SliceEnv[key]
} }
// GetEnvStoreClone to get clone of current env store object // GetEnvStoreClone to get clone of current env store object
func (e *EnvInMemoryStore) GetEnvStoreClone() Store { func (e *EnvStore) GetEnvStoreClone() Store {
e.mutex.Lock() e.mutex.Lock()
defer e.mutex.Unlock() defer e.mutex.Unlock()
result := *e.store result := *e.store
return result return result
} }
func (e *EnvStore) ResetStore() {
e.mutex.Lock()
defer e.mutex.Unlock()
e.store = defaultStore.store
}

View File

@@ -30,6 +30,7 @@ require (
google.golang.org/protobuf v1.27.1 // indirect google.golang.org/protobuf v1.27.1 // indirect
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect
gopkg.in/mail.v2 v2.3.1 gopkg.in/mail.v2 v2.3.1
gopkg.in/square/go-jose.v2 v2.6.0
gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect
gorm.io/driver/mysql v1.2.1 gorm.io/driver/mysql v1.2.1
gorm.io/driver/postgres v1.2.3 gorm.io/driver/postgres v1.2.3

View File

@@ -682,8 +682,9 @@ gopkg.in/mail.v2 v2.3.1/go.mod h1:htwXN1Qh09vZJ1NVKxQqHPBaCBbzKhp5GzuJEA4VJWw=
gopkg.in/readline.v1 v1.0.0-20160726135117-62c6fe619375/go.mod h1:lNEQeAhU009zbRxng+XOj5ITVgY24WcbNnQopyfKoYQ= gopkg.in/readline.v1 v1.0.0-20160726135117-62c6fe619375/go.mod h1:lNEQeAhU009zbRxng+XOj5ITVgY24WcbNnQopyfKoYQ=
gopkg.in/sourcemap.v1 v1.0.5 h1:inv58fC9f9J3TK2Y2R1NPntXEn3/wjWHkonhIUODNTI= gopkg.in/sourcemap.v1 v1.0.5 h1:inv58fC9f9J3TK2Y2R1NPntXEn3/wjWHkonhIUODNTI=
gopkg.in/sourcemap.v1 v1.0.5/go.mod h1:2RlvNNSMglmRrcvhfuzp4hQHwOtjxlbjX7UPY/GXb78= gopkg.in/sourcemap.v1 v1.0.5/go.mod h1:2RlvNNSMglmRrcvhfuzp4hQHwOtjxlbjX7UPY/GXb78=
gopkg.in/square/go-jose.v2 v2.5.1 h1:7odma5RETjNHWJnR32wx8t+Io4djHE1PqxCFx3iiZ2w=
gopkg.in/square/go-jose.v2 v2.5.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= gopkg.in/square/go-jose.v2 v2.5.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI=
gopkg.in/square/go-jose.v2 v2.6.0 h1:NGk74WTnPKBNUhNzQX7PYcTLUjoq7mzKk2OKbvwk2iI=
gopkg.in/square/go-jose.v2 v2.6.0/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=

View File

@@ -44,16 +44,20 @@ type DirectiveRoot struct {
type ComplexityRoot struct { type ComplexityRoot struct {
AuthResponse struct { AuthResponse struct {
AccessToken func(childComplexity int) int AccessToken func(childComplexity int) int
ExpiresAt func(childComplexity int) int ExpiresIn func(childComplexity int) int
Message func(childComplexity int) int IDToken func(childComplexity int) int
User func(childComplexity int) int Message func(childComplexity int) int
RefreshToken func(childComplexity int) int
User func(childComplexity int) int
} }
Env struct { Env struct {
AdminSecret func(childComplexity int) int AdminSecret func(childComplexity int) int
AllowedOrigins func(childComplexity int) int AllowedOrigins func(childComplexity int) int
AppURL func(childComplexity int) int AppURL func(childComplexity int) int
ClientID func(childComplexity int) int
ClientSecret func(childComplexity int) int
CookieName func(childComplexity int) int CookieName func(childComplexity int) int
CustomAccessTokenScript func(childComplexity int) int CustomAccessTokenScript func(childComplexity int) int
DatabaseName func(childComplexity int) int DatabaseName func(childComplexity int) int
@@ -70,6 +74,8 @@ type ComplexityRoot struct {
GithubClientSecret func(childComplexity int) int GithubClientSecret func(childComplexity int) int
GoogleClientID func(childComplexity int) int GoogleClientID func(childComplexity int) int
GoogleClientSecret func(childComplexity int) int GoogleClientSecret func(childComplexity int) int
JwtPrivateKey func(childComplexity int) int
JwtPublicKey func(childComplexity int) int
JwtRoleClaim func(childComplexity int) int JwtRoleClaim func(childComplexity int) int
JwtSecret func(childComplexity int) int JwtSecret func(childComplexity int) int
JwtType func(childComplexity int) int JwtType func(childComplexity int) int
@@ -92,6 +98,7 @@ type ComplexityRoot struct {
} }
Meta struct { Meta struct {
ClientID func(childComplexity int) int
IsBasicAuthenticationEnabled func(childComplexity int) int IsBasicAuthenticationEnabled func(childComplexity int) int
IsEmailVerificationEnabled func(childComplexity int) int IsEmailVerificationEnabled func(childComplexity int) int
IsFacebookLoginEnabled func(childComplexity int) int IsFacebookLoginEnabled func(childComplexity int) int
@@ -129,7 +136,6 @@ type ComplexityRoot struct {
Query struct { Query struct {
AdminSession func(childComplexity int) int AdminSession func(childComplexity int) int
Env func(childComplexity int) int Env func(childComplexity int) int
IsValidJwt func(childComplexity int, params *model.IsValidJWTQueryInput) int
Meta func(childComplexity int) int Meta func(childComplexity int) int
Profile func(childComplexity int) int Profile func(childComplexity int) int
Session func(childComplexity int, params *model.SessionQueryInput) int Session func(childComplexity int, params *model.SessionQueryInput) int
@@ -166,11 +172,6 @@ type ComplexityRoot struct {
Users func(childComplexity int) int Users func(childComplexity int) int
} }
ValidJWTResponse struct {
Message func(childComplexity int) int
Valid func(childComplexity int) int
}
VerificationRequest struct { VerificationRequest struct {
CreatedAt func(childComplexity int) int CreatedAt func(childComplexity int) int
Email func(childComplexity int) int Email func(childComplexity int) int
@@ -207,7 +208,6 @@ type MutationResolver interface {
type QueryResolver interface { type QueryResolver interface {
Meta(ctx context.Context) (*model.Meta, error) Meta(ctx context.Context) (*model.Meta, error)
Session(ctx context.Context, params *model.SessionQueryInput) (*model.AuthResponse, error) Session(ctx context.Context, params *model.SessionQueryInput) (*model.AuthResponse, error)
IsValidJwt(ctx context.Context, params *model.IsValidJWTQueryInput) (*model.ValidJWTResponse, error)
Profile(ctx context.Context) (*model.User, error) Profile(ctx context.Context) (*model.User, error)
Users(ctx context.Context, params *model.PaginatedInput) (*model.Users, error) Users(ctx context.Context, params *model.PaginatedInput) (*model.Users, error)
VerificationRequests(ctx context.Context, params *model.PaginatedInput) (*model.VerificationRequests, error) VerificationRequests(ctx context.Context, params *model.PaginatedInput) (*model.VerificationRequests, error)
@@ -237,12 +237,19 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in
return e.complexity.AuthResponse.AccessToken(childComplexity), true return e.complexity.AuthResponse.AccessToken(childComplexity), true
case "AuthResponse.expires_at": case "AuthResponse.expires_in":
if e.complexity.AuthResponse.ExpiresAt == nil { if e.complexity.AuthResponse.ExpiresIn == nil {
break break
} }
return e.complexity.AuthResponse.ExpiresAt(childComplexity), true return e.complexity.AuthResponse.ExpiresIn(childComplexity), true
case "AuthResponse.id_token":
if e.complexity.AuthResponse.IDToken == nil {
break
}
return e.complexity.AuthResponse.IDToken(childComplexity), true
case "AuthResponse.message": case "AuthResponse.message":
if e.complexity.AuthResponse.Message == nil { if e.complexity.AuthResponse.Message == nil {
@@ -251,6 +258,13 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in
return e.complexity.AuthResponse.Message(childComplexity), true return e.complexity.AuthResponse.Message(childComplexity), true
case "AuthResponse.refresh_token":
if e.complexity.AuthResponse.RefreshToken == nil {
break
}
return e.complexity.AuthResponse.RefreshToken(childComplexity), true
case "AuthResponse.user": case "AuthResponse.user":
if e.complexity.AuthResponse.User == nil { if e.complexity.AuthResponse.User == nil {
break break
@@ -279,6 +293,20 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in
return e.complexity.Env.AppURL(childComplexity), true return e.complexity.Env.AppURL(childComplexity), true
case "Env.CLIENT_ID":
if e.complexity.Env.ClientID == nil {
break
}
return e.complexity.Env.ClientID(childComplexity), true
case "Env.CLIENT_SECRET":
if e.complexity.Env.ClientSecret == nil {
break
}
return e.complexity.Env.ClientSecret(childComplexity), true
case "Env.COOKIE_NAME": case "Env.COOKIE_NAME":
if e.complexity.Env.CookieName == nil { if e.complexity.Env.CookieName == nil {
break break
@@ -391,6 +419,20 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in
return e.complexity.Env.GoogleClientSecret(childComplexity), true return e.complexity.Env.GoogleClientSecret(childComplexity), true
case "Env.JWT_PRIVATE_KEY":
if e.complexity.Env.JwtPrivateKey == nil {
break
}
return e.complexity.Env.JwtPrivateKey(childComplexity), true
case "Env.JWT_PUBLIC_KEY":
if e.complexity.Env.JwtPublicKey == nil {
break
}
return e.complexity.Env.JwtPublicKey(childComplexity), true
case "Env.JWT_ROLE_CLAIM": case "Env.JWT_ROLE_CLAIM":
if e.complexity.Env.JwtRoleClaim == nil { if e.complexity.Env.JwtRoleClaim == nil {
break break
@@ -503,6 +545,13 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in
return e.complexity.Error.Reason(childComplexity), true return e.complexity.Error.Reason(childComplexity), true
case "Meta.client_id":
if e.complexity.Meta.ClientID == nil {
break
}
return e.complexity.Meta.ClientID(childComplexity), true
case "Meta.is_basic_authentication_enabled": case "Meta.is_basic_authentication_enabled":
if e.complexity.Meta.IsBasicAuthenticationEnabled == nil { if e.complexity.Meta.IsBasicAuthenticationEnabled == nil {
break break
@@ -764,18 +813,6 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in
return e.complexity.Query.Env(childComplexity), true return e.complexity.Query.Env(childComplexity), true
case "Query.is_valid_jwt":
if e.complexity.Query.IsValidJwt == nil {
break
}
args, err := ec.field_Query_is_valid_jwt_args(context.TODO(), rawArgs)
if err != nil {
return 0, false
}
return e.complexity.Query.IsValidJwt(childComplexity, args["params"].(*model.IsValidJWTQueryInput)), true
case "Query.meta": case "Query.meta":
if e.complexity.Query.Meta == nil { if e.complexity.Query.Meta == nil {
break break
@@ -966,20 +1003,6 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in
return e.complexity.Users.Users(childComplexity), true return e.complexity.Users.Users(childComplexity), true
case "ValidJWTResponse.message":
if e.complexity.ValidJWTResponse.Message == nil {
break
}
return e.complexity.ValidJWTResponse.Message(childComplexity), true
case "ValidJWTResponse.valid":
if e.complexity.ValidJWTResponse.Valid == nil {
break
}
return e.complexity.ValidJWTResponse.Valid(childComplexity), true
case "VerificationRequest.created_at": case "VerificationRequest.created_at":
if e.complexity.VerificationRequest.CreatedAt == nil { if e.complexity.VerificationRequest.CreatedAt == nil {
break break
@@ -1123,6 +1146,7 @@ type Pagination {
type Meta { type Meta {
version: String! version: String!
client_id: String!
is_google_login_enabled: Boolean! is_google_login_enabled: Boolean!
is_facebook_login_enabled: Boolean! is_facebook_login_enabled: Boolean!
is_github_login_enabled: Boolean! is_github_login_enabled: Boolean!
@@ -1180,7 +1204,9 @@ type Error {
type AuthResponse { type AuthResponse {
message: String! message: String!
access_token: String access_token: String
expires_at: Int64 id_token: String
refresh_token: String
expires_in: Int64
user: User user: User
} }
@@ -1188,16 +1214,13 @@ type Response {
message: String! message: String!
} }
type ValidJWTResponse {
valid: Boolean!
message: String!
}
type Env { type Env {
ADMIN_SECRET: String ADMIN_SECRET: String
DATABASE_NAME: String DATABASE_NAME: String!
DATABASE_URL: String DATABASE_URL: String!
DATABASE_TYPE: String DATABASE_TYPE: String!
CLIENT_ID: String!
CLIENT_SECRET: String!
CUSTOM_ACCESS_TOKEN_SCRIPT: String CUSTOM_ACCESS_TOKEN_SCRIPT: String
SMTP_HOST: String SMTP_HOST: String
SMTP_PORT: String SMTP_PORT: String
@@ -1206,6 +1229,8 @@ type Env {
SENDER_EMAIL: String SENDER_EMAIL: String
JWT_TYPE: String JWT_TYPE: String
JWT_SECRET: String JWT_SECRET: String
JWT_PRIVATE_KEY: String
JWT_PUBLIC_KEY: String
ALLOWED_ORIGINS: [String!] ALLOWED_ORIGINS: [String!]
APP_URL: String APP_URL: String
REDIS_URL: String REDIS_URL: String
@@ -1240,6 +1265,8 @@ input UpdateEnvInput {
SENDER_EMAIL: String SENDER_EMAIL: String
JWT_TYPE: String JWT_TYPE: String
JWT_SECRET: String JWT_SECRET: String
JWT_PRIVATE_KEY: String
JWT_PUBLIC_KEY: String
ALLOWED_ORIGINS: [String!] ALLOWED_ORIGINS: [String!]
APP_URL: String APP_URL: String
REDIS_URL: String REDIS_URL: String
@@ -1290,6 +1317,7 @@ input LoginInput {
email: String! email: String!
password: String! password: String!
roles: [String!] roles: [String!]
scope: [String!]
} }
input VerifyEmailInput { input VerifyEmailInput {
@@ -1348,15 +1376,12 @@ input DeleteUserInput {
input MagicLinkLoginInput { input MagicLinkLoginInput {
email: String! email: String!
roles: [String!] roles: [String!]
scope: [String!]
} }
input SessionQueryInput { input SessionQueryInput {
roles: [String!] roles: [String!]
} scope: [String!]
input IsValidJWTQueryInput {
jwt: String
roles: [String!]
} }
input PaginationInput { input PaginationInput {
@@ -1390,7 +1415,6 @@ type Mutation {
type Query { type Query {
meta: Meta! meta: Meta!
session(params: SessionQueryInput): AuthResponse! session(params: SessionQueryInput): AuthResponse!
is_valid_jwt(params: IsValidJWTQueryInput): ValidJWTResponse!
profile: User! profile: User!
# admin only apis # admin only apis
_users(params: PaginatedInput): Users! _users(params: PaginatedInput): Users!
@@ -1646,21 +1670,6 @@ func (ec *executionContext) field_Query__verification_requests_args(ctx context.
return args, nil return args, nil
} }
func (ec *executionContext) field_Query_is_valid_jwt_args(ctx context.Context, rawArgs map[string]interface{}) (map[string]interface{}, error) {
var err error
args := map[string]interface{}{}
var arg0 *model.IsValidJWTQueryInput
if tmp, ok := rawArgs["params"]; ok {
ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("params"))
arg0, err = ec.unmarshalOIsValidJWTQueryInput2ᚖgithubᚗcomᚋauthorizerdevᚋauthorizerᚋserverᚋgraphᚋmodelᚐIsValidJWTQueryInput(ctx, tmp)
if err != nil {
return nil, err
}
}
args["params"] = arg0
return args, nil
}
func (ec *executionContext) field_Query_session_args(ctx context.Context, rawArgs map[string]interface{}) (map[string]interface{}, error) { func (ec *executionContext) field_Query_session_args(ctx context.Context, rawArgs map[string]interface{}) (map[string]interface{}, error) {
var err error var err error
args := map[string]interface{}{} args := map[string]interface{}{}
@@ -1781,7 +1790,7 @@ func (ec *executionContext) _AuthResponse_access_token(ctx context.Context, fiel
return ec.marshalOString2ᚖstring(ctx, field.Selections, res) return ec.marshalOString2ᚖstring(ctx, field.Selections, res)
} }
func (ec *executionContext) _AuthResponse_expires_at(ctx context.Context, field graphql.CollectedField, obj *model.AuthResponse) (ret graphql.Marshaler) { func (ec *executionContext) _AuthResponse_id_token(ctx context.Context, field graphql.CollectedField, obj *model.AuthResponse) (ret graphql.Marshaler) {
defer func() { defer func() {
if r := recover(); r != nil { if r := recover(); r != nil {
ec.Error(ctx, ec.Recover(ctx, r)) ec.Error(ctx, ec.Recover(ctx, r))
@@ -1799,7 +1808,71 @@ func (ec *executionContext) _AuthResponse_expires_at(ctx context.Context, field
ctx = graphql.WithFieldContext(ctx, fc) ctx = graphql.WithFieldContext(ctx, fc)
resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) {
ctx = rctx // use context from middleware stack in children ctx = rctx // use context from middleware stack in children
return obj.ExpiresAt, nil return obj.IDToken, 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) _AuthResponse_refresh_token(ctx context.Context, field graphql.CollectedField, obj *model.AuthResponse) (ret graphql.Marshaler) {
defer func() {
if r := recover(); r != nil {
ec.Error(ctx, ec.Recover(ctx, r))
ret = graphql.Null
}
}()
fc := &graphql.FieldContext{
Object: "AuthResponse",
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.RefreshToken, 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) _AuthResponse_expires_in(ctx context.Context, field graphql.CollectedField, obj *model.AuthResponse) (ret graphql.Marshaler) {
defer func() {
if r := recover(); r != nil {
ec.Error(ctx, ec.Recover(ctx, r))
ret = graphql.Null
}
}()
fc := &graphql.FieldContext{
Object: "AuthResponse",
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.ExpiresIn, nil
}) })
if err != nil { if err != nil {
ec.Error(ctx, err) ec.Error(ctx, err)
@@ -1902,11 +1975,14 @@ func (ec *executionContext) _Env_DATABASE_NAME(ctx context.Context, field graphq
return graphql.Null return graphql.Null
} }
if resTmp == nil { if resTmp == nil {
if !graphql.HasFieldError(ctx, fc) {
ec.Errorf(ctx, "must not be null")
}
return graphql.Null return graphql.Null
} }
res := resTmp.(*string) res := resTmp.(string)
fc.Result = res fc.Result = res
return ec.marshalOString2string(ctx, field.Selections, res) return ec.marshalNString2string(ctx, field.Selections, res)
} }
func (ec *executionContext) _Env_DATABASE_URL(ctx context.Context, field graphql.CollectedField, obj *model.Env) (ret graphql.Marshaler) { func (ec *executionContext) _Env_DATABASE_URL(ctx context.Context, field graphql.CollectedField, obj *model.Env) (ret graphql.Marshaler) {
@@ -1934,11 +2010,14 @@ func (ec *executionContext) _Env_DATABASE_URL(ctx context.Context, field graphql
return graphql.Null return graphql.Null
} }
if resTmp == nil { if resTmp == nil {
if !graphql.HasFieldError(ctx, fc) {
ec.Errorf(ctx, "must not be null")
}
return graphql.Null return graphql.Null
} }
res := resTmp.(*string) res := resTmp.(string)
fc.Result = res fc.Result = res
return ec.marshalOString2string(ctx, field.Selections, res) return ec.marshalNString2string(ctx, field.Selections, res)
} }
func (ec *executionContext) _Env_DATABASE_TYPE(ctx context.Context, field graphql.CollectedField, obj *model.Env) (ret graphql.Marshaler) { func (ec *executionContext) _Env_DATABASE_TYPE(ctx context.Context, field graphql.CollectedField, obj *model.Env) (ret graphql.Marshaler) {
@@ -1966,11 +2045,84 @@ func (ec *executionContext) _Env_DATABASE_TYPE(ctx context.Context, field graphq
return graphql.Null return graphql.Null
} }
if resTmp == nil { if resTmp == nil {
if !graphql.HasFieldError(ctx, fc) {
ec.Errorf(ctx, "must not be null")
}
return graphql.Null return graphql.Null
} }
res := resTmp.(*string) res := resTmp.(string)
fc.Result = res fc.Result = res
return ec.marshalOString2string(ctx, field.Selections, res) return ec.marshalNString2string(ctx, field.Selections, res)
}
func (ec *executionContext) _Env_CLIENT_ID(ctx context.Context, field graphql.CollectedField, obj *model.Env) (ret graphql.Marshaler) {
defer func() {
if r := recover(); r != nil {
ec.Error(ctx, ec.Recover(ctx, r))
ret = graphql.Null
}
}()
fc := &graphql.FieldContext{
Object: "Env",
Field: field,
Args: nil,
IsMethod: false,
IsResolver: false,
}
ctx = graphql.WithFieldContext(ctx, fc)
resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) {
ctx = rctx // use context from middleware stack in children
return obj.ClientID, 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.(string)
fc.Result = res
return ec.marshalNString2string(ctx, field.Selections, res)
}
func (ec *executionContext) _Env_CLIENT_SECRET(ctx context.Context, field graphql.CollectedField, obj *model.Env) (ret graphql.Marshaler) {
defer func() {
if r := recover(); r != nil {
ec.Error(ctx, ec.Recover(ctx, r))
ret = graphql.Null
}
}()
fc := &graphql.FieldContext{
Object: "Env",
Field: field,
Args: nil,
IsMethod: false,
IsResolver: false,
}
ctx = graphql.WithFieldContext(ctx, fc)
resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) {
ctx = rctx // use context from middleware stack in children
return obj.ClientSecret, 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.(string)
fc.Result = res
return ec.marshalNString2string(ctx, field.Selections, res)
} }
func (ec *executionContext) _Env_CUSTOM_ACCESS_TOKEN_SCRIPT(ctx context.Context, field graphql.CollectedField, obj *model.Env) (ret graphql.Marshaler) { func (ec *executionContext) _Env_CUSTOM_ACCESS_TOKEN_SCRIPT(ctx context.Context, field graphql.CollectedField, obj *model.Env) (ret graphql.Marshaler) {
@@ -2229,6 +2381,70 @@ func (ec *executionContext) _Env_JWT_SECRET(ctx context.Context, field graphql.C
return ec.marshalOString2ᚖstring(ctx, field.Selections, res) return ec.marshalOString2ᚖstring(ctx, field.Selections, res)
} }
func (ec *executionContext) _Env_JWT_PRIVATE_KEY(ctx context.Context, field graphql.CollectedField, obj *model.Env) (ret graphql.Marshaler) {
defer func() {
if r := recover(); r != nil {
ec.Error(ctx, ec.Recover(ctx, r))
ret = graphql.Null
}
}()
fc := &graphql.FieldContext{
Object: "Env",
Field: field,
Args: nil,
IsMethod: false,
IsResolver: false,
}
ctx = graphql.WithFieldContext(ctx, fc)
resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) {
ctx = rctx // use context from middleware stack in children
return obj.JwtPrivateKey, nil
})
if err != nil {
ec.Error(ctx, err)
return graphql.Null
}
if resTmp == nil {
return graphql.Null
}
res := resTmp.(*string)
fc.Result = res
return ec.marshalOString2ᚖstring(ctx, field.Selections, res)
}
func (ec *executionContext) _Env_JWT_PUBLIC_KEY(ctx context.Context, field graphql.CollectedField, obj *model.Env) (ret graphql.Marshaler) {
defer func() {
if r := recover(); r != nil {
ec.Error(ctx, ec.Recover(ctx, r))
ret = graphql.Null
}
}()
fc := &graphql.FieldContext{
Object: "Env",
Field: field,
Args: nil,
IsMethod: false,
IsResolver: false,
}
ctx = graphql.WithFieldContext(ctx, fc)
resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) {
ctx = rctx // use context from middleware stack in children
return obj.JwtPublicKey, nil
})
if err != nil {
ec.Error(ctx, err)
return graphql.Null
}
if resTmp == nil {
return graphql.Null
}
res := resTmp.(*string)
fc.Result = res
return ec.marshalOString2ᚖstring(ctx, field.Selections, res)
}
func (ec *executionContext) _Env_ALLOWED_ORIGINS(ctx context.Context, field graphql.CollectedField, obj *model.Env) (ret graphql.Marshaler) { func (ec *executionContext) _Env_ALLOWED_ORIGINS(ctx context.Context, field graphql.CollectedField, obj *model.Env) (ret graphql.Marshaler) {
defer func() { defer func() {
if r := recover(); r != nil { if r := recover(); r != nil {
@@ -3006,6 +3222,41 @@ func (ec *executionContext) _Meta_version(ctx context.Context, field graphql.Col
return ec.marshalNString2string(ctx, field.Selections, res) return ec.marshalNString2string(ctx, field.Selections, res)
} }
func (ec *executionContext) _Meta_client_id(ctx context.Context, field graphql.CollectedField, obj *model.Meta) (ret graphql.Marshaler) {
defer func() {
if r := recover(); r != nil {
ec.Error(ctx, ec.Recover(ctx, r))
ret = graphql.Null
}
}()
fc := &graphql.FieldContext{
Object: "Meta",
Field: field,
Args: nil,
IsMethod: false,
IsResolver: false,
}
ctx = graphql.WithFieldContext(ctx, fc)
resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) {
ctx = rctx // use context from middleware stack in children
return obj.ClientID, 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.(string)
fc.Result = res
return ec.marshalNString2string(ctx, field.Selections, res)
}
func (ec *executionContext) _Meta_is_google_login_enabled(ctx context.Context, field graphql.CollectedField, obj *model.Meta) (ret graphql.Marshaler) { func (ec *executionContext) _Meta_is_google_login_enabled(ctx context.Context, field graphql.CollectedField, obj *model.Meta) (ret graphql.Marshaler) {
defer func() { defer func() {
if r := recover(); r != nil { if r := recover(); r != nil {
@@ -4049,48 +4300,6 @@ func (ec *executionContext) _Query_session(ctx context.Context, field graphql.Co
return ec.marshalNAuthResponse2ᚖgithubᚗcomᚋauthorizerdevᚋauthorizerᚋserverᚋgraphᚋmodelᚐAuthResponse(ctx, field.Selections, res) return ec.marshalNAuthResponse2ᚖgithubᚗcomᚋauthorizerdevᚋauthorizerᚋserverᚋgraphᚋmodelᚐAuthResponse(ctx, field.Selections, res)
} }
func (ec *executionContext) _Query_is_valid_jwt(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_is_valid_jwt_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().IsValidJwt(rctx, args["params"].(*model.IsValidJWTQueryInput))
})
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.ValidJWTResponse)
fc.Result = res
return ec.marshalNValidJWTResponse2ᚖgithubᚗcomᚋauthorizerdevᚋauthorizerᚋserverᚋgraphᚋmodelᚐValidJWTResponse(ctx, field.Selections, res)
}
func (ec *executionContext) _Query_profile(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) { func (ec *executionContext) _Query_profile(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) {
defer func() { defer func() {
if r := recover(); r != nil { if r := recover(); r != nil {
@@ -5015,76 +5224,6 @@ 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) return ec.marshalNUser2ᚕᚖgithubᚗcomᚋauthorizerdevᚋauthorizerᚋserverᚋgraphᚋmodelᚐUserᚄ(ctx, field.Selections, res)
} }
func (ec *executionContext) _ValidJWTResponse_valid(ctx context.Context, field graphql.CollectedField, obj *model.ValidJWTResponse) (ret graphql.Marshaler) {
defer func() {
if r := recover(); r != nil {
ec.Error(ctx, ec.Recover(ctx, r))
ret = graphql.Null
}
}()
fc := &graphql.FieldContext{
Object: "ValidJWTResponse",
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.Valid, 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) _ValidJWTResponse_message(ctx context.Context, field graphql.CollectedField, obj *model.ValidJWTResponse) (ret graphql.Marshaler) {
defer func() {
if r := recover(); r != nil {
ec.Error(ctx, ec.Recover(ctx, r))
ret = graphql.Null
}
}()
fc := &graphql.FieldContext{
Object: "ValidJWTResponse",
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.Message, 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.(string)
fc.Result = res
return ec.marshalNString2string(ctx, field.Selections, res)
}
func (ec *executionContext) _VerificationRequest_id(ctx context.Context, field graphql.CollectedField, obj *model.VerificationRequest) (ret graphql.Marshaler) { func (ec *executionContext) _VerificationRequest_id(ctx context.Context, field graphql.CollectedField, obj *model.VerificationRequest) (ret graphql.Marshaler) {
defer func() { defer func() {
if r := recover(); r != nil { if r := recover(); r != nil {
@@ -6596,37 +6735,6 @@ func (ec *executionContext) unmarshalInputForgotPasswordInput(ctx context.Contex
return it, nil return it, nil
} }
func (ec *executionContext) unmarshalInputIsValidJWTQueryInput(ctx context.Context, obj interface{}) (model.IsValidJWTQueryInput, error) {
var it model.IsValidJWTQueryInput
asMap := map[string]interface{}{}
for k, v := range obj.(map[string]interface{}) {
asMap[k] = v
}
for k, v := range asMap {
switch k {
case "jwt":
var err error
ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("jwt"))
it.Jwt, err = ec.unmarshalOString2ᚖstring(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) unmarshalInputLoginInput(ctx context.Context, obj interface{}) (model.LoginInput, error) { func (ec *executionContext) unmarshalInputLoginInput(ctx context.Context, obj interface{}) (model.LoginInput, error) {
var it model.LoginInput var it model.LoginInput
asMap := map[string]interface{}{} asMap := map[string]interface{}{}
@@ -6660,6 +6768,14 @@ func (ec *executionContext) unmarshalInputLoginInput(ctx context.Context, obj in
if err != nil { if err != nil {
return it, err return it, err
} }
case "scope":
var err error
ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("scope"))
it.Scope, err = ec.unmarshalOString2ᚕstringᚄ(ctx, v)
if err != nil {
return it, err
}
} }
} }
@@ -6691,6 +6807,14 @@ func (ec *executionContext) unmarshalInputMagicLinkLoginInput(ctx context.Contex
if err != nil { if err != nil {
return it, err return it, err
} }
case "scope":
var err error
ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("scope"))
it.Scope, err = ec.unmarshalOString2ᚕstringᚄ(ctx, v)
if err != nil {
return it, err
}
} }
} }
@@ -6838,6 +6962,14 @@ func (ec *executionContext) unmarshalInputSessionQueryInput(ctx context.Context,
if err != nil { if err != nil {
return it, err return it, err
} }
case "scope":
var err error
ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("scope"))
it.Scope, err = ec.unmarshalOString2ᚕstringᚄ(ctx, v)
if err != nil {
return it, err
}
} }
} }
@@ -7044,6 +7176,22 @@ func (ec *executionContext) unmarshalInputUpdateEnvInput(ctx context.Context, ob
if err != nil { if err != nil {
return it, err return it, err
} }
case "JWT_PRIVATE_KEY":
var err error
ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("JWT_PRIVATE_KEY"))
it.JwtPrivateKey, err = ec.unmarshalOString2ᚖstring(ctx, v)
if err != nil {
return it, err
}
case "JWT_PUBLIC_KEY":
var err error
ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("JWT_PUBLIC_KEY"))
it.JwtPublicKey, err = ec.unmarshalOString2ᚖstring(ctx, v)
if err != nil {
return it, err
}
case "ALLOWED_ORIGINS": case "ALLOWED_ORIGINS":
var err error var err error
@@ -7489,8 +7637,12 @@ func (ec *executionContext) _AuthResponse(ctx context.Context, sel ast.Selection
} }
case "access_token": case "access_token":
out.Values[i] = ec._AuthResponse_access_token(ctx, field, obj) out.Values[i] = ec._AuthResponse_access_token(ctx, field, obj)
case "expires_at": case "id_token":
out.Values[i] = ec._AuthResponse_expires_at(ctx, field, obj) out.Values[i] = ec._AuthResponse_id_token(ctx, field, obj)
case "refresh_token":
out.Values[i] = ec._AuthResponse_refresh_token(ctx, field, obj)
case "expires_in":
out.Values[i] = ec._AuthResponse_expires_in(ctx, field, obj)
case "user": case "user":
out.Values[i] = ec._AuthResponse_user(ctx, field, obj) out.Values[i] = ec._AuthResponse_user(ctx, field, obj)
default: default:
@@ -7519,10 +7671,29 @@ func (ec *executionContext) _Env(ctx context.Context, sel ast.SelectionSet, obj
out.Values[i] = ec._Env_ADMIN_SECRET(ctx, field, obj) out.Values[i] = ec._Env_ADMIN_SECRET(ctx, field, obj)
case "DATABASE_NAME": case "DATABASE_NAME":
out.Values[i] = ec._Env_DATABASE_NAME(ctx, field, obj) out.Values[i] = ec._Env_DATABASE_NAME(ctx, field, obj)
if out.Values[i] == graphql.Null {
invalids++
}
case "DATABASE_URL": case "DATABASE_URL":
out.Values[i] = ec._Env_DATABASE_URL(ctx, field, obj) out.Values[i] = ec._Env_DATABASE_URL(ctx, field, obj)
if out.Values[i] == graphql.Null {
invalids++
}
case "DATABASE_TYPE": case "DATABASE_TYPE":
out.Values[i] = ec._Env_DATABASE_TYPE(ctx, field, obj) out.Values[i] = ec._Env_DATABASE_TYPE(ctx, field, obj)
if out.Values[i] == graphql.Null {
invalids++
}
case "CLIENT_ID":
out.Values[i] = ec._Env_CLIENT_ID(ctx, field, obj)
if out.Values[i] == graphql.Null {
invalids++
}
case "CLIENT_SECRET":
out.Values[i] = ec._Env_CLIENT_SECRET(ctx, field, obj)
if out.Values[i] == graphql.Null {
invalids++
}
case "CUSTOM_ACCESS_TOKEN_SCRIPT": case "CUSTOM_ACCESS_TOKEN_SCRIPT":
out.Values[i] = ec._Env_CUSTOM_ACCESS_TOKEN_SCRIPT(ctx, field, obj) out.Values[i] = ec._Env_CUSTOM_ACCESS_TOKEN_SCRIPT(ctx, field, obj)
case "SMTP_HOST": case "SMTP_HOST":
@@ -7539,6 +7710,10 @@ func (ec *executionContext) _Env(ctx context.Context, sel ast.SelectionSet, obj
out.Values[i] = ec._Env_JWT_TYPE(ctx, field, obj) out.Values[i] = ec._Env_JWT_TYPE(ctx, field, obj)
case "JWT_SECRET": case "JWT_SECRET":
out.Values[i] = ec._Env_JWT_SECRET(ctx, field, obj) out.Values[i] = ec._Env_JWT_SECRET(ctx, field, obj)
case "JWT_PRIVATE_KEY":
out.Values[i] = ec._Env_JWT_PRIVATE_KEY(ctx, field, obj)
case "JWT_PUBLIC_KEY":
out.Values[i] = ec._Env_JWT_PUBLIC_KEY(ctx, field, obj)
case "ALLOWED_ORIGINS": case "ALLOWED_ORIGINS":
out.Values[i] = ec._Env_ALLOWED_ORIGINS(ctx, field, obj) out.Values[i] = ec._Env_ALLOWED_ORIGINS(ctx, field, obj)
case "APP_URL": case "APP_URL":
@@ -7640,6 +7815,11 @@ func (ec *executionContext) _Meta(ctx context.Context, sel ast.SelectionSet, obj
if out.Values[i] == graphql.Null { if out.Values[i] == graphql.Null {
invalids++ invalids++
} }
case "client_id":
out.Values[i] = ec._Meta_client_id(ctx, field, obj)
if out.Values[i] == graphql.Null {
invalids++
}
case "is_google_login_enabled": case "is_google_login_enabled":
out.Values[i] = ec._Meta_is_google_login_enabled(ctx, field, obj) out.Values[i] = ec._Meta_is_google_login_enabled(ctx, field, obj)
if out.Values[i] == graphql.Null { if out.Values[i] == graphql.Null {
@@ -7867,20 +8047,6 @@ func (ec *executionContext) _Query(ctx context.Context, sel ast.SelectionSet) gr
} }
return res return res
}) })
case "is_valid_jwt":
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_is_valid_jwt(ctx, field)
if res == graphql.Null {
atomic.AddUint32(&invalids, 1)
}
return res
})
case "profile": case "profile":
field := field field := field
out.Concurrently(i, func() (res graphql.Marshaler) { out.Concurrently(i, func() (res graphql.Marshaler) {
@@ -8096,38 +8262,6 @@ func (ec *executionContext) _Users(ctx context.Context, sel ast.SelectionSet, ob
return out return out
} }
var validJWTResponseImplementors = []string{"ValidJWTResponse"}
func (ec *executionContext) _ValidJWTResponse(ctx context.Context, sel ast.SelectionSet, obj *model.ValidJWTResponse) graphql.Marshaler {
fields := graphql.CollectFields(ec.OperationContext, sel, validJWTResponseImplementors)
out := graphql.NewFieldSet(fields)
var invalids uint32
for i, field := range fields {
switch field.Name {
case "__typename":
out.Values[i] = graphql.MarshalString("ValidJWTResponse")
case "valid":
out.Values[i] = ec._ValidJWTResponse_valid(ctx, field, obj)
if out.Values[i] == graphql.Null {
invalids++
}
case "message":
out.Values[i] = ec._ValidJWTResponse_message(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"} var verificationRequestImplementors = []string{"VerificationRequest"}
func (ec *executionContext) _VerificationRequest(ctx context.Context, sel ast.SelectionSet, obj *model.VerificationRequest) graphql.Marshaler { func (ec *executionContext) _VerificationRequest(ctx context.Context, sel ast.SelectionSet, obj *model.VerificationRequest) graphql.Marshaler {
@@ -8743,20 +8877,6 @@ func (ec *executionContext) marshalNUsers2ᚖgithubᚗcomᚋauthorizerdevᚋauth
return ec._Users(ctx, sel, v) return ec._Users(ctx, sel, v)
} }
func (ec *executionContext) marshalNValidJWTResponse2githubᚗcomᚋauthorizerdevᚋauthorizerᚋserverᚋgraphᚋmodelᚐValidJWTResponse(ctx context.Context, sel ast.SelectionSet, v model.ValidJWTResponse) graphql.Marshaler {
return ec._ValidJWTResponse(ctx, sel, &v)
}
func (ec *executionContext) marshalNValidJWTResponse2ᚖgithubᚗcomᚋauthorizerdevᚋauthorizerᚋserverᚋgraphᚋmodelᚐValidJWTResponse(ctx context.Context, sel ast.SelectionSet, v *model.ValidJWTResponse) graphql.Marshaler {
if v == nil {
if !graphql.HasFieldError(ctx, graphql.GetFieldContext(ctx)) {
ec.Errorf(ctx, "must not be null")
}
return graphql.Null
}
return ec._ValidJWTResponse(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 { 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)) ret := make(graphql.Array, len(v))
var wg sync.WaitGroup var wg sync.WaitGroup
@@ -9126,14 +9246,6 @@ func (ec *executionContext) marshalOInt642ᚖint64(ctx context.Context, sel ast.
return graphql.MarshalInt64(*v) return graphql.MarshalInt64(*v)
} }
func (ec *executionContext) unmarshalOIsValidJWTQueryInput2ᚖgithubᚗcomᚋauthorizerdevᚋauthorizerᚋserverᚋgraphᚋmodelᚐIsValidJWTQueryInput(ctx context.Context, v interface{}) (*model.IsValidJWTQueryInput, error) {
if v == nil {
return nil, nil
}
res, err := ec.unmarshalInputIsValidJWTQueryInput(ctx, v)
return &res, graphql.ErrorOnPath(ctx, err)
}
func (ec *executionContext) unmarshalOPaginatedInput2ᚖgithubᚗcomᚋauthorizerdevᚋauthorizerᚋserverᚋgraphᚋmodelᚐPaginatedInput(ctx context.Context, v interface{}) (*model.PaginatedInput, error) { func (ec *executionContext) unmarshalOPaginatedInput2ᚖgithubᚗcomᚋauthorizerdevᚋauthorizerᚋserverᚋgraphᚋmodelᚐPaginatedInput(ctx context.Context, v interface{}) (*model.PaginatedInput, error) {
if v == nil { if v == nil {
return nil, nil return nil, nil

View File

@@ -11,10 +11,12 @@ type AdminSignupInput struct {
} }
type AuthResponse struct { type AuthResponse struct {
Message string `json:"message"` Message string `json:"message"`
AccessToken *string `json:"access_token"` AccessToken *string `json:"access_token"`
ExpiresAt *int64 `json:"expires_at"` IDToken *string `json:"id_token"`
User *User `json:"user"` RefreshToken *string `json:"refresh_token"`
ExpiresIn *int64 `json:"expires_in"`
User *User `json:"user"`
} }
type DeleteUserInput struct { type DeleteUserInput struct {
@@ -23,9 +25,11 @@ type DeleteUserInput struct {
type Env struct { type Env struct {
AdminSecret *string `json:"ADMIN_SECRET"` AdminSecret *string `json:"ADMIN_SECRET"`
DatabaseName *string `json:"DATABASE_NAME"` DatabaseName string `json:"DATABASE_NAME"`
DatabaseURL *string `json:"DATABASE_URL"` DatabaseURL string `json:"DATABASE_URL"`
DatabaseType *string `json:"DATABASE_TYPE"` DatabaseType string `json:"DATABASE_TYPE"`
ClientID string `json:"CLIENT_ID"`
ClientSecret string `json:"CLIENT_SECRET"`
CustomAccessTokenScript *string `json:"CUSTOM_ACCESS_TOKEN_SCRIPT"` CustomAccessTokenScript *string `json:"CUSTOM_ACCESS_TOKEN_SCRIPT"`
SMTPHost *string `json:"SMTP_HOST"` SMTPHost *string `json:"SMTP_HOST"`
SMTPPort *string `json:"SMTP_PORT"` SMTPPort *string `json:"SMTP_PORT"`
@@ -34,6 +38,8 @@ type Env struct {
SenderEmail *string `json:"SENDER_EMAIL"` SenderEmail *string `json:"SENDER_EMAIL"`
JwtType *string `json:"JWT_TYPE"` JwtType *string `json:"JWT_TYPE"`
JwtSecret *string `json:"JWT_SECRET"` JwtSecret *string `json:"JWT_SECRET"`
JwtPrivateKey *string `json:"JWT_PRIVATE_KEY"`
JwtPublicKey *string `json:"JWT_PUBLIC_KEY"`
AllowedOrigins []string `json:"ALLOWED_ORIGINS"` AllowedOrigins []string `json:"ALLOWED_ORIGINS"`
AppURL *string `json:"APP_URL"` AppURL *string `json:"APP_URL"`
RedisURL *string `json:"REDIS_URL"` RedisURL *string `json:"REDIS_URL"`
@@ -66,24 +72,22 @@ type ForgotPasswordInput struct {
Email string `json:"email"` Email string `json:"email"`
} }
type IsValidJWTQueryInput struct {
Jwt *string `json:"jwt"`
Roles []string `json:"roles"`
}
type LoginInput struct { type LoginInput struct {
Email string `json:"email"` Email string `json:"email"`
Password string `json:"password"` Password string `json:"password"`
Roles []string `json:"roles"` Roles []string `json:"roles"`
Scope []string `json:"scope"`
} }
type MagicLinkLoginInput struct { type MagicLinkLoginInput struct {
Email string `json:"email"` Email string `json:"email"`
Roles []string `json:"roles"` Roles []string `json:"roles"`
Scope []string `json:"scope"`
} }
type Meta struct { type Meta struct {
Version string `json:"version"` Version string `json:"version"`
ClientID string `json:"client_id"`
IsGoogleLoginEnabled bool `json:"is_google_login_enabled"` IsGoogleLoginEnabled bool `json:"is_google_login_enabled"`
IsFacebookLoginEnabled bool `json:"is_facebook_login_enabled"` IsFacebookLoginEnabled bool `json:"is_facebook_login_enabled"`
IsGithubLoginEnabled bool `json:"is_github_login_enabled"` IsGithubLoginEnabled bool `json:"is_github_login_enabled"`
@@ -125,6 +129,7 @@ type Response struct {
type SessionQueryInput struct { type SessionQueryInput struct {
Roles []string `json:"roles"` Roles []string `json:"roles"`
Scope []string `json:"scope"`
} }
type SignUpInput struct { type SignUpInput struct {
@@ -153,6 +158,8 @@ type UpdateEnvInput struct {
SenderEmail *string `json:"SENDER_EMAIL"` SenderEmail *string `json:"SENDER_EMAIL"`
JwtType *string `json:"JWT_TYPE"` JwtType *string `json:"JWT_TYPE"`
JwtSecret *string `json:"JWT_SECRET"` JwtSecret *string `json:"JWT_SECRET"`
JwtPrivateKey *string `json:"JWT_PRIVATE_KEY"`
JwtPublicKey *string `json:"JWT_PUBLIC_KEY"`
AllowedOrigins []string `json:"ALLOWED_ORIGINS"` AllowedOrigins []string `json:"ALLOWED_ORIGINS"`
AppURL *string `json:"APP_URL"` AppURL *string `json:"APP_URL"`
RedisURL *string `json:"REDIS_URL"` RedisURL *string `json:"REDIS_URL"`
@@ -231,11 +238,6 @@ type Users struct {
Users []*User `json:"users"` Users []*User `json:"users"`
} }
type ValidJWTResponse struct {
Valid bool `json:"valid"`
Message string `json:"message"`
}
type VerificationRequest struct { type VerificationRequest struct {
ID string `json:"id"` ID string `json:"id"`
Identifier *string `json:"identifier"` Identifier *string `json:"identifier"`

View File

@@ -14,6 +14,7 @@ type Pagination {
type Meta { type Meta {
version: String! version: String!
client_id: String!
is_google_login_enabled: Boolean! is_google_login_enabled: Boolean!
is_facebook_login_enabled: Boolean! is_facebook_login_enabled: Boolean!
is_github_login_enabled: Boolean! is_github_login_enabled: Boolean!
@@ -71,7 +72,9 @@ type Error {
type AuthResponse { type AuthResponse {
message: String! message: String!
access_token: String access_token: String
expires_at: Int64 id_token: String
refresh_token: String
expires_in: Int64
user: User user: User
} }
@@ -79,16 +82,13 @@ type Response {
message: String! message: String!
} }
type ValidJWTResponse {
valid: Boolean!
message: String!
}
type Env { type Env {
ADMIN_SECRET: String ADMIN_SECRET: String
DATABASE_NAME: String DATABASE_NAME: String!
DATABASE_URL: String DATABASE_URL: String!
DATABASE_TYPE: String DATABASE_TYPE: String!
CLIENT_ID: String!
CLIENT_SECRET: String!
CUSTOM_ACCESS_TOKEN_SCRIPT: String CUSTOM_ACCESS_TOKEN_SCRIPT: String
SMTP_HOST: String SMTP_HOST: String
SMTP_PORT: String SMTP_PORT: String
@@ -97,6 +97,8 @@ type Env {
SENDER_EMAIL: String SENDER_EMAIL: String
JWT_TYPE: String JWT_TYPE: String
JWT_SECRET: String JWT_SECRET: String
JWT_PRIVATE_KEY: String
JWT_PUBLIC_KEY: String
ALLOWED_ORIGINS: [String!] ALLOWED_ORIGINS: [String!]
APP_URL: String APP_URL: String
REDIS_URL: String REDIS_URL: String
@@ -131,6 +133,8 @@ input UpdateEnvInput {
SENDER_EMAIL: String SENDER_EMAIL: String
JWT_TYPE: String JWT_TYPE: String
JWT_SECRET: String JWT_SECRET: String
JWT_PRIVATE_KEY: String
JWT_PUBLIC_KEY: String
ALLOWED_ORIGINS: [String!] ALLOWED_ORIGINS: [String!]
APP_URL: String APP_URL: String
REDIS_URL: String REDIS_URL: String
@@ -181,6 +185,7 @@ input LoginInput {
email: String! email: String!
password: String! password: String!
roles: [String!] roles: [String!]
scope: [String!]
} }
input VerifyEmailInput { input VerifyEmailInput {
@@ -239,15 +244,12 @@ input DeleteUserInput {
input MagicLinkLoginInput { input MagicLinkLoginInput {
email: String! email: String!
roles: [String!] roles: [String!]
scope: [String!]
} }
input SessionQueryInput { input SessionQueryInput {
roles: [String!] roles: [String!]
} scope: [String!]
input IsValidJWTQueryInput {
jwt: String
roles: [String!]
} }
input PaginationInput { input PaginationInput {
@@ -281,7 +283,6 @@ type Mutation {
type Query { type Query {
meta: Meta! meta: Meta!
session(params: SessionQueryInput): AuthResponse! session(params: SessionQueryInput): AuthResponse!
is_valid_jwt(params: IsValidJWTQueryInput): ValidJWTResponse!
profile: User! profile: User!
# admin only apis # admin only apis
_users(params: PaginatedInput): Users! _users(params: PaginatedInput): Users!

View File

@@ -79,10 +79,6 @@ func (r *queryResolver) Session(ctx context.Context, params *model.SessionQueryI
return resolvers.SessionResolver(ctx, params) return resolvers.SessionResolver(ctx, params)
} }
func (r *queryResolver) IsValidJwt(ctx context.Context, params *model.IsValidJWTQueryInput) (*model.ValidJWTResponse, error) {
return resolvers.IsValidJwtResolver(ctx, params)
}
func (r *queryResolver) Profile(ctx context.Context) (*model.User, error) { func (r *queryResolver) Profile(ctx context.Context) (*model.User, error) {
return resolvers.ProfileResolver(ctx) return resolvers.ProfileResolver(ctx)
} }

View File

@@ -7,6 +7,7 @@ import (
"strings" "strings"
"github.com/authorizerdev/authorizer/server/constants" "github.com/authorizerdev/authorizer/server/constants"
"github.com/authorizerdev/authorizer/server/crypto"
"github.com/authorizerdev/authorizer/server/envstore" "github.com/authorizerdev/authorizer/server/envstore"
"github.com/authorizerdev/authorizer/server/utils" "github.com/authorizerdev/authorizer/server/utils"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
@@ -17,13 +18,14 @@ import (
type State struct { type State struct {
AuthorizerURL string `json:"authorizerURL"` AuthorizerURL string `json:"authorizerURL"`
RedirectURL string `json:"redirectURL"` RedirectURL string `json:"redirectURL"`
State string `json:"state"`
} }
// AppHandler is the handler for the /app route // AppHandler is the handler for the /app route
func AppHandler() gin.HandlerFunc { func AppHandler() gin.HandlerFunc {
return func(c *gin.Context) { return func(c *gin.Context) {
hostname := utils.GetHost(c) hostname := utils.GetHost(c)
if envstore.EnvInMemoryStoreObj.GetBoolStoreEnvVariable(constants.EnvKeyDisableLoginPage) { if envstore.EnvStoreObj.GetBoolStoreEnvVariable(constants.EnvKeyDisableLoginPage) {
c.JSON(400, gin.H{"error": "login page is not enabled"}) c.JSON(400, gin.H{"error": "login page is not enabled"})
return return
} }
@@ -36,7 +38,7 @@ func AppHandler() gin.HandlerFunc {
stateObj.AuthorizerURL = hostname stateObj.AuthorizerURL = hostname
stateObj.RedirectURL = hostname + "/app" stateObj.RedirectURL = hostname + "/app"
} else { } else {
decodedState, err := utils.DecryptB64(state) decodedState, err := crypto.DecryptB64(state)
if err != nil { if err != nil {
c.JSON(400, gin.H{"error": "[unable to decode state] invalid state"}) c.JSON(400, gin.H{"error": "[unable to decode state] invalid state"})
return return
@@ -79,8 +81,9 @@ func AppHandler() gin.HandlerFunc {
"data": map[string]string{ "data": map[string]string{
"authorizerURL": stateObj.AuthorizerURL, "authorizerURL": stateObj.AuthorizerURL,
"redirectURL": stateObj.RedirectURL, "redirectURL": stateObj.RedirectURL,
"organizationName": envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyOrganizationName), "state": stateObj.State,
"organizationLogo": envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyOrganizationLogo), "organizationName": envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyOrganizationName),
"organizationLogo": envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyOrganizationLogo),
}, },
}) })
} }

View File

@@ -0,0 +1,324 @@
package handlers
import (
"net/http"
"strings"
"github.com/authorizerdev/authorizer/server/constants"
"github.com/authorizerdev/authorizer/server/cookie"
"github.com/authorizerdev/authorizer/server/crypto"
"github.com/authorizerdev/authorizer/server/db"
"github.com/authorizerdev/authorizer/server/envstore"
"github.com/authorizerdev/authorizer/server/sessionstore"
"github.com/authorizerdev/authorizer/server/token"
"github.com/authorizerdev/authorizer/server/utils"
"github.com/gin-gonic/gin"
"github.com/google/uuid"
)
// AuthorizeHandler is the handler for the /authorize route
// required params
// ?redirect_uri = redirect url
// ?response_mode = to decide if result should be html or re-direct
// state[recommended] = to prevent CSRF attack (for authorizer its compulsory)
// code_challenge = to prevent CSRF attack
// code_challenge_method = to prevent CSRF attack [only sh256 is supported]
// check the flow for generating and verifying codes: https://developer.okta.com/blog/2019/08/22/okta-authjs-pkce#:~:text=PKCE%20works%20by%20having%20the,is%20called%20the%20Code%20Challenge.
func AuthorizeHandler() gin.HandlerFunc {
return func(gc *gin.Context) {
redirectURI := strings.TrimSpace(gc.Query("redirect_uri"))
responseType := strings.TrimSpace(gc.Query("response_type"))
state := strings.TrimSpace(gc.Query("state"))
codeChallenge := strings.TrimSpace(gc.Query("code_challenge"))
scopeString := strings.TrimSpace(gc.Query("scope"))
clientID := strings.TrimSpace(gc.Query("client_id"))
template := "authorize.tmpl"
responseMode := strings.TrimSpace(gc.Query("response_mode"))
if responseMode == "" {
responseMode = "query"
}
if responseMode != "query" && responseMode != "web_message" {
gc.JSON(400, gin.H{"error": "invalid response mode"})
}
if redirectURI == "" {
redirectURI = "/app"
}
isQuery := responseMode == "query"
hostname := utils.GetHost(gc)
loginRedirectState := crypto.EncryptB64(`{"authorizerURL":"` + hostname + `","redirectURL":"` + redirectURI + `", "state":"` + state + `"}`)
loginURL := "/app?state=" + loginRedirectState
if clientID == "" {
if isQuery {
gc.Redirect(http.StatusFound, loginURL)
} else {
gc.HTML(http.StatusOK, template, gin.H{
"target_origin": redirectURI,
"authorization_response": map[string]interface{}{
"type": "authorization_response",
"response": map[string]string{
"error": "client_id is required",
},
},
})
}
return
}
if clientID != envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyClientID) {
if isQuery {
gc.Redirect(http.StatusFound, loginURL)
} else {
gc.HTML(http.StatusOK, template, gin.H{
"target_origin": redirectURI,
"authorization_response": map[string]interface{}{
"type": "authorization_response",
"response": map[string]string{
"error": "invalid_client_id",
},
},
})
}
return
}
if state == "" {
if isQuery {
gc.Redirect(http.StatusFound, loginURL)
} else {
gc.HTML(http.StatusOK, template, gin.H{
"target_origin": redirectURI,
"authorization_response": map[string]interface{}{
"type": "authorization_response",
"response": map[string]string{
"error": "state is required",
},
},
})
}
return
}
if responseType == "" {
responseType = "token"
}
var scope []string
if scopeString == "" {
scope = []string{"openid", "profile", "email"}
} else {
scope = strings.Split(scopeString, " ")
}
isResponseTypeCode := responseType == "code"
isResponseTypeToken := responseType == "token"
if !isResponseTypeCode && !isResponseTypeToken {
if isQuery {
gc.Redirect(http.StatusFound, loginURL)
} else {
gc.HTML(http.StatusOK, template, gin.H{
"target_origin": redirectURI,
"authorization_response": map[string]interface{}{
"type": "authorization_response",
"response": map[string]string{
"error": "response_type is invalid",
},
},
})
}
return
}
if isResponseTypeCode {
if codeChallenge == "" {
if isQuery {
gc.Redirect(http.StatusFound, loginURL)
} else {
gc.HTML(http.StatusBadRequest, template, gin.H{
"target_origin": redirectURI,
"authorization_response": map[string]interface{}{
"type": "authorization_response",
"response": map[string]string{
"error": "code_challenge is required",
},
},
})
}
return
}
}
sessionToken, err := cookie.GetSession(gc)
if err != nil {
if isQuery {
gc.Redirect(http.StatusFound, loginURL)
} else {
gc.HTML(http.StatusOK, template, gin.H{
"target_origin": redirectURI,
"authorization_response": map[string]interface{}{
"type": "authorization_response",
"response": map[string]string{
"error": "login_required",
"error_description": "Login is required",
},
},
})
}
return
}
// get session from cookie
claims, err := token.ValidateBrowserSession(gc, sessionToken)
if err != nil {
if isQuery {
gc.Redirect(http.StatusFound, loginURL)
} else {
gc.HTML(http.StatusOK, template, gin.H{
"target_origin": redirectURI,
"authorization_response": map[string]interface{}{
"type": "authorization_response",
"response": map[string]string{
"error": "login_required",
"error_description": "Login is required",
},
},
})
}
return
}
userID := claims.Subject
user, err := db.Provider.GetUserByID(userID)
if err != nil {
if isQuery {
gc.Redirect(http.StatusFound, loginURL)
} else {
gc.HTML(http.StatusOK, template, gin.H{
"target_origin": redirectURI,
"authorization_response": map[string]interface{}{
"type": "authorization_response",
"response": map[string]string{
"error": "signup_required",
"error_description": "Sign up required",
},
},
})
}
return
}
// if user is logged in
// based on the response type, generate the response
if isResponseTypeCode {
// rollover the session for security
sessionstore.RemoveState(sessionToken)
nonce := uuid.New().String()
newSessionTokenData, newSessionToken, err := token.CreateSessionToken(user, nonce, claims.Roles, scope)
if err != nil {
if isQuery {
gc.Redirect(http.StatusFound, loginURL)
} else {
gc.HTML(http.StatusOK, template, gin.H{
"target_origin": redirectURI,
"authorization_response": map[string]interface{}{
"type": "authorization_response",
"response": map[string]string{
"error": "login_required",
"error_description": "Login is required",
},
},
})
}
return
}
sessionstore.SetState(newSessionToken, newSessionTokenData.Nonce+"@"+user.ID)
cookie.SetSession(gc, newSessionToken)
code := uuid.New().String()
sessionstore.SetState(codeChallenge, code+"@"+newSessionToken)
gc.HTML(http.StatusOK, template, gin.H{
"target_origin": redirectURI,
"authorization_response": map[string]interface{}{
"type": "authorization_response",
"response": map[string]string{
"code": code,
"state": state,
},
},
})
return
}
if isResponseTypeToken {
// rollover the session for security
authToken, err := token.CreateAuthToken(gc, user, claims.Roles, scope)
if err != nil {
if isQuery {
gc.Redirect(http.StatusFound, loginURL)
} else {
gc.HTML(http.StatusOK, template, gin.H{
"target_origin": redirectURI,
"authorization_response": map[string]interface{}{
"type": "authorization_response",
"response": map[string]string{
"error": "login_required",
"error_description": "Login is required",
},
},
})
}
return
}
sessionstore.RemoveState(sessionToken)
sessionstore.SetState(authToken.FingerPrintHash, authToken.FingerPrint+"@"+user.ID)
sessionstore.SetState(authToken.AccessToken.Token, authToken.FingerPrint+"@"+user.ID)
cookie.SetSession(gc, authToken.FingerPrintHash)
expiresIn := int64(1800)
res := map[string]interface{}{
"access_token": authToken.AccessToken.Token,
"id_token": authToken.IDToken.Token,
"state": state,
"scope": scope,
"token_type": "Bearer",
"expires_in": expiresIn,
}
if authToken.RefreshToken != nil {
res["refresh_token"] = authToken.RefreshToken.Token
sessionstore.SetState(authToken.AccessToken.Token, authToken.FingerPrint+"@"+user.ID)
}
gc.HTML(http.StatusOK, template, gin.H{
"target_origin": redirectURI,
"authorization_response": map[string]interface{}{
"type": "authorization_response",
"response": res,
},
})
return
}
if isQuery {
gc.Redirect(http.StatusFound, loginURL)
} else {
// by default return with error
gc.HTML(http.StatusOK, template, gin.H{
"target_origin": redirectURI,
"authorization_response": map[string]interface{}{
"type": "authorization_response",
"response": map[string]string{
"error": "login_required",
"error_description": "Login is required",
},
},
})
}
}
}

View File

@@ -13,7 +13,7 @@ func DashboardHandler() gin.HandlerFunc {
return func(c *gin.Context) { return func(c *gin.Context) {
isOnboardingCompleted := false isOnboardingCompleted := false
if envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyAdminSecret) != "" { if envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyAdminSecret) != "" {
isOnboardingCompleted = true isOnboardingCompleted = true
} }

30
server/handlers/jwks.go Normal file
View File

@@ -0,0 +1,30 @@
package handlers
import (
"encoding/json"
"github.com/authorizerdev/authorizer/server/constants"
"github.com/authorizerdev/authorizer/server/envstore"
"github.com/gin-gonic/gin"
)
func JWKsHandler() gin.HandlerFunc {
return func(c *gin.Context) {
var data map[string]string
jwk := envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyJWK)
err := json.Unmarshal([]byte(jwk), &data)
if err != nil {
c.JSON(500, gin.H{
"error": err.Error(),
})
return
}
c.JSON(200, gin.H{
"keys": []map[string]string{
data,
},
})
}
}

40
server/handlers/logout.go Normal file
View File

@@ -0,0 +1,40 @@
package handlers
import (
"net/http"
"github.com/authorizerdev/authorizer/server/cookie"
"github.com/authorizerdev/authorizer/server/crypto"
"github.com/authorizerdev/authorizer/server/sessionstore"
"github.com/gin-gonic/gin"
)
func LogoutHandler() gin.HandlerFunc {
return func(gc *gin.Context) {
// get fingerprint hash
fingerprintHash, err := cookie.GetSession(gc)
if err != nil {
gc.JSON(http.StatusUnauthorized, gin.H{
"error": err.Error(),
})
return
}
decryptedFingerPrint, err := crypto.DecryptAES(fingerprintHash)
if err != nil {
gc.JSON(http.StatusUnauthorized, gin.H{
"error": err.Error(),
})
return
}
fingerPrint := string(decryptedFingerPrint)
sessionstore.RemoveState(fingerPrint)
cookie.DeleteSession(gc)
gc.JSON(http.StatusOK, gin.H{
"message": "Logged out successfully",
})
}
}

View File

@@ -21,6 +21,7 @@ import (
"github.com/authorizerdev/authorizer/server/utils" "github.com/authorizerdev/authorizer/server/utils"
"github.com/coreos/go-oidc/v3/oidc" "github.com/coreos/go-oidc/v3/oidc"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"github.com/google/uuid"
"golang.org/x/oauth2" "golang.org/x/oauth2"
) )
@@ -30,11 +31,11 @@ func OAuthCallbackHandler() gin.HandlerFunc {
provider := c.Param("oauth_provider") provider := c.Param("oauth_provider")
state := c.Request.FormValue("state") state := c.Request.FormValue("state")
sessionState := sessionstore.GetSocailLoginState(state) sessionState := sessionstore.GetState(state)
if sessionState == "" { if sessionState == "" {
c.JSON(400, gin.H{"error": "invalid oauth state"}) c.JSON(400, gin.H{"error": "invalid oauth state"})
} }
sessionstore.RemoveSocialLoginState(state) sessionstore.GetState(state)
// contains random token, redirect url, role // contains random token, redirect url, role
sessionSplit := strings.Split(state, "___") sessionSplit := strings.Split(state, "___")
@@ -74,7 +75,7 @@ func OAuthCallbackHandler() gin.HandlerFunc {
// make sure inputRoles don't include protected roles // make sure inputRoles don't include protected roles
hasProtectedRole := false hasProtectedRole := false
for _, ir := range inputRoles { for _, ir := range inputRoles {
if utils.StringSliceContains(envstore.EnvInMemoryStoreObj.GetSliceStoreEnvVariable(constants.EnvKeyProtectedRoles), ir) { if utils.StringSliceContains(envstore.EnvStoreObj.GetSliceStoreEnvVariable(constants.EnvKeyProtectedRoles), ir) {
hasProtectedRole = true hasProtectedRole = true
} }
} }
@@ -122,7 +123,7 @@ func OAuthCallbackHandler() gin.HandlerFunc {
// check if it contains protected unassigned role // check if it contains protected unassigned role
hasProtectedRole := false hasProtectedRole := false
for _, ur := range unasignedRoles { for _, ur := range unasignedRoles {
if utils.StringSliceContains(envstore.EnvInMemoryStoreObj.GetSliceStoreEnvVariable(constants.EnvKeyProtectedRoles), ur) { if utils.StringSliceContains(envstore.EnvStoreObj.GetSliceStoreEnvVariable(constants.EnvKeyProtectedRoles), ur) {
hasProtectedRole = true hasProtectedRole = true
} }
} }
@@ -144,11 +145,17 @@ func OAuthCallbackHandler() gin.HandlerFunc {
} }
} }
authToken, _ := token.CreateAuthToken(user, inputRoles) // TODO use query param
sessionstore.SetUserSession(user.ID, authToken.FingerPrint, authToken.RefreshToken.Token) scope := []string{"openid", "email", "profile"}
cookie.SetCookie(c, authToken.AccessToken.Token, authToken.RefreshToken.Token, authToken.FingerPrintHash) nonce := uuid.New().String()
utils.SaveSessionInDB(user.ID, c) _, newSessionToken, err := token.CreateSessionToken(user, nonce, inputRoles, scope)
if err != nil {
c.JSON(500, gin.H{"error": err.Error()})
}
sessionstore.SetState(newSessionToken, nonce+"@"+user.ID)
cookie.SetSession(c, newSessionToken)
go utils.SaveSessionInDB(c, user.ID)
c.Redirect(http.StatusTemporaryRedirect, redirectURL) c.Redirect(http.StatusTemporaryRedirect, redirectURL)
} }
} }
@@ -227,7 +234,7 @@ func processGithubUserInfo(code string) (models.User, error) {
GivenName: &firstName, GivenName: &firstName,
FamilyName: &lastName, FamilyName: &lastName,
Picture: &picture, Picture: &picture,
Email: userRawData["email"], Email: userRawData["sub"],
} }
return user, nil return user, nil
@@ -260,7 +267,7 @@ func processFacebookUserInfo(code string) (models.User, error) {
userRawData := make(map[string]interface{}) userRawData := make(map[string]interface{})
json.Unmarshal(body, &userRawData) json.Unmarshal(body, &userRawData)
email := fmt.Sprintf("%v", userRawData["email"]) email := fmt.Sprintf("%v", userRawData["sub"])
picObject := userRawData["picture"].(map[string]interface{})["data"] picObject := userRawData["picture"].(map[string]interface{})["data"]
picDataObject := picObject.(map[string]interface{}) picDataObject := picObject.(map[string]interface{})

View File

@@ -33,14 +33,14 @@ func OAuthLoginHandler() gin.HandlerFunc {
// use protected roles verification for admin login only. // use protected roles verification for admin login only.
// though if not associated with user, it will be rejected from oauth_callback // though if not associated with user, it will be rejected from oauth_callback
if !utils.IsValidRoles(append([]string{}, append(envstore.EnvInMemoryStoreObj.GetSliceStoreEnvVariable(constants.EnvKeyRoles), envstore.EnvInMemoryStoreObj.GetSliceStoreEnvVariable(constants.EnvKeyProtectedRoles)...)...), rolesSplit) { if !utils.IsValidRoles(append([]string{}, append(envstore.EnvStoreObj.GetSliceStoreEnvVariable(constants.EnvKeyRoles), envstore.EnvStoreObj.GetSliceStoreEnvVariable(constants.EnvKeyProtectedRoles)...)...), rolesSplit) {
c.JSON(400, gin.H{ c.JSON(400, gin.H{
"error": "invalid role", "error": "invalid role",
}) })
return return
} }
} else { } else {
roles = strings.Join(envstore.EnvInMemoryStoreObj.GetSliceStoreEnvVariable(constants.EnvKeyDefaultRoles), ",") roles = strings.Join(envstore.EnvStoreObj.GetSliceStoreEnvVariable(constants.EnvKeyDefaultRoles), ",")
} }
uuid := uuid.New() uuid := uuid.New()
@@ -54,7 +54,7 @@ func OAuthLoginHandler() gin.HandlerFunc {
isProviderConfigured = false isProviderConfigured = false
break break
} }
sessionstore.SetSocailLoginState(oauthStateString, constants.SignupMethodGoogle) sessionstore.SetState(oauthStateString, constants.SignupMethodGoogle)
// during the init of OAuthProvider authorizer url might be empty // during the init of OAuthProvider authorizer url might be empty
oauth.OAuthProviders.GoogleConfig.RedirectURL = hostname + "/oauth_callback/google" oauth.OAuthProviders.GoogleConfig.RedirectURL = hostname + "/oauth_callback/google"
url := oauth.OAuthProviders.GoogleConfig.AuthCodeURL(oauthStateString) url := oauth.OAuthProviders.GoogleConfig.AuthCodeURL(oauthStateString)
@@ -64,7 +64,7 @@ func OAuthLoginHandler() gin.HandlerFunc {
isProviderConfigured = false isProviderConfigured = false
break break
} }
sessionstore.SetSocailLoginState(oauthStateString, constants.SignupMethodGithub) sessionstore.SetState(oauthStateString, constants.SignupMethodGithub)
oauth.OAuthProviders.GithubConfig.RedirectURL = hostname + "/oauth_callback/github" oauth.OAuthProviders.GithubConfig.RedirectURL = hostname + "/oauth_callback/github"
url := oauth.OAuthProviders.GithubConfig.AuthCodeURL(oauthStateString) url := oauth.OAuthProviders.GithubConfig.AuthCodeURL(oauthStateString)
c.Redirect(http.StatusTemporaryRedirect, url) c.Redirect(http.StatusTemporaryRedirect, url)
@@ -73,7 +73,7 @@ func OAuthLoginHandler() gin.HandlerFunc {
isProviderConfigured = false isProviderConfigured = false
break break
} }
sessionstore.SetSocailLoginState(oauthStateString, constants.SignupMethodFacebook) sessionstore.SetState(oauthStateString, constants.SignupMethodFacebook)
oauth.OAuthProviders.FacebookConfig.RedirectURL = hostname + "/oauth_callback/facebook" oauth.OAuthProviders.FacebookConfig.RedirectURL = hostname + "/oauth_callback/facebook"
url := oauth.OAuthProviders.FacebookConfig.AuthCodeURL(oauthStateString) url := oauth.OAuthProviders.FacebookConfig.AuthCodeURL(oauthStateString)
c.Redirect(http.StatusTemporaryRedirect, url) c.Redirect(http.StatusTemporaryRedirect, url)

View File

@@ -0,0 +1,30 @@
package handlers
import (
"github.com/gin-gonic/gin"
"github.com/authorizerdev/authorizer/server/constants"
"github.com/authorizerdev/authorizer/server/envstore"
"github.com/authorizerdev/authorizer/server/utils"
)
// OpenIDConfigurationHandler handler for open-id configurations
func OpenIDConfigurationHandler() gin.HandlerFunc {
return func(c *gin.Context) {
issuer := utils.GetHost(c)
jwtType := envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyJwtType)
c.JSON(200, gin.H{
"issuer": issuer,
"authorization_endpoint": issuer + "/authorize",
"token_endpoint": issuer + "/token",
"userinfo_endpoint": issuer + "/userinfo",
"jwks_uri": issuer + "/.well-known/jwks.json",
"response_types_supported": []string{"code", "token", "id_token", "code token", "code id_token", "token id_token", "code token id_token"},
"scopes_supported": []string{"openid", "email", "profile", "email_verified", "given_name", "family_name", "nick_name", "picture"},
"response_modes_supported": []string{"query", "fragment", "form_post"},
"id_token_signing_alg_values_supported": []string{jwtType},
"claims_supported": []string{"aud", "exp", "iss", "iat", "sub", "given_name", "family_name", "middle_name", "nickname", "preferred_username", "picture", "email", "email_verified", "roles", "gender", "birthdate", "phone_number", "phone_number_verified"},
})
}
}

138
server/handlers/token.go Normal file
View File

@@ -0,0 +1,138 @@
package handlers
import (
"crypto/sha256"
"encoding/base64"
"net/http"
"strings"
"github.com/authorizerdev/authorizer/server/constants"
"github.com/authorizerdev/authorizer/server/cookie"
"github.com/authorizerdev/authorizer/server/db"
"github.com/authorizerdev/authorizer/server/envstore"
"github.com/authorizerdev/authorizer/server/sessionstore"
"github.com/authorizerdev/authorizer/server/token"
"github.com/gin-gonic/gin"
)
func TokenHandler() gin.HandlerFunc {
return func(gc *gin.Context) {
var reqBody map[string]string
if err := gc.BindJSON(&reqBody); err != nil {
gc.JSON(http.StatusBadRequest, gin.H{
"error": "error_binding_json",
"error_description": err.Error(),
})
return
}
codeVerifier := strings.TrimSpace(reqBody["code_verifier"])
code := strings.TrimSpace(reqBody["code"])
clientID := strings.TrimSpace(reqBody["client_id"])
if clientID == "" {
gc.JSON(http.StatusBadRequest, gin.H{
"error": "client_id_required",
"error_description": "The client id is required",
})
return
}
if clientID != envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyClientID) {
gc.JSON(http.StatusBadRequest, gin.H{
"error": "invalid_client_id",
"error_description": "The client id is invalid",
})
return
}
if codeVerifier == "" {
gc.JSON(http.StatusBadRequest, gin.H{
"error": "invalid_code_verifier",
"error_description": "The code verifier is required",
})
return
}
if code == "" {
gc.JSON(http.StatusBadRequest, gin.H{
"error": "invalid_code",
"error_description": "The code is required",
})
return
}
hash := sha256.New()
hash.Write([]byte(codeVerifier))
encryptedCode := strings.ReplaceAll(base64.URLEncoding.EncodeToString(hash.Sum(nil)), "+", "-")
encryptedCode = strings.ReplaceAll(encryptedCode, "/", "_")
encryptedCode = strings.ReplaceAll(encryptedCode, "=", "")
sessionData := sessionstore.GetState(encryptedCode)
if sessionData == "" {
gc.JSON(http.StatusBadRequest, gin.H{
"error": "invalid_code_verifier",
"error_description": "The code verifier is invalid",
})
return
}
// split session data
// it contains code@sessiontoken
sessionDataSplit := strings.Split(sessionData, "@")
if sessionDataSplit[0] != code {
gc.JSON(http.StatusBadRequest, gin.H{
"error": "invalid_code_verifier",
"error_description": "The code verifier is invalid",
})
return
}
// validate session
claims, err := token.ValidateBrowserSession(gc, sessionDataSplit[1])
if err != nil {
gc.JSON(http.StatusUnauthorized, gin.H{
"error": "unauthorized",
"error_description": "Invalid session data",
})
return
}
userID := claims.Subject
user, err := db.Provider.GetUserByID(userID)
if err != nil {
gc.JSON(http.StatusUnauthorized, gin.H{
"error": "unauthorized",
"error_description": "User not found",
})
return
}
// rollover the session for security
sessionstore.RemoveState(sessionDataSplit[1])
authToken, err := token.CreateAuthToken(gc, user, claims.Roles, claims.Scope)
if err != nil {
gc.JSON(http.StatusUnauthorized, gin.H{
"error": "unauthorized",
"error_description": "User not found",
})
return
}
sessionstore.SetState(authToken.FingerPrintHash, authToken.FingerPrint+"@"+user.ID)
sessionstore.SetState(authToken.AccessToken.Token, authToken.FingerPrint+"@"+user.ID)
cookie.SetSession(gc, authToken.FingerPrintHash)
expiresIn := int64(1800)
res := map[string]interface{}{
"access_token": authToken.AccessToken.Token,
"id_token": authToken.IDToken.Token,
"scope": claims.Scope,
"expires_in": expiresIn,
}
if authToken.RefreshToken != nil {
res["refresh_token"] = authToken.RefreshToken.Token
sessionstore.SetState(authToken.AccessToken.Token, authToken.FingerPrint+"@"+user.ID)
}
gc.JSON(http.StatusOK, res)
}
}

View File

@@ -0,0 +1,40 @@
package handlers
import (
"net/http"
"github.com/authorizerdev/authorizer/server/db"
"github.com/authorizerdev/authorizer/server/token"
"github.com/gin-gonic/gin"
)
func UserInfoHandler() gin.HandlerFunc {
return func(gc *gin.Context) {
accessToken, err := token.GetAccessToken(gc)
if err != nil {
gc.JSON(http.StatusUnauthorized, gin.H{
"error": err.Error(),
})
return
}
claims, err := token.ValidateAccessToken(gc, accessToken)
if err != nil {
gc.JSON(http.StatusUnauthorized, gin.H{
"error": err.Error(),
})
return
}
userID := claims["sub"].(string)
user, err := db.Provider.GetUserByID(userID)
if err != nil {
gc.JSON(http.StatusUnauthorized, gin.H{
"error": err.Error(),
})
return
}
gc.JSON(http.StatusOK, user.AsAPIUser())
}
}

View File

@@ -11,6 +11,7 @@ import (
"github.com/authorizerdev/authorizer/server/token" "github.com/authorizerdev/authorizer/server/token"
"github.com/authorizerdev/authorizer/server/utils" "github.com/authorizerdev/authorizer/server/utils"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"github.com/google/uuid"
) )
// VerifyEmailHandler handles the verify email route. // VerifyEmailHandler handles the verify email route.
@@ -18,7 +19,7 @@ import (
func VerifyEmailHandler() gin.HandlerFunc { func VerifyEmailHandler() gin.HandlerFunc {
return func(c *gin.Context) { return func(c *gin.Context) {
errorRes := gin.H{ errorRes := gin.H{
"message": "invalid token", "error": "invalid token",
} }
tokenInQuery := c.Query("token") tokenInQuery := c.Query("token")
if tokenInQuery == "" { if tokenInQuery == "" {
@@ -33,13 +34,21 @@ func VerifyEmailHandler() gin.HandlerFunc {
} }
// verify if token exists in db // verify if token exists in db
claim, err := token.VerifyVerificationToken(tokenInQuery) hostname := utils.GetHost(c)
encryptedNonce, err := utils.EncryptNonce(verificationRequest.Nonce)
if err != nil {
c.JSON(400, gin.H{
"error": err.Error(),
})
return
}
claim, err := token.ParseJWTToken(tokenInQuery, hostname, encryptedNonce, verificationRequest.Email)
if err != nil { if err != nil {
c.JSON(400, errorRes) c.JSON(400, errorRes)
return return
} }
user, err := db.Provider.GetUserByEmail(claim.Email) user, err := db.Provider.GetUserByEmail(claim["sub"].(string))
if err != nil { if err != nil {
c.JSON(400, gin.H{ c.JSON(400, gin.H{
"message": err.Error(), "message": err.Error(),
@@ -57,17 +66,20 @@ func VerifyEmailHandler() gin.HandlerFunc {
db.Provider.DeleteVerificationRequest(verificationRequest) db.Provider.DeleteVerificationRequest(verificationRequest)
roles := strings.Split(user.Roles, ",") roles := strings.Split(user.Roles, ",")
authToken, err := token.CreateAuthToken(user, roles) scope := []string{"openid", "email", "profile"}
nonce := uuid.New().String()
_, authToken, err := token.CreateSessionToken(user, nonce, roles, scope)
if err != nil { if err != nil {
c.JSON(400, gin.H{ c.JSON(400, gin.H{
"message": err.Error(), "message": err.Error(),
}) })
return return
} }
sessionstore.SetUserSession(user.ID, authToken.FingerPrint, authToken.RefreshToken.Token) sessionstore.SetState(authToken, nonce+"@"+user.ID)
cookie.SetCookie(c, authToken.AccessToken.Token, authToken.RefreshToken.Token, authToken.FingerPrintHash) cookie.SetSession(c, authToken)
utils.SaveSessionInDB(user.ID, c)
c.Redirect(http.StatusTemporaryRedirect, claim.RedirectURL) go utils.SaveSessionInDB(c, user.ID)
c.Redirect(http.StatusTemporaryRedirect, claim["redirect_url"].(string))
} }
} }

View File

@@ -2,6 +2,7 @@ package main
import ( import (
"flag" "flag"
"log"
"github.com/authorizerdev/authorizer/server/constants" "github.com/authorizerdev/authorizer/server/constants"
"github.com/authorizerdev/authorizer/server/db" "github.com/authorizerdev/authorizer/server/db"
@@ -20,16 +21,45 @@ func main() {
envstore.ARG_ENV_FILE = flag.String("env_file", "", "Env file path") envstore.ARG_ENV_FILE = flag.String("env_file", "", "Env file path")
flag.Parse() flag.Parse()
envstore.EnvInMemoryStoreObj.UpdateEnvVariable(constants.StringStoreIdentifier, constants.EnvKeyVersion, VERSION) envstore.EnvStoreObj.UpdateEnvVariable(constants.StringStoreIdentifier, constants.EnvKeyVersion, VERSION)
env.InitEnv() // initialize required envs (mainly db & env file path)
db.InitDB() err := env.InitRequiredEnv()
env.PersistEnv() if err != nil {
log.Fatal("Error while initializing required envs:", err)
}
sessionstore.InitSession() // initialize db provider
oauth.InitOAuth() err = db.InitDB()
if err != nil {
log.Fatalln("Error while initializing db:", err)
}
// initialize all envs
// (get if present from db else construct from os env + defaults)
err = env.InitAllEnv()
if err != nil {
log.Fatalln("Error while initializing env: ", err)
}
// persist all envs
err = env.PersistEnv()
if err != nil {
log.Fatalln("Error while persisting env:", err)
}
// initialize session store (redis or in-memory based on env)
err = sessionstore.InitSession()
if err != nil {
log.Fatalln("Error while initializing session store:", err)
}
// initialize oauth providers based on env
err = oauth.InitOAuth()
if err != nil {
log.Fatalln("Error while initializing oauth:", err)
}
router := routes.InitRouter() router := routes.InitRouter()
router.Run(":" + envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyPort))
router.Run(":" + envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyPort))
} }

View File

@@ -2,7 +2,6 @@ package oauth
import ( import (
"context" "context"
"log"
"github.com/authorizerdev/authorizer/server/constants" "github.com/authorizerdev/authorizer/server/constants"
"github.com/authorizerdev/authorizer/server/envstore" "github.com/authorizerdev/authorizer/server/envstore"
@@ -32,37 +31,39 @@ var (
) )
// InitOAuth initializes the OAuth providers based on EnvData // InitOAuth initializes the OAuth providers based on EnvData
func InitOAuth() { func InitOAuth() error {
ctx := context.Background() ctx := context.Background()
if envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyGoogleClientID) != "" && envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyGoogleClientSecret) != "" { if envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyGoogleClientID) != "" && envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyGoogleClientSecret) != "" {
p, err := oidc.NewProvider(ctx, "https://accounts.google.com") p, err := oidc.NewProvider(ctx, "https://accounts.google.com")
if err != nil { if err != nil {
log.Fatalln("error creating oidc provider for google:", err) return err
} }
OIDCProviders.GoogleOIDC = p OIDCProviders.GoogleOIDC = p
OAuthProviders.GoogleConfig = &oauth2.Config{ OAuthProviders.GoogleConfig = &oauth2.Config{
ClientID: envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyGoogleClientID), ClientID: envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyGoogleClientID),
ClientSecret: envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyGoogleClientSecret), ClientSecret: envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyGoogleClientSecret),
RedirectURL: "/oauth_callback/google", RedirectURL: "/oauth_callback/google",
Endpoint: OIDCProviders.GoogleOIDC.Endpoint(), Endpoint: OIDCProviders.GoogleOIDC.Endpoint(),
Scopes: []string{oidc.ScopeOpenID, "profile", "email"}, Scopes: []string{oidc.ScopeOpenID, "profile", "email"},
} }
} }
if envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyGithubClientID) != "" && envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyGithubClientSecret) != "" { if envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyGithubClientID) != "" && envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyGithubClientSecret) != "" {
OAuthProviders.GithubConfig = &oauth2.Config{ OAuthProviders.GithubConfig = &oauth2.Config{
ClientID: envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyGithubClientID), ClientID: envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyGithubClientID),
ClientSecret: envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyGithubClientSecret), ClientSecret: envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyGithubClientSecret),
RedirectURL: "/oauth_callback/github", RedirectURL: "/oauth_callback/github",
Endpoint: githubOAuth2.Endpoint, Endpoint: githubOAuth2.Endpoint,
} }
} }
if envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyFacebookClientID) != "" && envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyGoogleClientID) != "" { if envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyFacebookClientID) != "" && envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyGoogleClientID) != "" {
OAuthProviders.FacebookConfig = &oauth2.Config{ OAuthProviders.FacebookConfig = &oauth2.Config{
ClientID: envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyFacebookClientID), ClientID: envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyFacebookClientID),
ClientSecret: envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyFacebookClientSecret), ClientSecret: envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyFacebookClientSecret),
RedirectURL: "/oauth_callback/facebook", RedirectURL: "/oauth_callback/facebook",
Endpoint: facebookOAuth2.Endpoint, Endpoint: facebookOAuth2.Endpoint,
Scopes: []string{"public_profile", "email"}, Scopes: []string{"public_profile", "email"},
} }
} }
return nil
} }

View File

@@ -6,6 +6,7 @@ import (
"github.com/authorizerdev/authorizer/server/constants" "github.com/authorizerdev/authorizer/server/constants"
"github.com/authorizerdev/authorizer/server/cookie" "github.com/authorizerdev/authorizer/server/cookie"
"github.com/authorizerdev/authorizer/server/crypto"
"github.com/authorizerdev/authorizer/server/envstore" "github.com/authorizerdev/authorizer/server/envstore"
"github.com/authorizerdev/authorizer/server/graph/model" "github.com/authorizerdev/authorizer/server/graph/model"
"github.com/authorizerdev/authorizer/server/utils" "github.com/authorizerdev/authorizer/server/utils"
@@ -20,12 +21,12 @@ func AdminLoginResolver(ctx context.Context, params model.AdminLoginInput) (*mod
return res, err return res, err
} }
adminSecret := envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyAdminSecret) adminSecret := envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyAdminSecret)
if params.AdminSecret != adminSecret { if params.AdminSecret != adminSecret {
return res, fmt.Errorf(`invalid admin secret`) return res, fmt.Errorf(`invalid admin secret`)
} }
hashedKey, err := utils.EncryptPassword(adminSecret) hashedKey, err := crypto.EncryptPassword(adminSecret)
if err != nil { if err != nil {
return res, err return res, err
} }

View File

@@ -6,6 +6,7 @@ import (
"github.com/authorizerdev/authorizer/server/constants" "github.com/authorizerdev/authorizer/server/constants"
"github.com/authorizerdev/authorizer/server/cookie" "github.com/authorizerdev/authorizer/server/cookie"
"github.com/authorizerdev/authorizer/server/crypto"
"github.com/authorizerdev/authorizer/server/envstore" "github.com/authorizerdev/authorizer/server/envstore"
"github.com/authorizerdev/authorizer/server/graph/model" "github.com/authorizerdev/authorizer/server/graph/model"
"github.com/authorizerdev/authorizer/server/token" "github.com/authorizerdev/authorizer/server/token"
@@ -25,7 +26,7 @@ func AdminSessionResolver(ctx context.Context) (*model.Response, error) {
return res, fmt.Errorf("unauthorized") return res, fmt.Errorf("unauthorized")
} }
hashedKey, err := utils.EncryptPassword(envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyAdminSecret)) hashedKey, err := crypto.EncryptPassword(envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyAdminSecret))
if err != nil { if err != nil {
return res, err return res, err
} }

View File

@@ -8,6 +8,7 @@ import (
"github.com/authorizerdev/authorizer/server/constants" "github.com/authorizerdev/authorizer/server/constants"
"github.com/authorizerdev/authorizer/server/cookie" "github.com/authorizerdev/authorizer/server/cookie"
"github.com/authorizerdev/authorizer/server/crypto"
"github.com/authorizerdev/authorizer/server/db" "github.com/authorizerdev/authorizer/server/db"
"github.com/authorizerdev/authorizer/server/envstore" "github.com/authorizerdev/authorizer/server/envstore"
"github.com/authorizerdev/authorizer/server/graph/model" "github.com/authorizerdev/authorizer/server/graph/model"
@@ -33,18 +34,18 @@ func AdminSignupResolver(ctx context.Context, params model.AdminSignupInput) (*m
return res, err return res, err
} }
adminSecret := envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyAdminSecret) adminSecret := envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyAdminSecret)
if adminSecret != "" { if adminSecret != "" {
err = fmt.Errorf("admin sign up already completed") err = fmt.Errorf("admin sign up already completed")
return res, err return res, err
} }
envstore.EnvInMemoryStoreObj.UpdateEnvVariable(constants.StringStoreIdentifier, constants.EnvKeyAdminSecret, params.AdminSecret) envstore.EnvStoreObj.UpdateEnvVariable(constants.StringStoreIdentifier, constants.EnvKeyAdminSecret, params.AdminSecret)
// consvert EnvData to JSON // consvert EnvData to JSON
var storeData envstore.Store var storeData envstore.Store
jsonBytes, err := json.Marshal(envstore.EnvInMemoryStoreObj.GetEnvStoreClone()) jsonBytes, err := json.Marshal(envstore.EnvStoreObj.GetEnvStoreClone())
if err != nil { if err != nil {
return res, err return res, err
} }
@@ -58,7 +59,7 @@ func AdminSignupResolver(ctx context.Context, params model.AdminSignupInput) (*m
return res, err return res, err
} }
envData, err := utils.EncryptEnvData(storeData) envData, err := crypto.EncryptEnvData(storeData)
if err != nil { if err != nil {
return res, err return res, err
} }
@@ -68,7 +69,7 @@ func AdminSignupResolver(ctx context.Context, params model.AdminSignupInput) (*m
return res, err return res, err
} }
hashedKey, err := utils.EncryptPassword(params.AdminSecret) hashedKey, err := crypto.EncryptPassword(params.AdminSecret)
if err != nil { if err != nil {
return res, err return res, err
} }

View File

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

View File

@@ -26,8 +26,10 @@ func EnvResolver(ctx context.Context) (*model.Env, error) {
} }
// get clone of store // get clone of store
store := envstore.EnvInMemoryStoreObj.GetEnvStoreClone() store := envstore.EnvStoreObj.GetEnvStoreClone()
adminSecret := store.StringEnv[constants.EnvKeyAdminSecret] adminSecret := store.StringEnv[constants.EnvKeyAdminSecret]
clientID := store.StringEnv[constants.EnvKeyClientID]
clientSecret := store.StringEnv[constants.EnvKeyClientSecret]
databaseURL := store.StringEnv[constants.EnvKeyDatabaseURL] databaseURL := store.StringEnv[constants.EnvKeyDatabaseURL]
databaseName := store.StringEnv[constants.EnvKeyDatabaseName] databaseName := store.StringEnv[constants.EnvKeyDatabaseName]
databaseType := store.StringEnv[constants.EnvKeyDatabaseType] databaseType := store.StringEnv[constants.EnvKeyDatabaseType]
@@ -40,6 +42,8 @@ func EnvResolver(ctx context.Context) (*model.Env, error) {
jwtType := store.StringEnv[constants.EnvKeyJwtType] jwtType := store.StringEnv[constants.EnvKeyJwtType]
jwtSecret := store.StringEnv[constants.EnvKeyJwtSecret] jwtSecret := store.StringEnv[constants.EnvKeyJwtSecret]
jwtRoleClaim := store.StringEnv[constants.EnvKeyJwtRoleClaim] jwtRoleClaim := store.StringEnv[constants.EnvKeyJwtRoleClaim]
jwtPublicKey := store.StringEnv[constants.EnvKeyJwtPublicKey]
jwtPrivateKey := store.StringEnv[constants.EnvKeyJwtPrivateKey]
allowedOrigins := store.SliceEnv[constants.EnvKeyAllowedOrigins] allowedOrigins := store.SliceEnv[constants.EnvKeyAllowedOrigins]
appURL := store.StringEnv[constants.EnvKeyAppURL] appURL := store.StringEnv[constants.EnvKeyAppURL]
redisURL := store.StringEnv[constants.EnvKeyRedisURL] redisURL := store.StringEnv[constants.EnvKeyRedisURL]
@@ -63,9 +67,11 @@ func EnvResolver(ctx context.Context) (*model.Env, error) {
res = &model.Env{ res = &model.Env{
AdminSecret: &adminSecret, AdminSecret: &adminSecret,
DatabaseName: &databaseName, DatabaseName: databaseName,
DatabaseURL: &databaseURL, DatabaseURL: databaseURL,
DatabaseType: &databaseType, DatabaseType: databaseType,
ClientID: clientID,
ClientSecret: clientSecret,
CustomAccessTokenScript: &customAccessTokenScript, CustomAccessTokenScript: &customAccessTokenScript,
SMTPHost: &smtpHost, SMTPHost: &smtpHost,
SMTPPort: &smtpPort, SMTPPort: &smtpPort,
@@ -74,6 +80,8 @@ func EnvResolver(ctx context.Context) (*model.Env, error) {
SenderEmail: &senderEmail, SenderEmail: &senderEmail,
JwtType: &jwtType, JwtType: &jwtType,
JwtSecret: &jwtSecret, JwtSecret: &jwtSecret,
JwtPrivateKey: &jwtPrivateKey,
JwtPublicKey: &jwtPublicKey,
JwtRoleClaim: &jwtRoleClaim, JwtRoleClaim: &jwtRoleClaim,
AllowedOrigins: allowedOrigins, AllowedOrigins: allowedOrigins,
AppURL: &appURL, AppURL: &appURL,

View File

@@ -24,7 +24,7 @@ func ForgotPasswordResolver(ctx context.Context, params model.ForgotPasswordInpu
if err != nil { if err != nil {
return res, err return res, err
} }
if envstore.EnvInMemoryStoreObj.GetBoolStoreEnvVariable(constants.EnvKeyDisableBasicAuthentication) { if envstore.EnvStoreObj.GetBoolStoreEnvVariable(constants.EnvKeyDisableBasicAuthentication) {
return res, fmt.Errorf(`basic authentication is disabled for this instance`) return res, fmt.Errorf(`basic authentication is disabled for this instance`)
} }
params.Email = strings.ToLower(params.Email) params.Email = strings.ToLower(params.Email)
@@ -39,7 +39,11 @@ func ForgotPasswordResolver(ctx context.Context, params model.ForgotPasswordInpu
} }
hostname := utils.GetHost(gc) hostname := utils.GetHost(gc)
verificationToken, err := token.CreateVerificationToken(params.Email, constants.VerificationTypeForgotPassword, hostname) nonce, nonceHash, err := utils.GenerateNonce()
if err != nil {
return res, err
}
verificationToken, err := token.CreateVerificationToken(params.Email, constants.VerificationTypeForgotPassword, hostname, nonceHash)
if err != nil { if err != nil {
log.Println(`error generating token`, err) log.Println(`error generating token`, err)
} }
@@ -48,12 +52,11 @@ func ForgotPasswordResolver(ctx context.Context, params model.ForgotPasswordInpu
Identifier: constants.VerificationTypeForgotPassword, Identifier: constants.VerificationTypeForgotPassword,
ExpiresAt: time.Now().Add(time.Minute * 30).Unix(), ExpiresAt: time.Now().Add(time.Minute * 30).Unix(),
Email: params.Email, Email: params.Email,
Nonce: nonce,
}) })
// exec it as go routin so that we can reduce the api latency // exec it as go routin so that we can reduce the api latency
go func() { go email.SendForgotPasswordMail(params.Email, verificationToken, hostname)
email.SendForgotPasswordMail(params.Email, verificationToken, hostname)
}()
res = &model.Response{ res = &model.Response{
Message: `Please check your inbox! We have sent a password reset link.`, Message: `Please check your inbox! We have sent a password reset link.`,

View File

@@ -1,52 +0,0 @@
package resolvers
import (
"context"
"errors"
"fmt"
"github.com/authorizerdev/authorizer/server/constants"
"github.com/authorizerdev/authorizer/server/envstore"
"github.com/authorizerdev/authorizer/server/graph/model"
"github.com/authorizerdev/authorizer/server/token"
tokenHelper "github.com/authorizerdev/authorizer/server/token"
"github.com/authorizerdev/authorizer/server/utils"
)
// IsValidJwtResolver resolver to return if given jwt is valid
func IsValidJwtResolver(ctx context.Context, params *model.IsValidJWTQueryInput) (*model.ValidJWTResponse, error) {
gc, err := utils.GinContextFromContext(ctx)
token, err := token.GetAccessToken(gc)
if token == "" || err != nil {
if params != nil && *params.Jwt != "" {
token = *params.Jwt
} else {
return nil, errors.New("no jwt provided via cookie / header / params")
}
}
claims, err := tokenHelper.VerifyJWTToken(token)
if err != nil {
return nil, err
}
claimRoleInterface := claims[envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyJwtRoleClaim)].([]interface{})
claimRoles := []string{}
for _, v := range claimRoleInterface {
claimRoles = append(claimRoles, v.(string))
}
if params != nil && params.Roles != nil && len(params.Roles) > 0 {
for _, v := range params.Roles {
if !utils.StringSliceContains(claimRoles, v) {
return nil, fmt.Errorf(`unauthorized`)
}
}
}
return &model.ValidJWTResponse{
Valid: true,
Message: "Valid JWT",
}, nil
}

View File

@@ -25,7 +25,7 @@ func LoginResolver(ctx context.Context, params model.LoginInput) (*model.AuthRes
return res, err return res, err
} }
if envstore.EnvInMemoryStoreObj.GetBoolStoreEnvVariable(constants.EnvKeyDisableBasicAuthentication) { if envstore.EnvStoreObj.GetBoolStoreEnvVariable(constants.EnvKeyDisableBasicAuthentication) {
return res, fmt.Errorf(`basic authentication is disabled for this instance`) return res, fmt.Errorf(`basic authentication is disabled for this instance`)
} }
@@ -49,7 +49,7 @@ func LoginResolver(ctx context.Context, params model.LoginInput) (*model.AuthRes
log.Println("compare password error:", err) log.Println("compare password error:", err)
return res, fmt.Errorf(`invalid password`) return res, fmt.Errorf(`invalid password`)
} }
roles := envstore.EnvInMemoryStoreObj.GetSliceStoreEnvVariable(constants.EnvKeyDefaultRoles) roles := envstore.EnvStoreObj.GetSliceStoreEnvVariable(constants.EnvKeyDefaultRoles)
currentRoles := strings.Split(user.Roles, ",") currentRoles := strings.Split(user.Roles, ",")
if len(params.Roles) > 0 { if len(params.Roles) > 0 {
if !utils.IsValidRoles(currentRoles, params.Roles) { if !utils.IsValidRoles(currentRoles, params.Roles) {
@@ -59,20 +59,36 @@ func LoginResolver(ctx context.Context, params model.LoginInput) (*model.AuthRes
roles = params.Roles roles = params.Roles
} }
authToken, err := token.CreateAuthToken(user, roles) scope := []string{"openid", "email", "profile"}
if params.Scope != nil && len(scope) > 0 {
scope = params.Scope
}
authToken, err := token.CreateAuthToken(gc, user, roles, scope)
if err != nil { if err != nil {
return res, err return res, err
} }
sessionstore.SetUserSession(user.ID, authToken.FingerPrint, authToken.RefreshToken.Token)
cookie.SetCookie(gc, authToken.AccessToken.Token, authToken.RefreshToken.Token, authToken.FingerPrintHash)
utils.SaveSessionInDB(user.ID, gc)
cookie.SetSession(gc, authToken.FingerPrintHash)
expiresIn := int64(1800)
res = &model.AuthResponse{ res = &model.AuthResponse{
Message: `Logged in successfully`, Message: `Logged in successfully`,
AccessToken: &authToken.AccessToken.Token, AccessToken: &authToken.AccessToken.Token,
ExpiresAt: &authToken.AccessToken.ExpiresAt, IDToken: &authToken.IDToken.Token,
ExpiresIn: &expiresIn,
User: user.AsAPIUser(), User: user.AsAPIUser(),
} }
sessionstore.SetState(authToken.FingerPrintHash, authToken.FingerPrint+"@"+user.ID)
sessionstore.SetState(authToken.AccessToken.Token, authToken.FingerPrint+"@"+user.ID)
if authToken.RefreshToken != nil {
res.RefreshToken = &authToken.RefreshToken.Token
sessionstore.SetState(authToken.AccessToken.Token, authToken.FingerPrint+"@"+user.ID)
}
go utils.SaveSessionInDB(gc, user.ID)
return res, nil return res, nil
} }

View File

@@ -4,9 +4,9 @@ import (
"context" "context"
"github.com/authorizerdev/authorizer/server/cookie" "github.com/authorizerdev/authorizer/server/cookie"
"github.com/authorizerdev/authorizer/server/crypto"
"github.com/authorizerdev/authorizer/server/graph/model" "github.com/authorizerdev/authorizer/server/graph/model"
"github.com/authorizerdev/authorizer/server/sessionstore" "github.com/authorizerdev/authorizer/server/sessionstore"
"github.com/authorizerdev/authorizer/server/token"
"github.com/authorizerdev/authorizer/server/utils" "github.com/authorizerdev/authorizer/server/utils"
) )
@@ -18,34 +18,21 @@ func LogoutResolver(ctx context.Context) (*model.Response, error) {
return res, err return res, err
} }
// get refresh token
refreshToken, err := token.GetRefreshToken(gc)
if err != nil {
return res, err
}
// get fingerprint hash // get fingerprint hash
fingerprintHash, err := token.GetFingerPrint(gc) fingerprintHash, err := cookie.GetSession(gc)
if err != nil { if err != nil {
return res, err return res, err
} }
decryptedFingerPrint, err := utils.DecryptAES([]byte(fingerprintHash)) decryptedFingerPrint, err := crypto.DecryptAES(fingerprintHash)
if err != nil { if err != nil {
return res, err return res, err
} }
fingerPrint := string(decryptedFingerPrint) fingerPrint := string(decryptedFingerPrint)
// verify refresh token and fingerprint sessionstore.RemoveState(fingerPrint)
claims, err := token.VerifyJWTToken(refreshToken) cookie.DeleteSession(gc)
if err != nil {
return res, err
}
userID := claims["id"].(string)
sessionstore.DeleteUserSession(userID, fingerPrint)
cookie.DeleteCookie(gc)
res = &model.Response{ res = &model.Response{
Message: "Logged out successfully", Message: "Logged out successfully",

View File

@@ -25,7 +25,7 @@ func MagicLinkLoginResolver(ctx context.Context, params model.MagicLinkLoginInpu
return res, err return res, err
} }
if envstore.EnvInMemoryStoreObj.GetBoolStoreEnvVariable(constants.EnvKeyDisableMagicLinkLogin) { if envstore.EnvStoreObj.GetBoolStoreEnvVariable(constants.EnvKeyDisableMagicLinkLogin) {
return res, fmt.Errorf(`magic link login is disabled for this instance`) return res, fmt.Errorf(`magic link login is disabled for this instance`)
} }
@@ -49,13 +49,13 @@ func MagicLinkLoginResolver(ctx context.Context, params model.MagicLinkLoginInpu
// define roles for new user // define roles for new user
if len(params.Roles) > 0 { if len(params.Roles) > 0 {
// check if roles exists // check if roles exists
if !utils.IsValidRoles(envstore.EnvInMemoryStoreObj.GetSliceStoreEnvVariable(constants.EnvKeyRoles), params.Roles) { if !utils.IsValidRoles(envstore.EnvStoreObj.GetSliceStoreEnvVariable(constants.EnvKeyRoles), params.Roles) {
return res, fmt.Errorf(`invalid roles`) return res, fmt.Errorf(`invalid roles`)
} else { } else {
inputRoles = params.Roles inputRoles = params.Roles
} }
} else { } else {
inputRoles = envstore.EnvInMemoryStoreObj.GetSliceStoreEnvVariable(constants.EnvKeyDefaultRoles) inputRoles = envstore.EnvStoreObj.GetSliceStoreEnvVariable(constants.EnvKeyDefaultRoles)
} }
user.Roles = strings.Join(inputRoles, ",") user.Roles = strings.Join(inputRoles, ",")
@@ -80,7 +80,7 @@ func MagicLinkLoginResolver(ctx context.Context, params model.MagicLinkLoginInpu
// check if it contains protected unassigned role // check if it contains protected unassigned role
hasProtectedRole := false hasProtectedRole := false
for _, ur := range unasignedRoles { for _, ur := range unasignedRoles {
if utils.StringSliceContains(envstore.EnvInMemoryStoreObj.GetSliceStoreEnvVariable(constants.EnvKeyProtectedRoles), ur) { if utils.StringSliceContains(envstore.EnvStoreObj.GetSliceStoreEnvVariable(constants.EnvKeyProtectedRoles), ur) {
hasProtectedRole = true hasProtectedRole = true
} }
} }
@@ -107,10 +107,14 @@ func MagicLinkLoginResolver(ctx context.Context, params model.MagicLinkLoginInpu
} }
hostname := utils.GetHost(gc) hostname := utils.GetHost(gc)
if !envstore.EnvInMemoryStoreObj.GetBoolStoreEnvVariable(constants.EnvKeyDisableEmailVerification) { if !envstore.EnvStoreObj.GetBoolStoreEnvVariable(constants.EnvKeyDisableEmailVerification) {
// insert verification request // insert verification request
nonce, nonceHash, err := utils.GenerateNonce()
if err != nil {
return res, err
}
verificationType := constants.VerificationTypeMagicLinkLogin verificationType := constants.VerificationTypeMagicLinkLogin
verificationToken, err := token.CreateVerificationToken(params.Email, verificationType, hostname) verificationToken, err := token.CreateVerificationToken(params.Email, verificationType, hostname, nonceHash)
if err != nil { if err != nil {
log.Println(`error generating token`, err) log.Println(`error generating token`, err)
} }
@@ -119,12 +123,11 @@ func MagicLinkLoginResolver(ctx context.Context, params model.MagicLinkLoginInpu
Identifier: verificationType, Identifier: verificationType,
ExpiresAt: time.Now().Add(time.Minute * 30).Unix(), ExpiresAt: time.Now().Add(time.Minute * 30).Unix(),
Email: params.Email, Email: params.Email,
Nonce: nonce,
}) })
// exec it as go routin so that we can reduce the api latency // exec it as go routin so that we can reduce the api latency
go func() { go email.SendVerificationMail(params.Email, verificationToken, hostname)
email.SendVerificationMail(params.Email, verificationToken, hostname)
}()
} }
res = &model.Response{ res = &model.Response{

View File

@@ -2,7 +2,6 @@ package resolvers
import ( import (
"context" "context"
"fmt"
"github.com/authorizerdev/authorizer/server/db" "github.com/authorizerdev/authorizer/server/db"
"github.com/authorizerdev/authorizer/server/graph/model" "github.com/authorizerdev/authorizer/server/graph/model"
@@ -18,13 +17,17 @@ func ProfileResolver(ctx context.Context) (*model.User, error) {
return res, err return res, err
} }
claims, err := token.ValidateAccessToken(gc) accessToken, err := token.GetAccessToken(gc)
if err != nil { if err != nil {
return res, err return res, err
} }
userID := fmt.Sprintf("%v", claims["id"]) claims, err := token.ValidateAccessToken(gc, accessToken)
if err != nil {
return res, err
}
userID := claims["sub"].(string)
user, err := db.Provider.GetUserByID(userID) user, err := db.Provider.GetUserByID(userID)
if err != nil { if err != nil {
return res, err return res, err

View File

@@ -44,7 +44,11 @@ func ResendVerifyEmailResolver(ctx context.Context, params model.ResendVerifyEma
} }
hostname := utils.GetHost(gc) hostname := utils.GetHost(gc)
verificationToken, err := token.CreateVerificationToken(params.Email, params.Identifier, hostname) nonce, nonceHash, err := utils.GenerateNonce()
if err != nil {
return res, err
}
verificationToken, err := token.CreateVerificationToken(params.Email, params.Identifier, hostname, nonceHash)
if err != nil { if err != nil {
log.Println(`error generating token`, err) log.Println(`error generating token`, err)
} }
@@ -53,12 +57,11 @@ func ResendVerifyEmailResolver(ctx context.Context, params model.ResendVerifyEma
Identifier: params.Identifier, Identifier: params.Identifier,
ExpiresAt: time.Now().Add(time.Minute * 30).Unix(), ExpiresAt: time.Now().Add(time.Minute * 30).Unix(),
Email: params.Email, Email: params.Email,
Nonce: nonce,
}) })
// exec it as go routin so that we can reduce the api latency // exec it as go routin so that we can reduce the api latency
go func() { go email.SendVerificationMail(params.Email, verificationToken, hostname)
email.SendVerificationMail(params.Email, verificationToken, hostname)
}()
res = &model.Response{ res = &model.Response{
Message: `Verification email has been sent. Please check your inbox`, Message: `Verification email has been sent. Please check your inbox`,

View File

@@ -7,6 +7,7 @@ import (
"time" "time"
"github.com/authorizerdev/authorizer/server/constants" "github.com/authorizerdev/authorizer/server/constants"
"github.com/authorizerdev/authorizer/server/crypto"
"github.com/authorizerdev/authorizer/server/db" "github.com/authorizerdev/authorizer/server/db"
"github.com/authorizerdev/authorizer/server/envstore" "github.com/authorizerdev/authorizer/server/envstore"
"github.com/authorizerdev/authorizer/server/graph/model" "github.com/authorizerdev/authorizer/server/graph/model"
@@ -17,7 +18,11 @@ import (
// ResetPasswordResolver is a resolver for reset password mutation // ResetPasswordResolver is a resolver for reset password mutation
func ResetPasswordResolver(ctx context.Context, params model.ResetPasswordInput) (*model.Response, error) { func ResetPasswordResolver(ctx context.Context, params model.ResetPasswordInput) (*model.Response, error) {
var res *model.Response var res *model.Response
if envstore.EnvInMemoryStoreObj.GetBoolStoreEnvVariable(constants.EnvKeyDisableBasicAuthentication) { gc, err := utils.GinContextFromContext(ctx)
if err != nil {
return res, err
}
if envstore.EnvStoreObj.GetBoolStoreEnvVariable(constants.EnvKeyDisableBasicAuthentication) {
return res, fmt.Errorf(`basic authentication is disabled for this instance`) return res, fmt.Errorf(`basic authentication is disabled for this instance`)
} }
@@ -31,17 +36,22 @@ func ResetPasswordResolver(ctx context.Context, params model.ResetPasswordInput)
} }
// verify if token exists in db // verify if token exists in db
claim, err := token.VerifyVerificationToken(params.Token) hostname := utils.GetHost(gc)
encryptedNonce, err := utils.EncryptNonce(verificationRequest.Nonce)
if err != nil {
return res, err
}
claim, err := token.ParseJWTToken(params.Token, hostname, encryptedNonce, verificationRequest.Email)
if err != nil { if err != nil {
return res, fmt.Errorf(`invalid token`) return res, fmt.Errorf(`invalid token`)
} }
user, err := db.Provider.GetUserByEmail(claim.Email) user, err := db.Provider.GetUserByEmail(claim["sub"].(string))
if err != nil { if err != nil {
return res, err return res, err
} }
password, _ := utils.EncryptPassword(params.Password) password, _ := crypto.EncryptPassword(params.Password)
user.Password = &password user.Password = &password
signupMethod := user.SignupMethods signupMethod := user.SignupMethods

View File

@@ -13,6 +13,7 @@ import (
) )
// SessionResolver is a resolver for session query // SessionResolver is a resolver for session query
// TODO allow validating with code and code verifier instead of cookie (PKCE flow)
func SessionResolver(ctx context.Context, params *model.SessionQueryInput) (*model.AuthResponse, error) { func SessionResolver(ctx context.Context, params *model.SessionQueryInput) (*model.AuthResponse, error) {
var res *model.AuthResponse var res *model.AuthResponse
@@ -21,48 +22,27 @@ func SessionResolver(ctx context.Context, params *model.SessionQueryInput) (*mod
return res, err return res, err
} }
// get refresh token sessionToken, err := cookie.GetSession(gc)
refreshToken, err := token.GetRefreshToken(gc)
if err != nil { if err != nil {
return res, err return res, err
} }
// get fingerprint hash // get session from cookie
fingerprintHash, err := token.GetFingerPrint(gc) claims, err := token.ValidateBrowserSession(gc, sessionToken)
if err != nil { if err != nil {
return res, err return res, err
} }
userID := claims.Subject
decryptedFingerPrint, err := utils.DecryptAES([]byte(fingerprintHash))
if err != nil {
return res, err
}
fingerPrint := string(decryptedFingerPrint)
// verify refresh token and fingerprint
claims, err := token.VerifyJWTToken(refreshToken)
if err != nil {
return res, err
}
userID := claims["id"].(string)
persistedRefresh := sessionstore.GetUserSession(userID, fingerPrint)
if refreshToken != persistedRefresh {
return res, fmt.Errorf(`unauthorized`)
}
user, err := db.Provider.GetUserByID(userID) user, err := db.Provider.GetUserByID(userID)
if err != nil { if err != nil {
return res, err return res, err
} }
// refresh token has "roles" as claim // refresh token has "roles" as claim
claimRoleInterface := claims["roles"].([]interface{}) claimRoleInterface := claims.Roles
claimRoles := []string{} claimRoles := []string{}
for _, v := range claimRoleInterface { for _, v := range claimRoleInterface {
claimRoles = append(claimRoles, v.(string)) claimRoles = append(claimRoles, v)
} }
if params != nil && params.Roles != nil && len(params.Roles) > 0 { if params != nil && params.Roles != nil && len(params.Roles) > 0 {
@@ -73,22 +53,35 @@ func SessionResolver(ctx context.Context, params *model.SessionQueryInput) (*mod
} }
} }
// delete older session scope := []string{"openid", "email", "profile"}
sessionstore.DeleteUserSession(userID, fingerPrint) if params != nil && params.Scope != nil && len(scope) > 0 {
scope = params.Scope
}
authToken, err := token.CreateAuthToken(user, claimRoles) authToken, err := token.CreateAuthToken(gc, user, claimRoles, scope)
if err != nil { if err != nil {
return res, err return res, err
} }
sessionstore.SetUserSession(user.ID, authToken.FingerPrint, authToken.RefreshToken.Token)
cookie.SetCookie(gc, authToken.AccessToken.Token, authToken.RefreshToken.Token, authToken.FingerPrintHash)
// rollover the session for security
sessionstore.RemoveState(sessionToken)
sessionstore.SetState(authToken.FingerPrintHash, authToken.FingerPrint+"@"+user.ID)
sessionstore.SetState(authToken.AccessToken.Token, authToken.FingerPrint+"@"+user.ID)
cookie.SetSession(gc, authToken.FingerPrintHash)
expiresIn := int64(1800)
res = &model.AuthResponse{ res = &model.AuthResponse{
Message: `Session token refreshed`, Message: `Session token refreshed`,
AccessToken: &authToken.AccessToken.Token, AccessToken: &authToken.AccessToken.Token,
ExpiresAt: &authToken.AccessToken.ExpiresAt, ExpiresIn: &expiresIn,
IDToken: &authToken.IDToken.Token,
User: user.AsAPIUser(), User: user.AsAPIUser(),
} }
if authToken.RefreshToken != nil {
res.RefreshToken = &authToken.RefreshToken.Token
sessionstore.SetState(authToken.AccessToken.Token, authToken.FingerPrint+"@"+user.ID)
}
return res, nil return res, nil
} }

View File

@@ -9,6 +9,7 @@ import (
"github.com/authorizerdev/authorizer/server/constants" "github.com/authorizerdev/authorizer/server/constants"
"github.com/authorizerdev/authorizer/server/cookie" "github.com/authorizerdev/authorizer/server/cookie"
"github.com/authorizerdev/authorizer/server/crypto"
"github.com/authorizerdev/authorizer/server/db" "github.com/authorizerdev/authorizer/server/db"
"github.com/authorizerdev/authorizer/server/db/models" "github.com/authorizerdev/authorizer/server/db/models"
"github.com/authorizerdev/authorizer/server/email" "github.com/authorizerdev/authorizer/server/email"
@@ -27,7 +28,7 @@ func SignupResolver(ctx context.Context, params model.SignUpInput) (*model.AuthR
return res, err return res, err
} }
if envstore.EnvInMemoryStoreObj.GetBoolStoreEnvVariable(constants.EnvKeyDisableBasicAuthentication) { if envstore.EnvStoreObj.GetBoolStoreEnvVariable(constants.EnvKeyDisableBasicAuthentication) {
return res, fmt.Errorf(`basic authentication is disabled for this instance`) return res, fmt.Errorf(`basic authentication is disabled for this instance`)
} }
if params.ConfirmPassword != params.Password { if params.ConfirmPassword != params.Password {
@@ -57,13 +58,13 @@ func SignupResolver(ctx context.Context, params model.SignUpInput) (*model.AuthR
if len(params.Roles) > 0 { if len(params.Roles) > 0 {
// check if roles exists // check if roles exists
if !utils.IsValidRoles(envstore.EnvInMemoryStoreObj.GetSliceStoreEnvVariable(constants.EnvKeyRoles), params.Roles) { if !utils.IsValidRoles(envstore.EnvStoreObj.GetSliceStoreEnvVariable(constants.EnvKeyRoles), params.Roles) {
return res, fmt.Errorf(`invalid roles`) return res, fmt.Errorf(`invalid roles`)
} else { } else {
inputRoles = params.Roles inputRoles = params.Roles
} }
} else { } else {
inputRoles = envstore.EnvInMemoryStoreObj.GetSliceStoreEnvVariable(constants.EnvKeyDefaultRoles) inputRoles = envstore.EnvStoreObj.GetSliceStoreEnvVariable(constants.EnvKeyDefaultRoles)
} }
user := models.User{ user := models.User{
@@ -72,7 +73,7 @@ func SignupResolver(ctx context.Context, params model.SignUpInput) (*model.AuthR
user.Roles = strings.Join(inputRoles, ",") user.Roles = strings.Join(inputRoles, ",")
password, _ := utils.EncryptPassword(params.Password) password, _ := crypto.EncryptPassword(params.Password)
user.Password = &password user.Password = &password
if params.GivenName != nil { if params.GivenName != nil {
@@ -108,7 +109,7 @@ func SignupResolver(ctx context.Context, params model.SignUpInput) (*model.AuthR
} }
user.SignupMethods = constants.SignupMethodBasicAuth user.SignupMethods = constants.SignupMethodBasicAuth
if envstore.EnvInMemoryStoreObj.GetBoolStoreEnvVariable(constants.EnvKeyDisableEmailVerification) { if envstore.EnvStoreObj.GetBoolStoreEnvVariable(constants.EnvKeyDisableEmailVerification) {
now := time.Now().Unix() now := time.Now().Unix()
user.EmailVerifiedAt = &now user.EmailVerifiedAt = &now
} }
@@ -120,43 +121,50 @@ func SignupResolver(ctx context.Context, params model.SignUpInput) (*model.AuthR
userToReturn := user.AsAPIUser() userToReturn := user.AsAPIUser()
hostname := utils.GetHost(gc) hostname := utils.GetHost(gc)
if !envstore.EnvInMemoryStoreObj.GetBoolStoreEnvVariable(constants.EnvKeyDisableEmailVerification) { if !envstore.EnvStoreObj.GetBoolStoreEnvVariable(constants.EnvKeyDisableEmailVerification) {
// insert verification request // insert verification request
verificationType := constants.VerificationTypeBasicAuthSignup nonce, nonceHash, err := utils.GenerateNonce()
verificationToken, err := token.CreateVerificationToken(params.Email, verificationType, hostname)
if err != nil { if err != nil {
log.Println(`error generating token`, err) return res, err
}
verificationType := constants.VerificationTypeBasicAuthSignup
verificationToken, err := token.CreateVerificationToken(params.Email, verificationType, hostname, nonceHash)
if err != nil {
return res, err
} }
db.Provider.AddVerificationRequest(models.VerificationRequest{ db.Provider.AddVerificationRequest(models.VerificationRequest{
Token: verificationToken, Token: verificationToken,
Identifier: verificationType, Identifier: verificationType,
ExpiresAt: time.Now().Add(time.Minute * 30).Unix(), ExpiresAt: time.Now().Add(time.Minute * 30).Unix(),
Email: params.Email, Email: params.Email,
Nonce: nonce,
}) })
// exec it as go routin so that we can reduce the api latency // exec it as go routin so that we can reduce the api latency
go func() { go email.SendVerificationMail(params.Email, verificationToken, hostname)
email.SendVerificationMail(params.Email, verificationToken, hostname)
}()
res = &model.AuthResponse{ res = &model.AuthResponse{
Message: `Verification email has been sent. Please check your inbox`, Message: `Verification email has been sent. Please check your inbox`,
User: userToReturn, User: userToReturn,
} }
} else { } else {
scope := []string{"openid", "email", "profile"}
authToken, err := token.CreateAuthToken(user, roles) authToken, err := token.CreateAuthToken(gc, user, roles, scope)
if err != nil { if err != nil {
return res, err return res, err
} }
sessionstore.SetUserSession(user.ID, authToken.FingerPrint, authToken.RefreshToken.Token)
cookie.SetCookie(gc, authToken.AccessToken.Token, authToken.RefreshToken.Token, authToken.FingerPrintHash) sessionstore.SetState(authToken.FingerPrintHash, authToken.FingerPrint+"@"+user.ID)
utils.SaveSessionInDB(user.ID, gc) cookie.SetSession(gc, authToken.FingerPrintHash)
go utils.SaveSessionInDB(gc, user.ID)
expiresIn := int64(1800)
res = &model.AuthResponse{ res = &model.AuthResponse{
Message: `Signed up successfully.`, Message: `Signed up successfully.`,
AccessToken: &authToken.AccessToken.Token, AccessToken: &authToken.AccessToken.Token,
ExpiresAt: &authToken.AccessToken.ExpiresAt, ExpiresIn: &expiresIn,
User: userToReturn, User: userToReturn,
} }
} }

View File

@@ -10,6 +10,7 @@ import (
"github.com/authorizerdev/authorizer/server/constants" "github.com/authorizerdev/authorizer/server/constants"
"github.com/authorizerdev/authorizer/server/cookie" "github.com/authorizerdev/authorizer/server/cookie"
"github.com/authorizerdev/authorizer/server/crypto"
"github.com/authorizerdev/authorizer/server/db" "github.com/authorizerdev/authorizer/server/db"
"github.com/authorizerdev/authorizer/server/envstore" "github.com/authorizerdev/authorizer/server/envstore"
"github.com/authorizerdev/authorizer/server/graph/model" "github.com/authorizerdev/authorizer/server/graph/model"
@@ -33,6 +34,66 @@ func UpdateEnvResolver(ctx context.Context, params model.UpdateEnvInput) (*model
return res, fmt.Errorf("unauthorized") return res, fmt.Errorf("unauthorized")
} }
updatedData := envstore.EnvStoreObj.GetEnvStoreClone()
isJWTUpdated := false
algo := updatedData.StringEnv[constants.EnvKeyJwtType]
if params.JwtType != nil {
algo = *params.JwtType
if !crypto.IsHMACA(algo) && !crypto.IsECDSA(algo) && !crypto.IsRSA(algo) {
return res, fmt.Errorf("invalid jwt type")
}
updatedData.StringEnv[constants.EnvKeyJwtType] = algo
isJWTUpdated = true
}
if params.JwtSecret != nil || params.JwtPublicKey != nil || params.JwtPrivateKey != nil {
isJWTUpdated = true
}
if isJWTUpdated {
// 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")
}
}
if crypto.IsRSA(algo) {
if params.JwtPrivateKey == nil || params.JwtPublicKey == nil {
return res, fmt.Errorf("jwt private and public key is required for RSA (PKCS1) / ECDSA algorithm")
}
_, err = crypto.ParseRsaPrivateKeyFromPemStr(*params.JwtPrivateKey)
if err != nil {
return res, err
}
_, err := crypto.ParseRsaPublicKeyFromPemStr(*params.JwtPublicKey)
if err != nil {
return res, err
}
}
if crypto.IsECDSA(algo) {
if params.JwtPrivateKey == nil || params.JwtPublicKey == nil {
return res, fmt.Errorf("jwt private and public key is required for RSA (PKCS1) / ECDSA algorithm")
}
_, err = crypto.ParseEcdsaPrivateKeyFromPemStr(*params.JwtPrivateKey)
if err != nil {
return res, err
}
_, err := crypto.ParseEcdsaPublicKeyFromPemStr(*params.JwtPublicKey)
if err != nil {
return res, err
}
}
}
var data map[string]interface{} var data map[string]interface{}
byteData, err := json.Marshal(params) byteData, err := json.Marshal(params)
if err != nil { if err != nil {
@@ -50,7 +111,7 @@ func UpdateEnvResolver(ctx context.Context, params model.UpdateEnvInput) (*model
return res, errors.New("admin secret and old admin secret are required for secret change") return res, errors.New("admin secret and old admin secret are required for secret change")
} }
if *params.OldAdminSecret != envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyAdminSecret) { if *params.OldAdminSecret != envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyAdminSecret) {
return res, errors.New("old admin secret is not correct") return res, errors.New("old admin secret is not correct")
} }
@@ -61,7 +122,6 @@ func UpdateEnvResolver(ctx context.Context, params model.UpdateEnvInput) (*model
} }
updatedData := envstore.EnvInMemoryStoreObj.GetEnvStoreClone()
for key, value := range data { for key, value := range data {
if value != nil { if value != nil {
fieldType := reflect.TypeOf(value).String() fieldType := reflect.TypeOf(value).String()
@@ -116,9 +176,21 @@ func UpdateEnvResolver(ctx context.Context, params model.UpdateEnvInput) (*model
} }
// Update local store // Update local store
envstore.EnvInMemoryStoreObj.UpdateEnvStore(updatedData) envstore.EnvStoreObj.UpdateEnvStore(updatedData)
sessionstore.InitSession() jwk, err := crypto.GenerateJWKBasedOnEnv()
oauth.InitOAuth() if err != nil {
return res, err
}
// updating jwk
envstore.EnvStoreObj.UpdateEnvVariable(constants.StringStoreIdentifier, constants.EnvKeyJWK, jwk)
err = sessionstore.InitSession()
if err != nil {
return res, err
}
err = oauth.InitOAuth()
if err != nil {
return res, err
}
// Fetch the current db store and update it // Fetch the current db store and update it
env, err := db.Provider.GetEnv() env, err := db.Provider.GetEnv()
@@ -127,14 +199,14 @@ func UpdateEnvResolver(ctx context.Context, params model.UpdateEnvInput) (*model
} }
if params.AdminSecret != nil { if params.AdminSecret != nil {
hashedKey, err := utils.EncryptPassword(envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyAdminSecret)) hashedKey, err := crypto.EncryptPassword(envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyAdminSecret))
if err != nil { if err != nil {
return res, err return res, err
} }
cookie.SetAdminCookie(gc, hashedKey) cookie.SetAdminCookie(gc, hashedKey)
} }
encryptedConfig, err := utils.EncryptEnvData(updatedData) encryptedConfig, err := crypto.EncryptEnvData(updatedData)
if err != nil { if err != nil {
return res, err return res, err
} }

View File

@@ -9,9 +9,11 @@ import (
"github.com/authorizerdev/authorizer/server/constants" "github.com/authorizerdev/authorizer/server/constants"
"github.com/authorizerdev/authorizer/server/cookie" "github.com/authorizerdev/authorizer/server/cookie"
"github.com/authorizerdev/authorizer/server/crypto"
"github.com/authorizerdev/authorizer/server/db" "github.com/authorizerdev/authorizer/server/db"
"github.com/authorizerdev/authorizer/server/db/models" "github.com/authorizerdev/authorizer/server/db/models"
"github.com/authorizerdev/authorizer/server/email" "github.com/authorizerdev/authorizer/server/email"
"github.com/authorizerdev/authorizer/server/envstore"
"github.com/authorizerdev/authorizer/server/graph/model" "github.com/authorizerdev/authorizer/server/graph/model"
"github.com/authorizerdev/authorizer/server/sessionstore" "github.com/authorizerdev/authorizer/server/sessionstore"
"github.com/authorizerdev/authorizer/server/token" "github.com/authorizerdev/authorizer/server/token"
@@ -27,7 +29,11 @@ func UpdateProfileResolver(ctx context.Context, params model.UpdateProfileInput)
return res, err return res, err
} }
claims, err := token.ValidateAccessToken(gc) accessToken, err := token.GetAccessToken(gc)
if err != nil {
return res, err
}
claims, err := token.ValidateAccessToken(gc, accessToken)
if err != nil { if err != nil {
return res, err return res, err
} }
@@ -37,8 +43,8 @@ func UpdateProfileResolver(ctx context.Context, params model.UpdateProfileInput)
return res, fmt.Errorf("please enter at least one param to update") return res, fmt.Errorf("please enter at least one param to update")
} }
userEmail := fmt.Sprintf("%v", claims["email"]) userID := claims["sub"].(string)
user, err := db.Provider.GetUserByEmail(userEmail) user, err := db.Provider.GetUserByID(userID)
if err != nil { if err != nil {
return res, err return res, err
} }
@@ -92,7 +98,7 @@ func UpdateProfileResolver(ctx context.Context, params model.UpdateProfileInput)
return res, fmt.Errorf(`password and confirm password does not match`) return res, fmt.Errorf(`password and confirm password does not match`)
} }
password, _ := utils.EncryptPassword(*params.NewPassword) password, _ := crypto.EncryptPassword(*params.NewPassword)
user.Password = &password user.Password = &password
} }
@@ -107,38 +113,44 @@ func UpdateProfileResolver(ctx context.Context, params model.UpdateProfileInput)
newEmail := strings.ToLower(*params.Email) newEmail := strings.ToLower(*params.Email)
// check if user with new email exists // check if user with new email exists
_, err := db.Provider.GetUserByEmail(newEmail) _, err := db.Provider.GetUserByEmail(newEmail)
// err = nil means user exists // err = nil means user exists
if err == nil { if err == nil {
return res, fmt.Errorf("user with this email address already exists") return res, fmt.Errorf("user with this email address already exists")
} }
sessionstore.DeleteAllUserSession(fmt.Sprintf("%v", user.ID)) // TODO figure out how to delete all user sessions
cookie.DeleteCookie(gc) go sessionstore.DeleteAllUserSession(user.ID)
hostname := utils.GetHost(gc) cookie.DeleteSession(gc)
user.Email = newEmail user.Email = newEmail
user.EmailVerifiedAt = nil
hasEmailChanged = true if !envstore.EnvStoreObj.GetBoolStoreEnvVariable(constants.EnvKeyDisableEmailVerification) {
// insert verification request hostname := utils.GetHost(gc)
verificationType := constants.VerificationTypeUpdateEmail user.EmailVerifiedAt = nil
verificationToken, err := token.CreateVerificationToken(newEmail, verificationType, hostname) hasEmailChanged = true
if err != nil { // insert verification request
log.Println(`error generating token`, err) nonce, nonceHash, err := utils.GenerateNonce()
if err != nil {
return res, err
}
verificationType := constants.VerificationTypeUpdateEmail
verificationToken, err := token.CreateVerificationToken(newEmail, verificationType, hostname, nonceHash)
if err != nil {
log.Println(`error generating token`, err)
}
db.Provider.AddVerificationRequest(models.VerificationRequest{
Token: verificationToken,
Identifier: verificationType,
ExpiresAt: time.Now().Add(time.Minute * 30).Unix(),
Email: newEmail,
Nonce: nonce,
})
// exec it as go routin so that we can reduce the api latency
go email.SendVerificationMail(newEmail, verificationToken, hostname)
} }
db.Provider.AddVerificationRequest(models.VerificationRequest{
Token: verificationToken,
Identifier: verificationType,
ExpiresAt: time.Now().Add(time.Minute * 30).Unix(),
Email: newEmail,
})
// exec it as go routin so that we can reduce the api latency
go func() {
email.SendVerificationMail(newEmail, verificationToken, hostname)
}()
} }
_, err = db.Provider.UpdateUser(user) _, err = db.Provider.UpdateUser(user)
if err != nil { if err != nil {
log.Println("error updating user:", err) log.Println("error updating user:", err)

View File

@@ -8,7 +8,6 @@ import (
"time" "time"
"github.com/authorizerdev/authorizer/server/constants" "github.com/authorizerdev/authorizer/server/constants"
"github.com/authorizerdev/authorizer/server/cookie"
"github.com/authorizerdev/authorizer/server/db" "github.com/authorizerdev/authorizer/server/db"
"github.com/authorizerdev/authorizer/server/db/models" "github.com/authorizerdev/authorizer/server/db/models"
"github.com/authorizerdev/authorizer/server/email" "github.com/authorizerdev/authorizer/server/email"
@@ -95,15 +94,19 @@ func UpdateUserResolver(ctx context.Context, params model.UpdateUserInput) (*mod
return res, fmt.Errorf("user with this email address already exists") return res, fmt.Errorf("user with this email address already exists")
} }
sessionstore.DeleteAllUserSession(fmt.Sprintf("%v", user.ID)) // TODO figure out how to do this
cookie.DeleteCookie(gc) go sessionstore.DeleteAllUserSession(user.ID)
hostname := utils.GetHost(gc) hostname := utils.GetHost(gc)
user.Email = newEmail user.Email = newEmail
user.EmailVerifiedAt = nil user.EmailVerifiedAt = nil
// insert verification request // insert verification request
nonce, nonceHash, err := utils.GenerateNonce()
if err != nil {
return res, err
}
verificationType := constants.VerificationTypeUpdateEmail verificationType := constants.VerificationTypeUpdateEmail
verificationToken, err := token.CreateVerificationToken(newEmail, verificationType, hostname) verificationToken, err := token.CreateVerificationToken(newEmail, verificationType, hostname, nonceHash)
if err != nil { if err != nil {
log.Println(`error generating token`, err) log.Println(`error generating token`, err)
} }
@@ -112,12 +115,12 @@ func UpdateUserResolver(ctx context.Context, params model.UpdateUserInput) (*mod
Identifier: verificationType, Identifier: verificationType,
ExpiresAt: time.Now().Add(time.Minute * 30).Unix(), ExpiresAt: time.Now().Add(time.Minute * 30).Unix(),
Email: newEmail, Email: newEmail,
Nonce: nonce,
}) })
// exec it as go routin so that we can reduce the api latency // exec it as go routin so that we can reduce the api latency
go func() { go email.SendVerificationMail(newEmail, verificationToken, hostname)
email.SendVerificationMail(newEmail, verificationToken, hostname)
}()
} }
rolesToSave := "" rolesToSave := ""
@@ -128,7 +131,7 @@ func UpdateUserResolver(ctx context.Context, params model.UpdateUserInput) (*mod
inputRoles = append(inputRoles, *item) inputRoles = append(inputRoles, *item)
} }
if !utils.IsValidRoles(inputRoles, append([]string{}, append(envstore.EnvInMemoryStoreObj.GetSliceStoreEnvVariable(constants.EnvKeyRoles), envstore.EnvInMemoryStoreObj.GetSliceStoreEnvVariable(constants.EnvKeyProtectedRoles)...)...)) { if !utils.IsValidRoles(inputRoles, append([]string{}, append(envstore.EnvStoreObj.GetSliceStoreEnvVariable(constants.EnvKeyRoles), envstore.EnvStoreObj.GetSliceStoreEnvVariable(constants.EnvKeyProtectedRoles)...)...)) {
return res, fmt.Errorf("invalid list of roles") return res, fmt.Errorf("invalid list of roles")
} }
@@ -136,8 +139,7 @@ func UpdateUserResolver(ctx context.Context, params model.UpdateUserInput) (*mod
rolesToSave = strings.Join(inputRoles, ",") rolesToSave = strings.Join(inputRoles, ",")
} }
sessionstore.DeleteAllUserSession(fmt.Sprintf("%v", user.ID)) go sessionstore.DeleteAllUserSession(user.ID)
cookie.DeleteCookie(gc)
} }
if rolesToSave != "" { if rolesToSave != "" {

View File

@@ -24,16 +24,21 @@ func VerifyEmailResolver(ctx context.Context, params model.VerifyEmailInput) (*m
verificationRequest, err := db.Provider.GetVerificationRequestByToken(params.Token) verificationRequest, err := db.Provider.GetVerificationRequestByToken(params.Token)
if err != nil { if err != nil {
return res, fmt.Errorf(`invalid token`) return res, fmt.Errorf(`invalid token: %s`, err.Error())
} }
// verify if token exists in db // verify if token exists in db
claim, err := token.VerifyVerificationToken(params.Token) hostname := utils.GetHost(gc)
encryptedNonce, err := utils.EncryptNonce(verificationRequest.Nonce)
if err != nil { if err != nil {
return res, fmt.Errorf(`invalid token`) return res, err
}
claim, err := token.ParseJWTToken(params.Token, hostname, encryptedNonce, verificationRequest.Email)
if err != nil {
return res, fmt.Errorf(`invalid token: %s`, err.Error())
} }
user, err := db.Provider.GetUserByEmail(claim.Email) user, err := db.Provider.GetUserByEmail(claim["sub"].(string))
if err != nil { if err != nil {
return res, err return res, err
} }
@@ -41,25 +46,35 @@ func VerifyEmailResolver(ctx context.Context, params model.VerifyEmailInput) (*m
// update email_verified_at in users table // update email_verified_at in users table
now := time.Now().Unix() now := time.Now().Unix()
user.EmailVerifiedAt = &now user.EmailVerifiedAt = &now
db.Provider.UpdateUser(user) user, err = db.Provider.UpdateUser(user)
// delete from verification table if err != nil {
db.Provider.DeleteVerificationRequest(verificationRequest) return res, err
}
roles := strings.Split(user.Roles, ",") // delete from verification table
authToken, err := token.CreateAuthToken(user, roles) err = db.Provider.DeleteVerificationRequest(verificationRequest)
if err != nil { if err != nil {
return res, err return res, err
} }
sessionstore.SetUserSession(user.ID, authToken.FingerPrint, authToken.RefreshToken.Token)
cookie.SetCookie(gc, authToken.AccessToken.Token, authToken.RefreshToken.Token, authToken.FingerPrintHash)
utils.SaveSessionInDB(user.ID, gc)
roles := strings.Split(user.Roles, ",")
scope := []string{"openid", "email", "profile"}
authToken, err := token.CreateAuthToken(gc, user, roles, scope)
if err != nil {
return res, err
}
sessionstore.SetState(authToken.FingerPrintHash, authToken.FingerPrint+"@"+user.ID)
sessionstore.SetState(authToken.AccessToken.Token, authToken.FingerPrint+"@"+user.ID)
cookie.SetSession(gc, authToken.FingerPrintHash)
go utils.SaveSessionInDB(gc, user.ID)
expiresIn := int64(1800)
res = &model.AuthResponse{ res = &model.AuthResponse{
Message: `Email verified successfully.`, Message: `Email verified successfully.`,
AccessToken: &authToken.AccessToken.Token, AccessToken: &authToken.AccessToken.Token,
ExpiresAt: &authToken.AccessToken.ExpiresAt, IDToken: &authToken.IDToken.Token,
ExpiresIn: &expiresIn,
User: user.AsAPIUser(), User: user.AsAPIUser(),
} }
return res, nil return res, nil
} }

View File

@@ -20,6 +20,13 @@ func InitRouter() *gin.Engine {
router.GET("/oauth_login/:oauth_provider", handlers.OAuthLoginHandler()) router.GET("/oauth_login/:oauth_provider", handlers.OAuthLoginHandler())
router.GET("/oauth_callback/:oauth_provider", handlers.OAuthCallbackHandler()) router.GET("/oauth_callback/:oauth_provider", handlers.OAuthCallbackHandler())
router.GET("/verify_email", handlers.VerifyEmailHandler()) router.GET("/verify_email", handlers.VerifyEmailHandler())
// OPEN ID routes
router.GET("/.well-known/openid-configuration", handlers.OpenIDConfigurationHandler())
router.GET("/.well-known/jwks.json", handlers.JWKsHandler())
router.GET("/authorize", handlers.AuthorizeHandler())
router.GET("/userinfo", handlers.UserInfoHandler())
router.GET("/logout", handlers.LogoutHandler())
router.POST("/oauth/token", handlers.TokenHandler())
router.LoadHTMLGlob("templates/*") router.LoadHTMLGlob("templates/*")
// login page app related routes. // login page app related routes.

View File

@@ -1,112 +1,74 @@
package sessionstore package sessionstore
import ( import (
"strings"
"sync" "sync"
) )
// InMemoryStore is a simple in-memory store for sessions. // InMemoryStore is a simple in-memory store for sessions.
type InMemoryStore struct { type InMemoryStore struct {
mutex sync.Mutex mutex sync.Mutex
store map[string]map[string]string sessionStore map[string]map[string]string
socialLoginState map[string]string stateStore map[string]string
}
// AddUserSession adds a user session to the in-memory store.
func (c *InMemoryStore) AddUserSession(userId, accessToken, refreshToken string) {
c.mutex.Lock()
defer c.mutex.Unlock()
// delete sessions > 500 // not recommended for production
if len(c.store) >= 500 {
c.store = map[string]map[string]string{}
}
// check if entry exists in map
_, exists := c.store[userId]
if exists {
tempMap := c.store[userId]
tempMap[accessToken] = refreshToken
c.store[userId] = tempMap
} else {
tempMap := map[string]string{
accessToken: refreshToken,
}
c.store[userId] = tempMap
}
}
// DeleteAllUserSession deletes all the user sessions from in-memory store.
func (c *InMemoryStore) DeleteAllUserSession(userId string) {
c.mutex.Lock()
defer c.mutex.Unlock()
delete(c.store, userId)
}
// DeleteUserSession deletes the particular user session from in-memory store.
func (c *InMemoryStore) DeleteUserSession(userId, accessToken string) {
c.mutex.Lock()
defer c.mutex.Unlock()
delete(c.store[userId], accessToken)
} }
// ClearStore clears the in-memory store. // ClearStore clears the in-memory store.
func (c *InMemoryStore) ClearStore() { func (c *InMemoryStore) ClearStore() {
c.mutex.Lock() c.mutex.Lock()
defer c.mutex.Unlock() defer c.mutex.Unlock()
c.store = map[string]map[string]string{} c.sessionStore = map[string]map[string]string{}
}
// GetUserSession returns the user session token from the in-memory store.
func (c *InMemoryStore) GetUserSession(userId, accessToken string) string {
// c.mutex.Lock()
// defer c.mutex.Unlock()
token := ""
if sessionMap, ok := c.store[userId]; ok {
if val, ok := sessionMap[accessToken]; ok {
token = val
}
}
return token
} }
// GetUserSessions returns all the user session token from the in-memory store. // GetUserSessions returns all the user session token from the in-memory store.
func (c *InMemoryStore) GetUserSessions(userId string) map[string]string { func (c *InMemoryStore) GetUserSessions(userId string) map[string]string {
// c.mutex.Lock() // c.mutex.Lock()
// defer c.mutex.Unlock() // defer c.mutex.Unlock()
res := map[string]string{}
sessionMap, ok := c.store[userId] for k, v := range c.stateStore {
if !ok { split := strings.Split(v, "@")
return nil if split[1] == userId {
res[k] = split[0]
}
} }
return sessionMap return res
} }
// SetSocialLoginState sets the social login state in the in-memory store. // DeleteAllUserSession deletes all the user sessions from in-memory store.
func (c *InMemoryStore) SetSocialLoginState(key, state string) { func (c *InMemoryStore) DeleteAllUserSession(userId string) {
// c.mutex.Lock()
// defer c.mutex.Unlock()
sessions := GetUserSessions(userId)
for k := range sessions {
RemoveState(k)
}
}
// SetState sets the state in the in-memory store.
func (c *InMemoryStore) SetState(key, state string) {
c.mutex.Lock() c.mutex.Lock()
defer c.mutex.Unlock() defer c.mutex.Unlock()
c.socialLoginState[key] = state c.stateStore[key] = state
} }
// GetSocialLoginState gets the social login state from the in-memory store. // GetState gets the state from the in-memory store.
func (c *InMemoryStore) GetSocialLoginState(key string) string { func (c *InMemoryStore) GetState(key string) string {
c.mutex.Lock() c.mutex.Lock()
defer c.mutex.Unlock() defer c.mutex.Unlock()
state := "" state := ""
if stateVal, ok := c.socialLoginState[key]; ok { if stateVal, ok := c.stateStore[key]; ok {
state = stateVal state = stateVal
} }
return state return state
} }
// RemoveSocialLoginState removes the social login state from the in-memory store. // RemoveState removes the state from the in-memory store.
func (c *InMemoryStore) RemoveSocialLoginState(key string) { func (c *InMemoryStore) RemoveState(key string) {
c.mutex.Lock() c.mutex.Lock()
defer c.mutex.Unlock() defer c.mutex.Unlock()
delete(c.socialLoginState, key) delete(c.stateStore, key)
} }

View File

@@ -0,0 +1,18 @@
package sessionstore
import (
"context"
"time"
"github.com/go-redis/redis/v8"
)
type RedisSessionClient interface {
HMSet(ctx context.Context, key string, values ...interface{}) *redis.BoolCmd
Del(ctx context.Context, keys ...string) *redis.IntCmd
HDel(ctx context.Context, key string, fields ...string) *redis.IntCmd
HMGet(ctx context.Context, key string, fields ...string) *redis.SliceCmd
HGetAll(ctx context.Context, key string) *redis.StringStringMapCmd
Set(ctx context.Context, key string, value interface{}, expiration time.Duration) *redis.StatusCmd
Get(ctx context.Context, key string) *redis.StringCmd
}

View File

@@ -2,41 +2,13 @@ package sessionstore
import ( import (
"context" "context"
"fmt"
"log" "log"
"strings"
"github.com/go-redis/redis/v8"
) )
type RedisStore struct { type RedisStore struct {
ctx context.Context ctx context.Context
store *redis.Client store RedisSessionClient
}
// AddUserSession adds the user session to redis
func (c *RedisStore) AddUserSession(userId, accessToken, refreshToken string) {
err := c.store.HMSet(c.ctx, "authorizer_"+userId, map[string]string{
accessToken: refreshToken,
}).Err()
if err != nil {
log.Fatalln("Error saving redis token:", err)
}
}
// DeleteAllUserSession deletes all the user session from redis
func (c *RedisStore) DeleteAllUserSession(userId string) {
err := c.store.Del(c.ctx, "authorizer_"+userId).Err()
if err != nil {
log.Fatalln("Error deleting redis token:", err)
}
}
// DeleteUserSession deletes the particular user session from redis
func (c *RedisStore) DeleteUserSession(userId, accessToken string) {
err := c.store.HDel(c.ctx, "authorizer_"+userId, accessToken).Err()
if err != nil {
log.Fatalln("Error deleting redis token:", err)
}
} }
// ClearStore clears the redis store for authorizer related tokens // ClearStore clears the redis store for authorizer related tokens
@@ -47,41 +19,49 @@ func (c *RedisStore) ClearStore() {
} }
} }
// GetUserSession returns the user session token from the redis store.
func (c *RedisStore) GetUserSession(userId, accessToken string) string {
token := ""
res, err := c.store.HMGet(c.ctx, "authorizer_"+userId, accessToken).Result()
if err != nil {
log.Println("error getting token from redis store:", err)
}
if len(res) > 0 && res[0] != nil {
token = fmt.Sprintf("%v", res[0])
}
return token
}
// GetUserSessions returns all the user session token from the redis store. // GetUserSessions returns all the user session token from the redis store.
func (c *RedisStore) GetUserSessions(userID string) map[string]string { func (c *RedisStore) GetUserSessions(userID string) map[string]string {
res, err := c.store.HGetAll(c.ctx, "authorizer_"+userID).Result() data, err := c.store.HGetAll(c.ctx, "*").Result()
if err != nil { if err != nil {
log.Println("error getting token from redis store:", err) log.Println("error getting token from redis store:", err)
} }
res := map[string]string{}
for k, v := range data {
split := strings.Split(v, "@")
if split[1] == userID {
res[k] = split[0]
}
}
return res return res
} }
// SetSocialLoginState sets the social login state in redis store. // DeleteAllUserSession deletes all the user session from redis
func (c *RedisStore) SetSocialLoginState(key, state string) { func (c *RedisStore) DeleteAllUserSession(userId string) {
err := c.store.Set(c.ctx, key, state, 0).Err() sessions := GetUserSessions(userId)
for k, v := range sessions {
if k == "token" {
err := c.store.Del(c.ctx, v)
if err != nil {
log.Println("Error deleting redis token:", err)
}
}
}
}
// SetState sets the state in redis store.
func (c *RedisStore) SetState(key, value string) {
err := c.store.Set(c.ctx, "authorizer_"+key, value, 0).Err()
if err != nil { if err != nil {
log.Fatalln("Error saving redis token:", err) log.Fatalln("Error saving redis token:", err)
} }
} }
// GetSocialLoginState gets the social login state from redis store. // GetState gets the state from redis store.
func (c *RedisStore) GetSocialLoginState(key string) string { func (c *RedisStore) GetState(key string) string {
state := "" state := ""
state, err := c.store.Get(c.ctx, key).Result() state, err := c.store.Get(c.ctx, "authorizer_"+key).Result()
if err != nil { if err != nil {
log.Println("error getting token from redis store:", err) log.Println("error getting token from redis store:", err)
} }
@@ -89,9 +69,9 @@ func (c *RedisStore) GetSocialLoginState(key string) string {
return state return state
} }
// RemoveSocialLoginState removes the social login state from redis store. // RemoveState removes the state from redis store.
func (c *RedisStore) RemoveSocialLoginState(key string) { func (c *RedisStore) RemoveState(key string) {
err := c.store.Del(c.ctx, key).Err() err := c.store.Del(c.ctx, "authorizer_"+key).Err()
if err != nil { if err != nil {
log.Fatalln("Error deleting redis token:", err) log.Fatalln("Error deleting redis token:", err)
} }

View File

@@ -3,6 +3,7 @@ package sessionstore
import ( import (
"context" "context"
"log" "log"
"strings"
"github.com/authorizerdev/authorizer/server/constants" "github.com/authorizerdev/authorizer/server/constants"
"github.com/authorizerdev/authorizer/server/envstore" "github.com/authorizerdev/authorizer/server/envstore"
@@ -21,26 +22,6 @@ type SessionStore struct {
// reference to various session store instances // reference to various session store instances
var SessionStoreObj SessionStore var SessionStoreObj SessionStore
// SetUserSession sets the user session in the session store
func SetUserSession(userId, fingerprint, refreshToken string) {
if SessionStoreObj.RedisMemoryStoreObj != nil {
SessionStoreObj.RedisMemoryStoreObj.AddUserSession(userId, fingerprint, refreshToken)
}
if SessionStoreObj.InMemoryStoreObj != nil {
SessionStoreObj.InMemoryStoreObj.AddUserSession(userId, fingerprint, refreshToken)
}
}
// DeleteUserSession deletes the particular user session from the session store
func DeleteUserSession(userId, fingerprint string) {
if SessionStoreObj.RedisMemoryStoreObj != nil {
SessionStoreObj.RedisMemoryStoreObj.DeleteUserSession(userId, fingerprint)
}
if SessionStoreObj.InMemoryStoreObj != nil {
SessionStoreObj.InMemoryStoreObj.DeleteUserSession(userId, fingerprint)
}
}
// DeleteAllSessions deletes all the sessions from the session store // DeleteAllSessions deletes all the sessions from the session store
func DeleteAllUserSession(userId string) { func DeleteAllUserSession(userId string) {
if SessionStoreObj.RedisMemoryStoreObj != nil { if SessionStoreObj.RedisMemoryStoreObj != nil {
@@ -51,18 +32,6 @@ func DeleteAllUserSession(userId string) {
} }
} }
// GetUserSession returns the user session from the session store
func GetUserSession(userId, fingerprint string) string {
if SessionStoreObj.RedisMemoryStoreObj != nil {
return SessionStoreObj.RedisMemoryStoreObj.GetUserSession(userId, fingerprint)
}
if SessionStoreObj.InMemoryStoreObj != nil {
return SessionStoreObj.InMemoryStoreObj.GetUserSession(userId, fingerprint)
}
return ""
}
// GetUserSessions returns all the user sessions from the session store // GetUserSessions returns all the user sessions from the session store
func GetUserSessions(userId string) map[string]string { func GetUserSessions(userId string) map[string]string {
if SessionStoreObj.RedisMemoryStoreObj != nil { if SessionStoreObj.RedisMemoryStoreObj != nil {
@@ -85,62 +54,97 @@ func ClearStore() {
} }
} }
// SetSocialLoginState sets the social login state in the session store // SetState sets the login state (key, value form) in the session store
func SetSocailLoginState(key, state string) { func SetState(key, state string) {
if SessionStoreObj.RedisMemoryStoreObj != nil { if SessionStoreObj.RedisMemoryStoreObj != nil {
SessionStoreObj.RedisMemoryStoreObj.SetSocialLoginState(key, state) SessionStoreObj.RedisMemoryStoreObj.SetState(key, state)
} }
if SessionStoreObj.InMemoryStoreObj != nil { if SessionStoreObj.InMemoryStoreObj != nil {
SessionStoreObj.InMemoryStoreObj.SetSocialLoginState(key, state) SessionStoreObj.InMemoryStoreObj.SetState(key, state)
} }
} }
// GetSocialLoginState returns the social login state from the session store // GetState returns the state from the session store
func GetSocailLoginState(key string) string { func GetState(key string) string {
if SessionStoreObj.RedisMemoryStoreObj != nil { if SessionStoreObj.RedisMemoryStoreObj != nil {
return SessionStoreObj.RedisMemoryStoreObj.GetSocialLoginState(key) return SessionStoreObj.RedisMemoryStoreObj.GetState(key)
} }
if SessionStoreObj.InMemoryStoreObj != nil { if SessionStoreObj.InMemoryStoreObj != nil {
return SessionStoreObj.InMemoryStoreObj.GetSocialLoginState(key) return SessionStoreObj.InMemoryStoreObj.GetState(key)
} }
return "" return ""
} }
// RemoveSocialLoginState removes the social login state from the session store // RemoveState removes the social login state from the session store
func RemoveSocialLoginState(key string) { func RemoveState(key string) {
if SessionStoreObj.RedisMemoryStoreObj != nil { if SessionStoreObj.RedisMemoryStoreObj != nil {
SessionStoreObj.RedisMemoryStoreObj.RemoveSocialLoginState(key) SessionStoreObj.RedisMemoryStoreObj.RemoveState(key)
} }
if SessionStoreObj.InMemoryStoreObj != nil { if SessionStoreObj.InMemoryStoreObj != nil {
SessionStoreObj.InMemoryStoreObj.RemoveSocialLoginState(key) SessionStoreObj.InMemoryStoreObj.RemoveState(key)
} }
} }
// InitializeSessionStore initializes the SessionStoreObj based on environment variables // InitializeSessionStore initializes the SessionStoreObj based on environment variables
func InitSession() { func InitSession() error {
if envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyRedisURL) != "" { if envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyRedisURL) != "" {
log.Println("using redis store to save sessions") log.Println("using redis store to save sessions")
opt, err := redis.ParseURL(envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyRedisURL))
if err != nil { redisURL := envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyRedisURL)
log.Fatalln("Error parsing redis url:", err) redisURLHostPortsList := strings.Split(redisURL, ",")
if len(redisURLHostPortsList) > 1 {
opt, err := redis.ParseURL(redisURLHostPortsList[0])
if err != nil {
return err
}
urls := []string{opt.Addr}
urlList := redisURLHostPortsList[1:]
urls = append(urls, urlList...)
clusterOpt := &redis.ClusterOptions{Addrs: urls}
rdb := redis.NewClusterClient(clusterOpt)
ctx := context.Background()
_, err = rdb.Ping(ctx).Result()
if err != nil {
return err
}
SessionStoreObj.RedisMemoryStoreObj = &RedisStore{
ctx: ctx,
store: rdb,
}
// return on successful initialization
return nil
} }
opt, err := redis.ParseURL(envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyRedisURL))
if err != nil {
return err
}
rdb := redis.NewClient(opt) rdb := redis.NewClient(opt)
ctx := context.Background() ctx := context.Background()
_, err = rdb.Ping(ctx).Result() _, err = rdb.Ping(ctx).Result()
if err != nil { if err != nil {
log.Fatalln("Error connecting to redis server", err) return err
} }
SessionStoreObj.RedisMemoryStoreObj = &RedisStore{ SessionStoreObj.RedisMemoryStoreObj = &RedisStore{
ctx: ctx, ctx: ctx,
store: rdb, store: rdb,
} }
} else { // return on successful initialization
SessionStoreObj.InMemoryStoreObj = &InMemoryStore{ return nil
store: map[string]map[string]string{},
socialLoginState: map[string]string{},
}
} }
// if redis url is not set use in memory store
SessionStoreObj.InMemoryStoreObj = &InMemoryStore{
sessionStore: map[string]map[string]string{},
stateStore: map[string]string{},
}
return nil
} }

View File

@@ -21,7 +21,7 @@ func adminLoginTests(t *testing.T, s TestSetup) {
assert.NotNil(t, err) assert.NotNil(t, err)
_, err = resolvers.AdminLoginResolver(ctx, model.AdminLoginInput{ _, err = resolvers.AdminLoginResolver(ctx, model.AdminLoginInput{
AdminSecret: envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyAdminSecret), AdminSecret: envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyAdminSecret),
}) })
assert.Nil(t, err) assert.Nil(t, err)

View File

@@ -5,9 +5,9 @@ import (
"testing" "testing"
"github.com/authorizerdev/authorizer/server/constants" "github.com/authorizerdev/authorizer/server/constants"
"github.com/authorizerdev/authorizer/server/crypto"
"github.com/authorizerdev/authorizer/server/envstore" "github.com/authorizerdev/authorizer/server/envstore"
"github.com/authorizerdev/authorizer/server/resolvers" "github.com/authorizerdev/authorizer/server/resolvers"
"github.com/authorizerdev/authorizer/server/utils"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
) )
@@ -18,9 +18,9 @@ func adminLogoutTests(t *testing.T, s TestSetup) {
_, err := resolvers.AdminLogoutResolver(ctx) _, err := resolvers.AdminLogoutResolver(ctx)
assert.NotNil(t, err) assert.NotNil(t, err)
h, err := utils.EncryptPassword(envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyAdminSecret)) h, err := crypto.EncryptPassword(envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyAdminSecret))
assert.Nil(t, err) assert.Nil(t, err)
req.Header.Set("Cookie", fmt.Sprintf("%s=%s", envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyAdminCookieName), h)) req.Header.Set("Cookie", fmt.Sprintf("%s=%s", envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyAdminCookieName), h))
_, err = resolvers.AdminLogoutResolver(ctx) _, err = resolvers.AdminLogoutResolver(ctx)
assert.Nil(t, err) assert.Nil(t, err)

View File

@@ -5,9 +5,9 @@ import (
"testing" "testing"
"github.com/authorizerdev/authorizer/server/constants" "github.com/authorizerdev/authorizer/server/constants"
"github.com/authorizerdev/authorizer/server/crypto"
"github.com/authorizerdev/authorizer/server/envstore" "github.com/authorizerdev/authorizer/server/envstore"
"github.com/authorizerdev/authorizer/server/resolvers" "github.com/authorizerdev/authorizer/server/resolvers"
"github.com/authorizerdev/authorizer/server/utils"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
) )
@@ -18,9 +18,9 @@ func adminSessionTests(t *testing.T, s TestSetup) {
_, err := resolvers.AdminSessionResolver(ctx) _, err := resolvers.AdminSessionResolver(ctx)
assert.NotNil(t, err) assert.NotNil(t, err)
h, err := utils.EncryptPassword(envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyAdminSecret)) h, err := crypto.EncryptPassword(envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyAdminSecret))
assert.Nil(t, err) assert.Nil(t, err)
req.Header.Set("Cookie", fmt.Sprintf("%s=%s", envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyAdminCookieName), h)) req.Header.Set("Cookie", fmt.Sprintf("%s=%s", envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyAdminCookieName), h))
_, err = resolvers.AdminSessionResolver(ctx) _, err = resolvers.AdminSessionResolver(ctx)
assert.Nil(t, err) assert.Nil(t, err)

View File

@@ -20,7 +20,7 @@ func adminSignupTests(t *testing.T, s TestSetup) {
assert.NotNil(t, err) assert.NotNil(t, err)
// reset env for test to pass // reset env for test to pass
envstore.EnvInMemoryStoreObj.UpdateEnvVariable(constants.StringStoreIdentifier, constants.EnvKeyAdminSecret, "") envstore.EnvStoreObj.UpdateEnvVariable(constants.StringStoreIdentifier, constants.EnvKeyAdminSecret, "")
_, err = resolvers.AdminSignupResolver(ctx, model.AdminSignupInput{ _, err = resolvers.AdminSignupResolver(ctx, model.AdminSignupInput{
AdminSecret: "admin123", AdminSecret: "admin123",

View File

@@ -5,10 +5,10 @@ import (
"testing" "testing"
"github.com/authorizerdev/authorizer/server/constants" "github.com/authorizerdev/authorizer/server/constants"
"github.com/authorizerdev/authorizer/server/crypto"
"github.com/authorizerdev/authorizer/server/envstore" "github.com/authorizerdev/authorizer/server/envstore"
"github.com/authorizerdev/authorizer/server/graph/model" "github.com/authorizerdev/authorizer/server/graph/model"
"github.com/authorizerdev/authorizer/server/resolvers" "github.com/authorizerdev/authorizer/server/resolvers"
"github.com/authorizerdev/authorizer/server/utils"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
) )
@@ -28,9 +28,9 @@ func deleteUserTest(t *testing.T, s TestSetup) {
}) })
assert.NotNil(t, err, "unauthorized") assert.NotNil(t, err, "unauthorized")
h, err := utils.EncryptPassword(envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyAdminSecret)) h, err := crypto.EncryptPassword(envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyAdminSecret))
assert.Nil(t, err) assert.Nil(t, err)
req.Header.Set("Cookie", fmt.Sprintf("%s=%s", envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyAdminCookieName), h)) req.Header.Set("Cookie", fmt.Sprintf("%s=%s", envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyAdminCookieName), h))
_, err = resolvers.DeleteUserResolver(ctx, model.DeleteUserInput{ _, err = resolvers.DeleteUserResolver(ctx, model.DeleteUserInput{
Email: email, Email: email,

View File

@@ -10,15 +10,15 @@ import (
) )
func TestEnvs(t *testing.T) { func TestEnvs(t *testing.T) {
envstore.EnvInMemoryStoreObj.UpdateEnvVariable(constants.StringStoreIdentifier, constants.EnvKeyEnvPath, "../../.env.sample") envstore.EnvStoreObj.UpdateEnvVariable(constants.StringStoreIdentifier, constants.EnvKeyEnvPath, "../../.env.sample")
env.InitEnv() env.InitAllEnv()
store := envstore.EnvInMemoryStoreObj.GetEnvStoreClone() store := envstore.EnvStoreObj.GetEnvStoreClone()
assert.Equal(t, store.StringEnv[constants.EnvKeyEnv], "production") assert.Equal(t, store.StringEnv[constants.EnvKeyEnv], "production")
assert.False(t, store.BoolEnv[constants.EnvKeyDisableEmailVerification]) assert.False(t, store.BoolEnv[constants.EnvKeyDisableEmailVerification])
assert.False(t, store.BoolEnv[constants.EnvKeyDisableMagicLinkLogin]) assert.False(t, store.BoolEnv[constants.EnvKeyDisableMagicLinkLogin])
assert.False(t, store.BoolEnv[constants.EnvKeyDisableBasicAuthentication]) assert.False(t, store.BoolEnv[constants.EnvKeyDisableBasicAuthentication])
assert.Equal(t, store.StringEnv[constants.EnvKeyJwtType], "HS256") assert.Equal(t, store.StringEnv[constants.EnvKeyJwtType], "RS256")
assert.Equal(t, store.StringEnv[constants.EnvKeyJwtRoleClaim], "role") assert.Equal(t, store.StringEnv[constants.EnvKeyJwtRoleClaim], "role")
assert.EqualValues(t, store.SliceEnv[constants.EnvKeyRoles], []string{"user"}) assert.EqualValues(t, store.SliceEnv[constants.EnvKeyRoles], []string{"user"})
assert.EqualValues(t, store.SliceEnv[constants.EnvKeyDefaultRoles], []string{"user"}) assert.EqualValues(t, store.SliceEnv[constants.EnvKeyDefaultRoles], []string{"user"})

View File

@@ -5,9 +5,9 @@ import (
"testing" "testing"
"github.com/authorizerdev/authorizer/server/constants" "github.com/authorizerdev/authorizer/server/constants"
"github.com/authorizerdev/authorizer/server/crypto"
"github.com/authorizerdev/authorizer/server/envstore" "github.com/authorizerdev/authorizer/server/envstore"
"github.com/authorizerdev/authorizer/server/resolvers" "github.com/authorizerdev/authorizer/server/resolvers"
"github.com/authorizerdev/authorizer/server/utils"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
) )
@@ -18,12 +18,12 @@ func envTests(t *testing.T, s TestSetup) {
_, err := resolvers.EnvResolver(ctx) _, err := resolvers.EnvResolver(ctx)
assert.NotNil(t, err) assert.NotNil(t, err)
h, err := utils.EncryptPassword(envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyAdminSecret)) h, err := crypto.EncryptPassword(envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyAdminSecret))
assert.Nil(t, err) assert.Nil(t, err)
req.Header.Set("Cookie", fmt.Sprintf("%s=%s", envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyAdminCookieName), h)) req.Header.Set("Cookie", fmt.Sprintf("%s=%s", envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyAdminCookieName), h))
res, err := resolvers.EnvResolver(ctx) res, err := resolvers.EnvResolver(ctx)
assert.Nil(t, err) assert.Nil(t, err)
assert.Equal(t, *res.AdminSecret, envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyAdminSecret)) assert.Equal(t, *res.AdminSecret, envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyAdminSecret))
}) })
} }

View File

@@ -1,38 +0,0 @@
package test
import (
"testing"
"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/token"
"github.com/google/uuid"
"github.com/stretchr/testify/assert"
)
func isValidJWTTests(t *testing.T, s TestSetup) {
t.Helper()
_, ctx := createContext(s)
expiredToken := "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhbGxvd2VkX3JvbGVzIjpbIiJdLCJiaXJ0aGRhdGUiOm51bGwsImNyZWF0ZWRfYXQiOjAsImVtYWlsIjoiam9obi5kb2VAZ21haWwuY29tIiwiZW1haWxfdmVyaWZpZWQiOmZhbHNlLCJleHAiOjE2NDI5NjEwMTEsImV4dHJhIjp7IngtZXh0cmEtaWQiOiJkMmNhMjQwNy05MzZmLTQwYzQtOTQ2NS05Y2M5MWYxZTJhNDQifSwiZmFtaWx5X25hbWUiOm51bGwsImdlbmRlciI6bnVsbCwiZ2l2ZW5fbmFtZSI6bnVsbCwiaWF0IjoxNjQyOTYwOTgxLCJpZCI6ImQyY2EyNDA3LTkzNmYtNDBjNC05NDY1LTljYzkxZjFlMmE0NCIsIm1pZGRsZV9uYW1lIjpudWxsLCJuaWNrbmFtZSI6bnVsbCwicGhvbmVfbnVtYmVyIjpudWxsLCJwaG9uZV9udW1iZXJfdmVyaWZpZWQiOmZhbHNlLCJwaWN0dXJlIjpudWxsLCJwcmVmZXJyZWRfdXNlcm5hbWUiOiJqb2huLmRvZUBnbWFpbC5jb20iLCJyb2xlIjpbXSwic2lnbnVwX21ldGhvZHMiOiIiLCJ0b2tlbl90eXBlIjoiYWNjZXNzX3Rva2VuIiwidXBkYXRlZF9hdCI6MH0.FrdyeOC5e8uU1SowGj0omFJuwRnh4BrEk89S_fbEkzs"
t.Run(`should fail for invalid jwt`, func(t *testing.T) {
_, err := resolvers.IsValidJwtResolver(ctx, &model.IsValidJWTQueryInput{
Jwt: &expiredToken,
})
assert.NotNil(t, err)
})
t.Run(`should pass with valid jwt`, func(t *testing.T) {
authToken, err := token.CreateAuthToken(models.User{
ID: uuid.New().String(),
Email: "john.doe@gmail.com",
}, []string{})
assert.Nil(t, err)
res, err := resolvers.IsValidJwtResolver(ctx, &model.IsValidJWTQueryInput{
Jwt: &authToken.AccessToken.Token,
})
assert.Nil(t, err)
assert.True(t, res.Valid)
})
}

172
server/test/jwt_test.go Normal file
View File

@@ -0,0 +1,172 @@
package test
import (
"testing"
"time"
"github.com/authorizerdev/authorizer/server/constants"
"github.com/authorizerdev/authorizer/server/crypto"
"github.com/authorizerdev/authorizer/server/envstore"
"github.com/authorizerdev/authorizer/server/token"
"github.com/golang-jwt/jwt"
"github.com/google/uuid"
"github.com/stretchr/testify/assert"
)
func TestJwt(t *testing.T) {
// persist older data till test is done and then reset it
jwtType := envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyJwtType)
publicKey := envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyJwtPublicKey)
privateKey := envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyJwtPrivateKey)
clientID := envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyClientID)
nonce := uuid.New().String()
hostname := "localhost"
subject := "test"
claims := jwt.MapClaims{
"exp": time.Now().Add(time.Minute * 30).Unix(),
"iat": time.Now().Unix(),
"email": "test@yopmail.com",
"sub": subject,
"aud": clientID,
"nonce": nonce,
"iss": hostname,
}
t.Run("invalid jwt type", func(t *testing.T) {
envstore.EnvStoreObj.UpdateEnvVariable(constants.StringStoreIdentifier, constants.EnvKeyJwtType, "invalid")
token, err := token.SignJWTToken(claims)
assert.Error(t, err, "unsupported signing method")
assert.Empty(t, token)
})
t.Run("expired jwt token", func(t *testing.T) {
envstore.EnvStoreObj.UpdateEnvVariable(constants.StringStoreIdentifier, constants.EnvKeyJwtType, "HS256")
envstore.EnvStoreObj.UpdateEnvVariable(constants.StringStoreIdentifier, constants.EnvKeyJwtSecret, "test")
expiredClaims := jwt.MapClaims{
"exp": time.Now().Add(-time.Minute * 30).Unix(),
"iat": time.Now().Unix(),
"email": "test@yopmail.com",
}
jwtToken, err := token.SignJWTToken(expiredClaims)
assert.NoError(t, err)
_, err = token.ParseJWTToken(jwtToken, hostname, nonce, subject)
assert.Error(t, err, err.Error(), "Token is expired")
})
t.Run("HMAC algorithms", func(t *testing.T) {
envstore.EnvStoreObj.UpdateEnvVariable(constants.StringStoreIdentifier, constants.EnvKeyJwtSecret, "test")
t.Run("HS256", func(t *testing.T) {
envstore.EnvStoreObj.UpdateEnvVariable(constants.StringStoreIdentifier, constants.EnvKeyJwtType, "HS256")
jwtToken, err := token.SignJWTToken(claims)
assert.NoError(t, err)
assert.NotEmpty(t, jwtToken)
c, err := token.ParseJWTToken(jwtToken, hostname, nonce, subject)
assert.NoError(t, err)
assert.Equal(t, c["email"].(string), claims["email"])
})
t.Run("HS384", func(t *testing.T) {
envstore.EnvStoreObj.UpdateEnvVariable(constants.StringStoreIdentifier, constants.EnvKeyJwtType, "HS384")
jwtToken, err := token.SignJWTToken(claims)
assert.NoError(t, err)
assert.NotEmpty(t, jwtToken)
c, err := token.ParseJWTToken(jwtToken, hostname, nonce, subject)
assert.NoError(t, err)
assert.Equal(t, c["email"].(string), claims["email"])
})
t.Run("HS512", func(t *testing.T) {
envstore.EnvStoreObj.UpdateEnvVariable(constants.StringStoreIdentifier, constants.EnvKeyJwtType, "HS512")
jwtToken, err := token.SignJWTToken(claims)
assert.NoError(t, err)
assert.NotEmpty(t, jwtToken)
c, err := token.ParseJWTToken(jwtToken, hostname, nonce, subject)
assert.NoError(t, err)
assert.Equal(t, c["email"].(string), claims["email"])
})
})
t.Run("RSA algorithms", func(t *testing.T) {
t.Run("RS256", func(t *testing.T) {
_, privateKey, publickKey, _, err := crypto.NewRSAKey("RS256", clientID)
assert.NoError(t, err)
envstore.EnvStoreObj.UpdateEnvVariable(constants.StringStoreIdentifier, constants.EnvKeyJwtType, "RS256")
envstore.EnvStoreObj.UpdateEnvVariable(constants.StringStoreIdentifier, constants.EnvKeyJwtPrivateKey, privateKey)
envstore.EnvStoreObj.UpdateEnvVariable(constants.StringStoreIdentifier, constants.EnvKeyJwtPublicKey, publickKey)
jwtToken, err := token.SignJWTToken(claims)
assert.NoError(t, err)
assert.NotEmpty(t, jwtToken)
c, err := token.ParseJWTToken(jwtToken, hostname, nonce, subject)
assert.NoError(t, err)
assert.Equal(t, c["email"].(string), claims["email"])
})
t.Run("RS384", func(t *testing.T) {
_, privateKey, publickKey, _, err := crypto.NewRSAKey("RS384", clientID)
assert.NoError(t, err)
envstore.EnvStoreObj.UpdateEnvVariable(constants.StringStoreIdentifier, constants.EnvKeyJwtType, "RS384")
envstore.EnvStoreObj.UpdateEnvVariable(constants.StringStoreIdentifier, constants.EnvKeyJwtPrivateKey, privateKey)
envstore.EnvStoreObj.UpdateEnvVariable(constants.StringStoreIdentifier, constants.EnvKeyJwtPublicKey, publickKey)
jwtToken, err := token.SignJWTToken(claims)
assert.NoError(t, err)
assert.NotEmpty(t, jwtToken)
c, err := token.ParseJWTToken(jwtToken, hostname, nonce, subject)
assert.NoError(t, err)
assert.Equal(t, c["email"].(string), claims["email"])
})
t.Run("RS512", func(t *testing.T) {
_, privateKey, publickKey, _, err := crypto.NewRSAKey("RS512", clientID)
assert.NoError(t, err)
envstore.EnvStoreObj.UpdateEnvVariable(constants.StringStoreIdentifier, constants.EnvKeyJwtType, "RS512")
envstore.EnvStoreObj.UpdateEnvVariable(constants.StringStoreIdentifier, constants.EnvKeyJwtPrivateKey, privateKey)
envstore.EnvStoreObj.UpdateEnvVariable(constants.StringStoreIdentifier, constants.EnvKeyJwtPublicKey, publickKey)
jwtToken, err := token.SignJWTToken(claims)
assert.NoError(t, err)
assert.NotEmpty(t, jwtToken)
c, err := token.ParseJWTToken(jwtToken, hostname, nonce, subject)
assert.NoError(t, err)
assert.Equal(t, c["email"].(string), claims["email"])
})
})
t.Run("ECDSA algorithms", func(t *testing.T) {
t.Run("ES256", func(t *testing.T) {
_, privateKey, publickKey, _, err := crypto.NewECDSAKey("ES256", clientID)
assert.NoError(t, err)
envstore.EnvStoreObj.UpdateEnvVariable(constants.StringStoreIdentifier, constants.EnvKeyJwtType, "ES256")
envstore.EnvStoreObj.UpdateEnvVariable(constants.StringStoreIdentifier, constants.EnvKeyJwtPrivateKey, privateKey)
envstore.EnvStoreObj.UpdateEnvVariable(constants.StringStoreIdentifier, constants.EnvKeyJwtPublicKey, publickKey)
jwtToken, err := token.SignJWTToken(claims)
assert.NoError(t, err)
assert.NotEmpty(t, jwtToken)
c, err := token.ParseJWTToken(jwtToken, hostname, nonce, subject)
assert.NoError(t, err)
assert.Equal(t, c["email"].(string), claims["email"])
})
t.Run("ES384", func(t *testing.T) {
_, privateKey, publickKey, _, err := crypto.NewECDSAKey("ES384", clientID)
assert.NoError(t, err)
envstore.EnvStoreObj.UpdateEnvVariable(constants.StringStoreIdentifier, constants.EnvKeyJwtType, "ES384")
envstore.EnvStoreObj.UpdateEnvVariable(constants.StringStoreIdentifier, constants.EnvKeyJwtPrivateKey, privateKey)
envstore.EnvStoreObj.UpdateEnvVariable(constants.StringStoreIdentifier, constants.EnvKeyJwtPublicKey, publickKey)
jwtToken, err := token.SignJWTToken(claims)
assert.NoError(t, err)
assert.NotEmpty(t, jwtToken)
c, err := token.ParseJWTToken(jwtToken, hostname, nonce, subject)
assert.NoError(t, err)
assert.Equal(t, c["email"].(string), claims["email"])
})
t.Run("ES512", func(t *testing.T) {
_, privateKey, publickKey, _, err := crypto.NewECDSAKey("ES512", clientID)
assert.NoError(t, err)
envstore.EnvStoreObj.UpdateEnvVariable(constants.StringStoreIdentifier, constants.EnvKeyJwtType, "ES512")
envstore.EnvStoreObj.UpdateEnvVariable(constants.StringStoreIdentifier, constants.EnvKeyJwtPrivateKey, privateKey)
envstore.EnvStoreObj.UpdateEnvVariable(constants.StringStoreIdentifier, constants.EnvKeyJwtPublicKey, publickKey)
jwtToken, err := token.SignJWTToken(claims)
assert.NoError(t, err)
assert.NotEmpty(t, jwtToken)
c, err := token.ParseJWTToken(jwtToken, hostname, nonce, subject)
assert.NoError(t, err)
assert.Equal(t, c["email"].(string), claims["email"])
})
})
envstore.EnvStoreObj.UpdateEnvVariable(constants.StringStoreIdentifier, constants.EnvKeyJwtType, jwtType)
envstore.EnvStoreObj.UpdateEnvVariable(constants.StringStoreIdentifier, constants.EnvKeyJwtPublicKey, publicKey)
envstore.EnvStoreObj.UpdateEnvVariable(constants.StringStoreIdentifier, constants.EnvKeyJwtPrivateKey, privateKey)
}

View File

@@ -5,14 +5,17 @@ import (
"github.com/authorizerdev/authorizer/server/constants" "github.com/authorizerdev/authorizer/server/constants"
"github.com/authorizerdev/authorizer/server/db" "github.com/authorizerdev/authorizer/server/db"
"github.com/authorizerdev/authorizer/server/envstore"
"github.com/authorizerdev/authorizer/server/graph/model" "github.com/authorizerdev/authorizer/server/graph/model"
"github.com/authorizerdev/authorizer/server/resolvers" "github.com/authorizerdev/authorizer/server/resolvers"
"github.com/authorizerdev/authorizer/server/utils"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
) )
func loginTests(t *testing.T, s TestSetup) { func loginTests(t *testing.T, s TestSetup) {
t.Helper() t.Helper()
t.Run(`should login`, func(t *testing.T) { t.Run(`should login`, func(t *testing.T) {
t.Logf("=> is enabled: %v", envstore.EnvStoreObj.GetBoolStoreEnvVariable(constants.EnvKeyDisableEmailVerification))
_, ctx := createContext(s) _, ctx := createContext(s)
email := "login." + s.TestInfo.Email email := "login." + s.TestInfo.Email
_, err := resolvers.SignupResolver(ctx, model.SignUpInput{ _, err := resolvers.SignupResolver(ctx, model.SignUpInput{
@@ -21,18 +24,23 @@ func loginTests(t *testing.T, s TestSetup) {
ConfirmPassword: s.TestInfo.Password, ConfirmPassword: s.TestInfo.Password,
}) })
_, err = resolvers.LoginResolver(ctx, model.LoginInput{ res, err := resolvers.LoginResolver(ctx, model.LoginInput{
Email: email, Email: email,
Password: s.TestInfo.Password, Password: s.TestInfo.Password,
}) })
assert.NotNil(t, err, "should fail because email is not verified") assert.NotNil(t, err, "should fail because email is not verified")
assert.Nil(t, res)
verificationRequest, err := db.Provider.GetVerificationRequestByEmail(email, constants.VerificationTypeBasicAuthSignup) verificationRequest, err := db.Provider.GetVerificationRequestByEmail(email, constants.VerificationTypeBasicAuthSignup)
resolvers.VerifyEmailResolver(ctx, model.VerifyEmailInput{ n, err := utils.EncryptNonce(verificationRequest.Nonce)
assert.NoError(t, err)
assert.NotEmpty(t, n)
assert.NotNil(t, verificationRequest)
res, err = resolvers.VerifyEmailResolver(ctx, model.VerifyEmailInput{
Token: verificationRequest.Token, Token: verificationRequest.Token,
}) })
assert.NoError(t, err)
assert.NotNil(t, res)
_, err = resolvers.LoginResolver(ctx, model.LoginInput{ _, err = resolvers.LoginResolver(ctx, model.LoginInput{
Email: email, Email: email,
Password: s.TestInfo.Password, Password: s.TestInfo.Password,

View File

@@ -2,7 +2,6 @@ package test
import ( import (
"fmt" "fmt"
"net/url"
"testing" "testing"
"github.com/authorizerdev/authorizer/server/constants" "github.com/authorizerdev/authorizer/server/constants"
@@ -11,7 +10,6 @@ import (
"github.com/authorizerdev/authorizer/server/graph/model" "github.com/authorizerdev/authorizer/server/graph/model"
"github.com/authorizerdev/authorizer/server/resolvers" "github.com/authorizerdev/authorizer/server/resolvers"
"github.com/authorizerdev/authorizer/server/sessionstore" "github.com/authorizerdev/authorizer/server/sessionstore"
"github.com/authorizerdev/authorizer/server/utils"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
) )
@@ -30,18 +28,15 @@ func logoutTests(t *testing.T, s TestSetup) {
Token: verificationRequest.Token, Token: verificationRequest.Token,
}) })
sessions := sessionstore.GetUserSessions(verifyRes.User.ID)
fingerPrint := ""
refreshToken := ""
for key, val := range sessions {
fingerPrint = key
refreshToken = val
}
fingerPrintHash, _ := utils.EncryptAES([]byte(fingerPrint))
token := *verifyRes.AccessToken token := *verifyRes.AccessToken
cookie := fmt.Sprintf("%s=%s;%s=%s;%s=%s", envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyCookieName)+".fingerprint", url.QueryEscape(string(fingerPrintHash)), envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyCookieName)+".refresh_token", refreshToken, envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyCookieName)+".access_token", token) sessions := sessionstore.GetUserSessions(verifyRes.User.ID)
cookie := ""
// set all they keys in cookie one of them should be session cookie
for key := range sessions {
if key != token {
cookie += fmt.Sprintf("%s=%s;", envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyCookieName)+"_session", key)
}
}
req.Header.Set("Cookie", cookie) req.Header.Set("Cookie", cookie)
_, err = resolvers.LogoutResolver(ctx) _, err = resolvers.LogoutResolver(ctx)

View File

@@ -1,12 +1,11 @@
package test package test
import ( import (
"fmt" "context"
"testing" "testing"
"github.com/authorizerdev/authorizer/server/constants" "github.com/authorizerdev/authorizer/server/constants"
"github.com/authorizerdev/authorizer/server/db" "github.com/authorizerdev/authorizer/server/db"
"github.com/authorizerdev/authorizer/server/envstore"
"github.com/authorizerdev/authorizer/server/graph/model" "github.com/authorizerdev/authorizer/server/graph/model"
"github.com/authorizerdev/authorizer/server/resolvers" "github.com/authorizerdev/authorizer/server/resolvers"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
@@ -27,12 +26,13 @@ func magicLinkLoginTests(t *testing.T, s TestSetup) {
verifyRes, err := resolvers.VerifyEmailResolver(ctx, model.VerifyEmailInput{ verifyRes, err := resolvers.VerifyEmailResolver(ctx, model.VerifyEmailInput{
Token: verificationRequest.Token, Token: verificationRequest.Token,
}) })
assert.NoError(t, err)
token := *verifyRes.AccessToken assert.NotNil(t, verifyRes.AccessToken)
req.Header.Set("Cookie", fmt.Sprintf("%s=%s", envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyCookieName)+".access_token", token)) s.GinContext.Request.Header.Set("Authorization", "Bearer "+*verifyRes.AccessToken)
ctx = context.WithValue(req.Context(), "GinContextKey", s.GinContext)
_, err = resolvers.ProfileResolver(ctx) _, err = resolvers.ProfileResolver(ctx)
assert.Nil(t, err) assert.Nil(t, err)
s.GinContext.Request.Header.Set("Authorization", "")
cleanData(email) cleanData(email)
}) })
} }

View File

@@ -1,12 +1,11 @@
package test package test
import ( import (
"fmt" "context"
"testing" "testing"
"github.com/authorizerdev/authorizer/server/constants" "github.com/authorizerdev/authorizer/server/constants"
"github.com/authorizerdev/authorizer/server/db" "github.com/authorizerdev/authorizer/server/db"
"github.com/authorizerdev/authorizer/server/envstore"
"github.com/authorizerdev/authorizer/server/graph/model" "github.com/authorizerdev/authorizer/server/graph/model"
"github.com/authorizerdev/authorizer/server/resolvers" "github.com/authorizerdev/authorizer/server/resolvers"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
@@ -14,7 +13,7 @@ import (
func profileTests(t *testing.T, s TestSetup) { func profileTests(t *testing.T, s TestSetup) {
t.Helper() t.Helper()
t.Run(`should get profile only with token`, func(t *testing.T) { t.Run(`should get profile only access_token token`, func(t *testing.T) {
req, ctx := createContext(s) req, ctx := createContext(s)
email := "profile." + s.TestInfo.Email email := "profile." + s.TestInfo.Email
@@ -31,11 +30,14 @@ func profileTests(t *testing.T, s TestSetup) {
verifyRes, err := resolvers.VerifyEmailResolver(ctx, model.VerifyEmailInput{ verifyRes, err := resolvers.VerifyEmailResolver(ctx, model.VerifyEmailInput{
Token: verificationRequest.Token, Token: verificationRequest.Token,
}) })
assert.NoError(t, err)
assert.NotNil(t, verifyRes.AccessToken)
token := *verifyRes.AccessToken s.GinContext.Request.Header.Set("Authorization", "Bearer "+*verifyRes.AccessToken)
req.Header.Set("Cookie", fmt.Sprintf("%s=%s", envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyCookieName)+".access_token", token)) ctx = context.WithValue(req.Context(), "GinContextKey", s.GinContext)
profileRes, err := resolvers.ProfileResolver(ctx) profileRes, err := resolvers.ProfileResolver(ctx)
assert.Nil(t, err) assert.Nil(t, err)
s.GinContext.Request.Header.Set("Authorization", "")
newEmail := *&profileRes.Email newEmail := *&profileRes.Email
assert.Equal(t, email, newEmail, "emails should be equal") assert.Equal(t, email, newEmail, "emails should be equal")

View File

@@ -1,7 +1,6 @@
package test package test
import ( import (
"log"
"testing" "testing"
"github.com/authorizerdev/authorizer/server/constants" "github.com/authorizerdev/authorizer/server/constants"
@@ -12,29 +11,31 @@ import (
func TestResolvers(t *testing.T) { func TestResolvers(t *testing.T) {
databases := map[string]string{ databases := map[string]string{
constants.DbTypeSqlite: "../../data.db", constants.DbTypeSqlite: "../../data.db",
constants.DbTypeArangodb: "http://localhost:8529", // constants.DbTypeArangodb: "http://localhost:8529",
constants.DbTypeMongodb: "mongodb://localhost:27017", // constants.DbTypeMongodb: "mongodb://localhost:27017",
} }
envstore.EnvInMemoryStoreObj.UpdateEnvVariable(constants.StringStoreIdentifier, constants.EnvKeyVersion, "test") envstore.EnvStoreObj.UpdateEnvVariable(constants.StringStoreIdentifier, constants.EnvKeyVersion, "test")
for dbType, dbURL := range databases { for dbType, dbURL := range databases {
envstore.EnvInMemoryStoreObj.UpdateEnvVariable(constants.StringStoreIdentifier, constants.EnvKeyDatabaseURL, dbURL)
envstore.EnvInMemoryStoreObj.UpdateEnvVariable(constants.StringStoreIdentifier, constants.EnvKeyDatabaseType, dbType)
s := testSetup() s := testSetup()
envstore.EnvStoreObj.UpdateEnvVariable(constants.StringStoreIdentifier, constants.EnvKeyDatabaseURL, dbURL)
envstore.EnvStoreObj.UpdateEnvVariable(constants.StringStoreIdentifier, constants.EnvKeyDatabaseType, dbType)
defer s.Server.Close() defer s.Server.Close()
err := db.InitDB()
db.InitDB() if err != nil {
t.Errorf("Error initializing database: %s", err)
}
// clean the persisted config for test to use fresh config // clean the persisted config for test to use fresh config
envData, err := db.Provider.GetEnv() envData, err := db.Provider.GetEnv()
log.Println("=> envData:", envstore.EnvInMemoryStoreObj.GetEnvStoreClone())
if err == nil { if err == nil {
envData.EnvData = "" envData.EnvData = ""
db.Provider.UpdateEnv(envData) db.Provider.UpdateEnv(envData)
} }
env.PersistEnv() env.PersistEnv()
envstore.EnvStoreObj.UpdateEnvVariable(constants.StringStoreIdentifier, constants.EnvKeyEnv, "test")
envstore.EnvStoreObj.UpdateEnvVariable(constants.BoolStoreIdentifier, constants.EnvKeyIsProd, false)
t.Run("should pass tests for "+dbType, func(t *testing.T) { t.Run("should pass tests for "+dbType, func(t *testing.T) {
// admin tests // admin tests
adminSignupTests(t, s) adminSignupTests(t, s)
@@ -61,7 +62,6 @@ func TestResolvers(t *testing.T) {
magicLinkLoginTests(t, s) magicLinkLoginTests(t, s)
logoutTests(t, s) logoutTests(t, s)
metaTests(t, s) metaTests(t, s)
isValidJWTTests(t, s)
}) })
} }
} }

View File

@@ -2,7 +2,7 @@ package test
import ( import (
"fmt" "fmt"
"net/url" "strings"
"testing" "testing"
"github.com/authorizerdev/authorizer/server/constants" "github.com/authorizerdev/authorizer/server/constants"
@@ -11,7 +11,6 @@ import (
"github.com/authorizerdev/authorizer/server/graph/model" "github.com/authorizerdev/authorizer/server/graph/model"
"github.com/authorizerdev/authorizer/server/resolvers" "github.com/authorizerdev/authorizer/server/resolvers"
"github.com/authorizerdev/authorizer/server/sessionstore" "github.com/authorizerdev/authorizer/server/sessionstore"
"github.com/authorizerdev/authorizer/server/utils"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
) )
@@ -36,17 +35,15 @@ func sessionTests(t *testing.T, s TestSetup) {
}) })
sessions := sessionstore.GetUserSessions(verifyRes.User.ID) sessions := sessionstore.GetUserSessions(verifyRes.User.ID)
fingerPrint := "" cookie := ""
refreshToken := ""
for key, val := range sessions {
fingerPrint = key
refreshToken = val
}
fingerPrintHash, _ := utils.EncryptAES([]byte(fingerPrint))
token := *verifyRes.AccessToken token := *verifyRes.AccessToken
cookie := fmt.Sprintf("%s=%s;%s=%s;%s=%s", envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyCookieName)+".fingerprint", url.QueryEscape(string(fingerPrintHash)), envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyCookieName)+".refresh_token", refreshToken, envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyCookieName)+".access_token", token) // set all they keys in cookie one of them should be session cookie
for key := range sessions {
if key != token {
cookie += fmt.Sprintf("%s=%s;", envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyCookieName)+"_session", key)
}
}
cookie = strings.TrimSuffix(cookie, ";")
req.Header.Set("Cookie", cookie) req.Header.Set("Cookie", cookie)

View File

@@ -71,14 +71,16 @@ func testSetup() TestSetup {
Password: "test", Password: "test",
} }
envstore.EnvInMemoryStoreObj.UpdateEnvVariable(constants.StringStoreIdentifier, constants.EnvKeyEnvPath, "../../.env.sample") envstore.EnvStoreObj.UpdateEnvVariable(constants.StringStoreIdentifier, constants.EnvKeyEnvPath, "../../.env.sample")
envstore.EnvInMemoryStoreObj.UpdateEnvVariable(constants.StringStoreIdentifier, constants.EnvKeySmtpHost, "smtp.yopmail.com") env.InitRequiredEnv()
envstore.EnvInMemoryStoreObj.UpdateEnvVariable(constants.StringStoreIdentifier, constants.EnvKeySmtpPort, "2525") envstore.EnvStoreObj.UpdateEnvVariable(constants.StringStoreIdentifier, constants.EnvKeySmtpHost, "smtp.yopmail.com")
envstore.EnvInMemoryStoreObj.UpdateEnvVariable(constants.StringStoreIdentifier, constants.EnvKeySmtpUsername, "lakhan@yopmail.com") envstore.EnvStoreObj.UpdateEnvVariable(constants.StringStoreIdentifier, constants.EnvKeySmtpPort, "2525")
envstore.EnvInMemoryStoreObj.UpdateEnvVariable(constants.StringStoreIdentifier, constants.EnvKeySmtpPassword, "test") envstore.EnvStoreObj.UpdateEnvVariable(constants.StringStoreIdentifier, constants.EnvKeySmtpUsername, "lakhan@yopmail.com")
envstore.EnvInMemoryStoreObj.UpdateEnvVariable(constants.StringStoreIdentifier, constants.EnvKeySenderEmail, "info@yopmail.com") envstore.EnvStoreObj.UpdateEnvVariable(constants.StringStoreIdentifier, constants.EnvKeySmtpPassword, "test")
envstore.EnvInMemoryStoreObj.UpdateEnvVariable(constants.SliceStoreIdentifier, constants.EnvKeyProtectedRoles, []string{"admin"}) envstore.EnvStoreObj.UpdateEnvVariable(constants.StringStoreIdentifier, constants.EnvKeySenderEmail, "info@yopmail.com")
env.InitEnv() envstore.EnvStoreObj.UpdateEnvVariable(constants.SliceStoreIdentifier, constants.EnvKeyProtectedRoles, []string{"admin"})
db.InitDB()
env.InitAllEnv()
sessionstore.InitSession() sessionstore.InitSession()
w := httptest.NewRecorder() w := httptest.NewRecorder()

View File

@@ -5,10 +5,10 @@ import (
"testing" "testing"
"github.com/authorizerdev/authorizer/server/constants" "github.com/authorizerdev/authorizer/server/constants"
"github.com/authorizerdev/authorizer/server/crypto"
"github.com/authorizerdev/authorizer/server/envstore" "github.com/authorizerdev/authorizer/server/envstore"
"github.com/authorizerdev/authorizer/server/graph/model" "github.com/authorizerdev/authorizer/server/graph/model"
"github.com/authorizerdev/authorizer/server/resolvers" "github.com/authorizerdev/authorizer/server/resolvers"
"github.com/authorizerdev/authorizer/server/utils"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
) )
@@ -16,16 +16,16 @@ func updateEnvTests(t *testing.T, s TestSetup) {
t.Helper() t.Helper()
t.Run(`should update envs`, func(t *testing.T) { t.Run(`should update envs`, func(t *testing.T) {
req, ctx := createContext(s) req, ctx := createContext(s)
originalAppURL := envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyAppURL) originalAppURL := envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyAppURL)
data := model.UpdateEnvInput{} data := model.UpdateEnvInput{}
_, err := resolvers.UpdateEnvResolver(ctx, data) _, err := resolvers.UpdateEnvResolver(ctx, data)
assert.NotNil(t, err) assert.NotNil(t, err)
h, err := utils.EncryptPassword(envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyAdminSecret)) h, err := crypto.EncryptPassword(envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyAdminSecret))
assert.Nil(t, err) assert.Nil(t, err)
req.Header.Set("Cookie", fmt.Sprintf("%s=%s", envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyAdminCookieName), h)) req.Header.Set("Cookie", fmt.Sprintf("%s=%s", envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyAdminCookieName), h))
newURL := "https://test.com" newURL := "https://test.com"
disableLoginPage := true disableLoginPage := true
allowedOrigins := []string{"http://localhost:8080"} allowedOrigins := []string{"http://localhost:8080"}
@@ -37,9 +37,9 @@ func updateEnvTests(t *testing.T, s TestSetup) {
_, err = resolvers.UpdateEnvResolver(ctx, data) _, err = resolvers.UpdateEnvResolver(ctx, data)
assert.Nil(t, err) assert.Nil(t, err)
assert.Equal(t, envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyAppURL), newURL) assert.Equal(t, envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyAppURL), newURL)
assert.True(t, envstore.EnvInMemoryStoreObj.GetBoolStoreEnvVariable(constants.EnvKeyDisableLoginPage)) assert.True(t, envstore.EnvStoreObj.GetBoolStoreEnvVariable(constants.EnvKeyDisableLoginPage))
assert.Equal(t, envstore.EnvInMemoryStoreObj.GetSliceStoreEnvVariable(constants.EnvKeyAllowedOrigins), allowedOrigins) assert.Equal(t, envstore.EnvStoreObj.GetSliceStoreEnvVariable(constants.EnvKeyAllowedOrigins), allowedOrigins)
disableLoginPage = false disableLoginPage = false
data = model.UpdateEnvInput{ data = model.UpdateEnvInput{

View File

@@ -1,12 +1,11 @@
package test package test
import ( import (
"fmt" "context"
"testing" "testing"
"github.com/authorizerdev/authorizer/server/constants" "github.com/authorizerdev/authorizer/server/constants"
"github.com/authorizerdev/authorizer/server/db" "github.com/authorizerdev/authorizer/server/db"
"github.com/authorizerdev/authorizer/server/envstore"
"github.com/authorizerdev/authorizer/server/graph/model" "github.com/authorizerdev/authorizer/server/graph/model"
"github.com/authorizerdev/authorizer/server/resolvers" "github.com/authorizerdev/authorizer/server/resolvers"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
@@ -34,18 +33,16 @@ func updateProfileTests(t *testing.T, s TestSetup) {
verifyRes, err := resolvers.VerifyEmailResolver(ctx, model.VerifyEmailInput{ verifyRes, err := resolvers.VerifyEmailResolver(ctx, model.VerifyEmailInput{
Token: verificationRequest.Token, Token: verificationRequest.Token,
}) })
assert.NoError(t, err)
token := *verifyRes.AccessToken s.GinContext.Request.Header.Set("Authorization", "Bearer "+*verifyRes.AccessToken)
req.Header.Set("Cookie", fmt.Sprintf("%s=%s", envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyCookieName)+".access_token", token)) ctx = context.WithValue(req.Context(), "GinContextKey", s.GinContext)
_, err = resolvers.UpdateProfileResolver(ctx, model.UpdateProfileInput{
FamilyName: &fName,
})
assert.Nil(t, err)
newEmail := "new_" + email newEmail := "new_" + email
_, err = resolvers.UpdateProfileResolver(ctx, model.UpdateProfileInput{ _, err = resolvers.UpdateProfileResolver(ctx, model.UpdateProfileInput{
Email: &newEmail, Email: &newEmail,
}) })
s.GinContext.Request.Header.Set("Authorization", "")
assert.Nil(t, err) assert.Nil(t, err)
_, err = resolvers.ProfileResolver(ctx) _, err = resolvers.ProfileResolver(ctx)
assert.NotNil(t, err, "unauthorized") assert.NotNil(t, err, "unauthorized")

View File

@@ -5,10 +5,10 @@ import (
"testing" "testing"
"github.com/authorizerdev/authorizer/server/constants" "github.com/authorizerdev/authorizer/server/constants"
"github.com/authorizerdev/authorizer/server/crypto"
"github.com/authorizerdev/authorizer/server/envstore" "github.com/authorizerdev/authorizer/server/envstore"
"github.com/authorizerdev/authorizer/server/graph/model" "github.com/authorizerdev/authorizer/server/graph/model"
"github.com/authorizerdev/authorizer/server/resolvers" "github.com/authorizerdev/authorizer/server/resolvers"
"github.com/authorizerdev/authorizer/server/utils"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
) )
@@ -33,9 +33,9 @@ func updateUserTest(t *testing.T, s TestSetup) {
}) })
assert.NotNil(t, err, "unauthorized") assert.NotNil(t, err, "unauthorized")
h, err := utils.EncryptPassword(envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyAdminSecret)) h, err := crypto.EncryptPassword(envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyAdminSecret))
assert.Nil(t, err) assert.Nil(t, err)
req.Header.Set("Cookie", fmt.Sprintf("%s=%s", envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyAdminCookieName), h)) req.Header.Set("Cookie", fmt.Sprintf("%s=%s", envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyAdminCookieName), h))
_, err = resolvers.UpdateUserResolver(ctx, model.UpdateUserInput{ _, err = resolvers.UpdateUserResolver(ctx, model.UpdateUserInput{
ID: user.ID, ID: user.ID,
Roles: newRoles, Roles: newRoles,

View File

@@ -2,14 +2,13 @@ package test
import ( import (
"fmt" "fmt"
"log"
"testing" "testing"
"github.com/authorizerdev/authorizer/server/constants" "github.com/authorizerdev/authorizer/server/constants"
"github.com/authorizerdev/authorizer/server/crypto"
"github.com/authorizerdev/authorizer/server/envstore" "github.com/authorizerdev/authorizer/server/envstore"
"github.com/authorizerdev/authorizer/server/graph/model" "github.com/authorizerdev/authorizer/server/graph/model"
"github.com/authorizerdev/authorizer/server/resolvers" "github.com/authorizerdev/authorizer/server/resolvers"
"github.com/authorizerdev/authorizer/server/utils"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
) )
@@ -36,13 +35,12 @@ func usersTest(t *testing.T, s TestSetup) {
usersRes, err := resolvers.UsersResolver(ctx, pagination) usersRes, err := resolvers.UsersResolver(ctx, pagination)
assert.NotNil(t, err, "unauthorized") assert.NotNil(t, err, "unauthorized")
h, err := utils.EncryptPassword(envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyAdminSecret)) h, err := crypto.EncryptPassword(envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyAdminSecret))
assert.Nil(t, err) assert.Nil(t, err)
req.Header.Set("Cookie", fmt.Sprintf("%s=%s", envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyAdminCookieName), h)) req.Header.Set("Cookie", fmt.Sprintf("%s=%s", envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyAdminCookieName), h))
usersRes, err = resolvers.UsersResolver(ctx, pagination) usersRes, err = resolvers.UsersResolver(ctx, pagination)
assert.Nil(t, err) assert.Nil(t, err)
log.Println("=> userRes:", usersRes)
rLen := len(usersRes.Users) rLen := len(usersRes.Users)
assert.GreaterOrEqual(t, rLen, 1) assert.GreaterOrEqual(t, rLen, 1)

View File

@@ -22,7 +22,7 @@ func TestIsValidEmail(t *testing.T) {
func TestIsValidOrigin(t *testing.T) { func TestIsValidOrigin(t *testing.T) {
// don't use portocal(http/https) for ALLOWED_ORIGINS while testing, // don't use portocal(http/https) for ALLOWED_ORIGINS while testing,
// as we trim them off while running the main function // as we trim them off while running the main function
envstore.EnvInMemoryStoreObj.UpdateEnvVariable(constants.SliceStoreIdentifier, constants.EnvKeyAllowedOrigins, []string{"localhost:8080", "*.google.com", "*.google.in", "*abc.*"}) envstore.EnvStoreObj.UpdateEnvVariable(constants.SliceStoreIdentifier, constants.EnvKeyAllowedOrigins, []string{"localhost:8080", "*.google.com", "*.google.in", "*abc.*"})
assert.False(t, utils.IsValidOrigin("http://myapp.com"), "it should be invalid origin") assert.False(t, utils.IsValidOrigin("http://myapp.com"), "it should be invalid origin")
assert.False(t, utils.IsValidOrigin("http://appgoogle.com"), "it should be invalid origin") assert.False(t, utils.IsValidOrigin("http://appgoogle.com"), "it should be invalid origin")
assert.True(t, utils.IsValidOrigin("http://app.google.com"), "it should be valid origin") assert.True(t, utils.IsValidOrigin("http://app.google.com"), "it should be valid origin")
@@ -32,7 +32,7 @@ func TestIsValidOrigin(t *testing.T) {
assert.True(t, utils.IsValidOrigin("http://xyx.abc.in"), "it should be valid origin") assert.True(t, utils.IsValidOrigin("http://xyx.abc.in"), "it should be valid origin")
assert.True(t, utils.IsValidOrigin("http://xyxabc.in"), "it should be valid origin") assert.True(t, utils.IsValidOrigin("http://xyxabc.in"), "it should be valid origin")
assert.True(t, utils.IsValidOrigin("http://localhost:8080"), "it should be valid origin") assert.True(t, utils.IsValidOrigin("http://localhost:8080"), "it should be valid origin")
envstore.EnvInMemoryStoreObj.UpdateEnvVariable(constants.SliceStoreIdentifier, constants.EnvKeyAllowedOrigins, []string{"*"}) envstore.EnvStoreObj.UpdateEnvVariable(constants.SliceStoreIdentifier, constants.EnvKeyAllowedOrigins, []string{"*"})
} }
func TestIsValidIdentifier(t *testing.T) { func TestIsValidIdentifier(t *testing.T) {

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