Compare commits

..

24 Commits

Author SHA1 Message Date
Lakhan Samani
fadd9f6168 fix: update scope for apple login 2022-06-14 13:11:39 +05:30
Lakhan Samani
395e2e2a85 fix: update scope for apple login 2022-06-14 12:35:23 +05:30
Lakhan Samani
6335084835 fix: add post method support for oauth callback 2022-06-14 12:17:43 +05:30
Lakhan Samani
eab336cd3d fix: apple login params 2022-06-14 12:06:46 +05:30
Lakhan Samani
f4691fca1f fix: id token parsing 2022-06-14 11:38:04 +05:30
Lakhan Samani
341d4fbae5 fix: scope for apple login 2022-06-14 11:21:26 +05:30
Lakhan Samani
e467b45ab1 fix: apple client secret field 2022-06-14 11:11:09 +05:30
Lakhan Samani
7edfad3486 fix: apple client secret field 2022-06-14 10:56:47 +05:30
Lakhan Samani
80578b88ac feat: update app 2022-06-13 07:37:26 +05:30
Lakhan Samani
5646e7a0e7 feat: add test code to process apple user 2022-06-12 18:30:33 +05:30
Lakhan Samani
53a592ef63 feat: add base for apple login 2022-06-12 14:49:48 +05:30
Lakhan Samani
3337dbd0a4 Merge pull request #191 from authorizerdev/fix/session-invalidation
fix: session invalidation
2022-06-12 09:08:57 +05:30
Lakhan Samani
82a2a42f84 fix: user session access 2022-06-12 00:27:21 +05:30
Lakhan Samani
ac49b5bb70 fix: session tests 2022-06-11 19:24:53 +05:30
Lakhan Samani
926ab07c07 fix: session invalidation 2022-06-11 19:10:39 +05:30
Lakhan Samani
7a2dbea019 Merge branch 'main' of https://github.com/authorizerdev/authorizer 2022-06-09 23:43:28 +05:30
Lakhan Samani
dff50097e8 feat: add support for cockroachdb 2022-06-09 23:43:21 +05:30
Lakhan Samani
aff9d3af20 Merge pull request #187 from authorizerdev/fix-parallel-access
fix: parallel access of env vars
2022-06-09 23:13:34 +05:30
Lakhan Samani
02eb1d6677 fix: add const for test env 2022-06-09 23:13:22 +05:30
Lakhan Samani
78a673e4ad fix: fix parallel access of env vars 2022-06-08 09:50:30 +05:30
Lakhan Samani
e0d8644264 fix: role validation while signup 2022-06-07 08:00:30 +05:30
Lakhan Samani
d8c662eaad fix: dashboard roles 2022-06-07 07:30:01 +05:30
Lakhan Samani
6d1d259f71 Merge pull request #182 from authorizerdev/feat/add-linkedin-login
feat: add linkedin login
2022-06-06 22:09:08 +05:30
Lakhan Samani
2841853d37 feat: add linkedin login 2022-06-06 22:08:32 +05:30
57 changed files with 1356 additions and 445 deletions

42
app/package-lock.json generated
View File

@@ -9,7 +9,7 @@
"version": "1.0.0",
"license": "ISC",
"dependencies": {
"@authorizerdev/authorizer-react": "^0.17.0",
"@authorizerdev/authorizer-react": "^0.24.0-beta.1",
"@types/react": "^17.0.15",
"@types/react-dom": "^17.0.9",
"esbuild": "^0.12.17",
@@ -26,9 +26,9 @@
}
},
"node_modules/@authorizerdev/authorizer-js": {
"version": "0.10.0",
"resolved": "https://registry.npmjs.org/@authorizerdev/authorizer-js/-/authorizer-js-0.10.0.tgz",
"integrity": "sha512-REM8FLD/Ej9gzA2zDGDAke6QFss33ubePlTDmLDmIYUuQmpHFlO5mCCS6nVsKkN7F/Bcwkmp+eUNQjkdGCaKLg==",
"version": "0.13.0-beta.2",
"resolved": "https://registry.npmjs.org/@authorizerdev/authorizer-js/-/authorizer-js-0.13.0-beta.2.tgz",
"integrity": "sha512-3K3Qew/npD375DQdJnYb43aWVOU7giG2+8sHOjy8aXhZ+GlQQ8cGu54lozFYUMDcM1HjQU2N53xLmneONPepSw==",
"dependencies": {
"node-fetch": "^2.6.1"
},
@@ -37,11 +37,11 @@
}
},
"node_modules/@authorizerdev/authorizer-react": {
"version": "0.17.0",
"resolved": "https://registry.npmjs.org/@authorizerdev/authorizer-react/-/authorizer-react-0.17.0.tgz",
"integrity": "sha512-7WcNCU7hDFkVfFb8LcJXFwWiLYd8aY78z1AbNPxCa2Cw5G85PaRkzjKybP6h01ITVOHO6M03lLwPj8p6Sr6fEg==",
"version": "0.24.0-beta.1",
"resolved": "https://registry.npmjs.org/@authorizerdev/authorizer-react/-/authorizer-react-0.24.0-beta.1.tgz",
"integrity": "sha512-S/Oqc24EfotbrABuv379i/3uCfQPYJQqXrOU9d8AytF++pzG/2dcoIoaMbWZQkATR3m6a5AnhpG6bIB+4NbrUQ==",
"dependencies": {
"@authorizerdev/authorizer-js": "^0.10.0",
"@authorizerdev/authorizer-js": "^0.13.0-beta.2",
"final-form": "^4.20.2",
"react-final-form": "^6.5.3",
"styled-components": "^5.3.0"
@@ -816,7 +816,7 @@
"node_modules/tr46": {
"version": "0.0.3",
"resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz",
"integrity": "sha1-gYT9NH2snNwYWZLzpmIuFLnZq2o="
"integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw=="
},
"node_modules/typescript": {
"version": "4.3.5",
@@ -838,12 +838,12 @@
"node_modules/webidl-conversions": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz",
"integrity": "sha1-JFNCdeKnvGvnvIZhHMFq4KVlSHE="
"integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ=="
},
"node_modules/whatwg-url": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz",
"integrity": "sha1-lmRU6HZUYuN2RNNib2dCzotwll0=",
"integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==",
"dependencies": {
"tr46": "~0.0.3",
"webidl-conversions": "^3.0.0"
@@ -852,19 +852,19 @@
},
"dependencies": {
"@authorizerdev/authorizer-js": {
"version": "0.10.0",
"resolved": "https://registry.npmjs.org/@authorizerdev/authorizer-js/-/authorizer-js-0.10.0.tgz",
"integrity": "sha512-REM8FLD/Ej9gzA2zDGDAke6QFss33ubePlTDmLDmIYUuQmpHFlO5mCCS6nVsKkN7F/Bcwkmp+eUNQjkdGCaKLg==",
"version": "0.13.0-beta.2",
"resolved": "https://registry.npmjs.org/@authorizerdev/authorizer-js/-/authorizer-js-0.13.0-beta.2.tgz",
"integrity": "sha512-3K3Qew/npD375DQdJnYb43aWVOU7giG2+8sHOjy8aXhZ+GlQQ8cGu54lozFYUMDcM1HjQU2N53xLmneONPepSw==",
"requires": {
"node-fetch": "^2.6.1"
}
},
"@authorizerdev/authorizer-react": {
"version": "0.17.0",
"resolved": "https://registry.npmjs.org/@authorizerdev/authorizer-react/-/authorizer-react-0.17.0.tgz",
"integrity": "sha512-7WcNCU7hDFkVfFb8LcJXFwWiLYd8aY78z1AbNPxCa2Cw5G85PaRkzjKybP6h01ITVOHO6M03lLwPj8p6Sr6fEg==",
"version": "0.24.0-beta.1",
"resolved": "https://registry.npmjs.org/@authorizerdev/authorizer-react/-/authorizer-react-0.24.0-beta.1.tgz",
"integrity": "sha512-S/Oqc24EfotbrABuv379i/3uCfQPYJQqXrOU9d8AytF++pzG/2dcoIoaMbWZQkATR3m6a5AnhpG6bIB+4NbrUQ==",
"requires": {
"@authorizerdev/authorizer-js": "^0.10.0",
"@authorizerdev/authorizer-js": "^0.13.0-beta.2",
"final-form": "^4.20.2",
"react-final-form": "^6.5.3",
"styled-components": "^5.3.0"
@@ -1482,7 +1482,7 @@
"tr46": {
"version": "0.0.3",
"resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz",
"integrity": "sha1-gYT9NH2snNwYWZLzpmIuFLnZq2o="
"integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw=="
},
"typescript": {
"version": "4.3.5",
@@ -1497,12 +1497,12 @@
"webidl-conversions": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz",
"integrity": "sha1-JFNCdeKnvGvnvIZhHMFq4KVlSHE="
"integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ=="
},
"whatwg-url": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz",
"integrity": "sha1-lmRU6HZUYuN2RNNib2dCzotwll0=",
"integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==",
"requires": {
"tr46": "~0.0.3",
"webidl-conversions": "^3.0.0"

View File

@@ -11,7 +11,7 @@
"author": "Lakhan Samani",
"license": "ISC",
"dependencies": {
"@authorizerdev/authorizer-react": "^0.17.0",
"@authorizerdev/authorizer-react": "^0.24.0-beta.1",
"@types/react": "^17.0.15",
"@types/react-dom": "^17.0.9",
"esbuild": "^0.12.17",

View File

@@ -9,7 +9,13 @@ import {
Divider,
useMediaQuery,
} from '@chakra-ui/react';
import { FaGoogle, FaGithub, FaFacebookF } from 'react-icons/fa';
import {
FaGoogle,
FaGithub,
FaFacebookF,
FaLinkedin,
FaApple,
} from 'react-icons/fa';
import { TextInputType, HiddenInputType } from '../../constants';
const OAuthConfig = ({
@@ -182,6 +188,82 @@ const OAuthConfig = ({
/>
</Center>
</Flex>
<Flex direction={isNotSmallerScreen ? 'row' : 'column'}>
<Center
w={isNotSmallerScreen ? '55px' : '35px'}
h="35px"
marginRight="1.5%"
border="1px solid #3b5998"
borderRadius="5px"
>
<FaLinkedin style={{ color: '#3b5998' }} />
</Center>
<Center
w={isNotSmallerScreen ? '70%' : '100%'}
mt={isNotSmallerScreen ? '0' : '3'}
marginRight="1.5%"
>
<InputField
borderRadius={5}
variables={envVariables}
setVariables={setVariables}
inputType={TextInputType.LINKEDIN_CLIENT_ID}
placeholder="LinkedIn Client ID"
/>
</Center>
<Center
w={isNotSmallerScreen ? '70%' : '100%'}
mt={isNotSmallerScreen ? '0' : '3'}
>
<InputField
borderRadius={5}
variables={envVariables}
setVariables={setVariables}
fieldVisibility={fieldVisibility}
setFieldVisibility={setFieldVisibility}
inputType={HiddenInputType.LINKEDIN_CLIENT_SECRET}
placeholder="LinkedIn Client Secret"
/>
</Center>
</Flex>
<Flex direction={isNotSmallerScreen ? 'row' : 'column'}>
<Center
w={isNotSmallerScreen ? '55px' : '35px'}
h="35px"
marginRight="1.5%"
border="1px solid #3b5998"
borderRadius="5px"
>
<FaApple style={{ color: '#3b5998' }} />
</Center>
<Center
w={isNotSmallerScreen ? '70%' : '100%'}
mt={isNotSmallerScreen ? '0' : '3'}
marginRight="1.5%"
>
<InputField
borderRadius={5}
variables={envVariables}
setVariables={setVariables}
inputType={TextInputType.APPLE_CLIENT_ID}
placeholder="Apple Client ID"
/>
</Center>
<Center
w={isNotSmallerScreen ? '70%' : '100%'}
mt={isNotSmallerScreen ? '0' : '3'}
>
<InputField
borderRadius={5}
variables={envVariables}
setVariables={setVariables}
fieldVisibility={fieldVisibility}
setFieldVisibility={setFieldVisibility}
inputType={HiddenInputType.APPLE_CLIENT_SECRET}
placeholder="Apple CLient Secret"
/>
</Center>
</Flex>
</Stack>
</Box>
</div>

View File

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

View File

@@ -7,6 +7,8 @@ export const TextInputType = {
GOOGLE_CLIENT_ID: 'GOOGLE_CLIENT_ID',
GITHUB_CLIENT_ID: 'GITHUB_CLIENT_ID',
FACEBOOK_CLIENT_ID: 'FACEBOOK_CLIENT_ID',
LINKEDIN_CLIENT_ID: 'LINKEDIN_CLIENT_ID',
APPLE_CLIENT_ID: 'APPLE_CLIENT_ID',
JWT_ROLE_CLAIM: 'JWT_ROLE_CLAIM',
REDIS_URL: 'REDIS_URL',
SMTP_HOST: 'SMTP_HOST',
@@ -31,6 +33,8 @@ export const HiddenInputType = {
GOOGLE_CLIENT_SECRET: 'GOOGLE_CLIENT_SECRET',
GITHUB_CLIENT_SECRET: 'GITHUB_CLIENT_SECRET',
FACEBOOK_CLIENT_SECRET: 'FACEBOOK_CLIENT_SECRET',
LINKEDIN_CLIENT_SECRET: 'LINKEDIN_CLIENT_SECRET',
APPLE_CLIENT_SECRET: 'APPLE_CLIENT_SECRET',
JWT_SECRET: 'JWT_SECRET',
SMTP_PASSWORD: 'SMTP_PASSWORD',
ADMIN_SECRET: 'ADMIN_SECRET',
@@ -99,6 +103,10 @@ export interface envVarTypes {
GITHUB_CLIENT_SECRET: string;
FACEBOOK_CLIENT_ID: string;
FACEBOOK_CLIENT_SECRET: string;
LINKEDIN_CLIENT_ID: string;
LINKEDIN_CLIENT_SECRET: string;
APPLE_CLIENT_ID: string;
APPLE_CLIENT_SECRET: string;
ROLES: [string] | [];
DEFAULT_ROLES: [string] | [];
PROTECTED_ROLES: [string] | [];

View File

@@ -26,9 +26,13 @@ export const EnvVariablesQuery = `
GITHUB_CLIENT_SECRET,
FACEBOOK_CLIENT_ID,
FACEBOOK_CLIENT_SECRET,
ROLES,
LINKEDIN_CLIENT_ID,
LINKEDIN_CLIENT_SECRET,
APPLE_CLIENT_ID,
APPLE_CLIENT_SECRET,
DEFAULT_ROLES,
PROTECTED_ROLES,
ROLES,
JWT_TYPE,
JWT_SECRET,
JWT_ROLE_CLAIM,

View File

@@ -46,6 +46,10 @@ const Environment = () => {
GITHUB_CLIENT_SECRET: '',
FACEBOOK_CLIENT_ID: '',
FACEBOOK_CLIENT_SECRET: '',
LINKEDIN_CLIENT_ID: '',
LINKEDIN_CLIENT_SECRET: '',
APPLE_CLIENT_ID: '',
APPLE_CLIENT_SECRET: '',
ROLES: [],
DEFAULT_ROLES: [],
PROTECTED_ROLES: [],
@@ -83,6 +87,8 @@ const Environment = () => {
GOOGLE_CLIENT_SECRET: false,
GITHUB_CLIENT_SECRET: false,
FACEBOOK_CLIENT_SECRET: false,
LINKEDIN_CLIENT_SECRET: false,
APPLE_CLIENT_SECRET: false,
JWT_SECRET: false,
SMTP_PASSWORD: false,
ADMIN_SECRET: false,

6
package-lock.json generated Normal file
View File

@@ -0,0 +1,6 @@
{
"name": "authorizer",
"lockfileVersion": 2,
"requires": true,
"packages": {}
}

View File

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

View File

@@ -3,6 +3,8 @@ package constants
var VERSION = "0.0.1"
const (
// TestEnv is used for testing
TestEnv = "test"
// EnvKeyEnv key for env variable ENV
EnvKeyEnv = "ENV"
// EnvKeyEnvPath key for cli arg variable ENV_PATH
@@ -73,6 +75,14 @@ const (
EnvKeyFacebookClientID = "FACEBOOK_CLIENT_ID"
// EnvKeyFacebookClientSecret key for env variable FACEBOOK_CLIENT_SECRET
EnvKeyFacebookClientSecret = "FACEBOOK_CLIENT_SECRET"
// EnvKeyLinkedinClientID key for env variable LINKEDIN_CLIENT_ID
EnvKeyLinkedInClientID = "LINKEDIN_CLIENT_ID"
// EnvKeyLinkedinClientSecret key for env variable LINKEDIN_CLIENT_SECRET
EnvKeyLinkedInClientSecret = "LINKEDIN_CLIENT_SECRET"
// EnvKeyAppleClientID key for env variable APPLE_CLIENT_ID
EnvKeyAppleClientID = "APPLE_CLIENT_ID"
// EnvKeyAppleClientSecret key for env variable APPLE_CLIENT_SECRET
EnvKeyAppleClientSecret = "APPLE_CLIENT_SECRET"
// EnvKeyOrganizationName key for env variable ORGANIZATION_NAME
EnvKeyOrganizationName = "ORGANIZATION_NAME"
// EnvKeyOrganizationLogo key for env variable ORGANIZATION_LOGO

View File

@@ -8,4 +8,7 @@ const (
FacebookUserInfoURL = "https://graph.facebook.com/me?fields=id,first_name,last_name,name,email,picture&access_token="
// Ref: https://docs.github.com/en/developers/apps/building-github-apps/identifying-and-authorizing-users-for-github-apps#3-your-github-app-accesses-the-api-with-the-users-access-token
GithubUserInfoURL = "https://api.github.com/user"
// Ref: https://docs.microsoft.com/en-us/linkedin/shared/integrations/people/profile-api
LinkedInUserInfoURL = "https://api.linkedin.com/v2/me?projection=(id,localizedFirstName,localizedLastName,emailAddress,profilePicture(displayImage~:playableStreams))"
LinkedInEmailURL = "https://api.linkedin.com/v2/emailAddress?q=members&projection=(elements*(handle~))"
)

View File

@@ -11,4 +11,8 @@ const (
SignupMethodGithub = "github"
// SignupMethodFacebook is the facebook signup method
SignupMethodFacebook = "facebook"
// SignupMethodLinkedin is the linkedin signup method
SignupMethodLinkedIn = "linkedin"
// SignupMethodApple is the apple signup method
SignupMethodApple = "apple"
)

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

32
server/env/env.go vendored
View File

@@ -68,6 +68,10 @@ func InitAllEnv() error {
osGithubClientSecret := os.Getenv(constants.EnvKeyGithubClientSecret)
osFacebookClientID := os.Getenv(constants.EnvKeyFacebookClientID)
osFacebookClientSecret := os.Getenv(constants.EnvKeyFacebookClientSecret)
osLinkedInClientID := os.Getenv(constants.EnvKeyLinkedInClientID)
osLinkedInClientSecret := os.Getenv(constants.EnvKeyLinkedInClientSecret)
osAppleClientID := os.Getenv(constants.EnvKeyAppleClientID)
osAppleClientSecret := os.Getenv(constants.EnvKeyAppleClientSecret)
osResetPasswordURL := os.Getenv(constants.EnvKeyResetPasswordURL)
osOrganizationName := os.Getenv(constants.EnvKeyOrganizationName)
osOrganizationLogo := os.Getenv(constants.EnvKeyOrganizationLogo)
@@ -345,6 +349,34 @@ func InitAllEnv() error {
envData[constants.EnvKeyFacebookClientSecret] = osFacebookClientSecret
}
if val, ok := envData[constants.EnvKeyLinkedInClientID]; !ok || val == "" {
envData[constants.EnvKeyLinkedInClientID] = osLinkedInClientID
}
if osFacebookClientID != "" && envData[constants.EnvKeyLinkedInClientID] != osFacebookClientID {
envData[constants.EnvKeyLinkedInClientID] = osLinkedInClientID
}
if val, ok := envData[constants.EnvKeyLinkedInClientSecret]; !ok || val == "" {
envData[constants.EnvKeyLinkedInClientSecret] = osLinkedInClientSecret
}
if osFacebookClientSecret != "" && envData[constants.EnvKeyLinkedInClientSecret] != osFacebookClientSecret {
envData[constants.EnvKeyLinkedInClientSecret] = osLinkedInClientSecret
}
if val, ok := envData[constants.EnvKeyAppleClientID]; !ok || val == "" {
envData[constants.EnvKeyAppleClientID] = osAppleClientID
}
if osFacebookClientID != "" && envData[constants.EnvKeyAppleClientID] != osFacebookClientID {
envData[constants.EnvKeyAppleClientID] = osAppleClientID
}
if val, ok := envData[constants.EnvKeyAppleClientSecret]; !ok || val == "" {
envData[constants.EnvKeyAppleClientSecret] = osAppleClientSecret
}
if osFacebookClientSecret != "" && envData[constants.EnvKeyAppleClientSecret] != osFacebookClientSecret {
envData[constants.EnvKeyAppleClientSecret] = osAppleClientSecret
}
if val, ok := envData[constants.EnvKeyResetPasswordURL]; !ok || val == "" {
envData[constants.EnvKeyResetPasswordURL] = strings.TrimPrefix(osResetPasswordURL, "/")
}

View File

@@ -57,6 +57,8 @@ type ComplexityRoot struct {
AdminSecret func(childComplexity int) int
AllowedOrigins func(childComplexity int) int
AppURL func(childComplexity int) int
AppleClientID func(childComplexity int) int
AppleClientSecret func(childComplexity int) int
ClientID func(childComplexity int) int
ClientSecret func(childComplexity int) int
CustomAccessTokenScript func(childComplexity int) int
@@ -85,6 +87,8 @@ type ComplexityRoot struct {
JwtRoleClaim func(childComplexity int) int
JwtSecret func(childComplexity int) int
JwtType func(childComplexity int) int
LinkedinClientID func(childComplexity int) int
LinkedinClientSecret func(childComplexity int) int
OrganizationLogo func(childComplexity int) int
OrganizationName func(childComplexity int) int
ProtectedRoles func(childComplexity int) int
@@ -111,11 +115,13 @@ type ComplexityRoot struct {
Meta struct {
ClientID func(childComplexity int) int
IsAppleLoginEnabled func(childComplexity int) int
IsBasicAuthenticationEnabled func(childComplexity int) int
IsEmailVerificationEnabled func(childComplexity int) int
IsFacebookLoginEnabled func(childComplexity int) int
IsGithubLoginEnabled func(childComplexity int) int
IsGoogleLoginEnabled func(childComplexity int) int
IsLinkedinLoginEnabled func(childComplexity int) int
IsMagicLinkLoginEnabled func(childComplexity int) int
IsSignUpEnabled func(childComplexity int) int
Version func(childComplexity int) int
@@ -332,6 +338,20 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in
return e.complexity.Env.AppURL(childComplexity), true
case "Env.APPLE_CLIENT_ID":
if e.complexity.Env.AppleClientID == nil {
break
}
return e.complexity.Env.AppleClientID(childComplexity), true
case "Env.APPLE_CLIENT_SECRET":
if e.complexity.Env.AppleClientSecret == nil {
break
}
return e.complexity.Env.AppleClientSecret(childComplexity), true
case "Env.CLIENT_ID":
if e.complexity.Env.ClientID == nil {
break
@@ -528,6 +548,20 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in
return e.complexity.Env.JwtType(childComplexity), true
case "Env.LINKEDIN_CLIENT_ID":
if e.complexity.Env.LinkedinClientID == nil {
break
}
return e.complexity.Env.LinkedinClientID(childComplexity), true
case "Env.LINKEDIN_CLIENT_SECRET":
if e.complexity.Env.LinkedinClientSecret == nil {
break
}
return e.complexity.Env.LinkedinClientSecret(childComplexity), true
case "Env.ORGANIZATION_LOGO":
if e.complexity.Env.OrganizationLogo == nil {
break
@@ -647,6 +681,13 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in
return e.complexity.Meta.ClientID(childComplexity), true
case "Meta.is_apple_login_enabled":
if e.complexity.Meta.IsAppleLoginEnabled == nil {
break
}
return e.complexity.Meta.IsAppleLoginEnabled(childComplexity), true
case "Meta.is_basic_authentication_enabled":
if e.complexity.Meta.IsBasicAuthenticationEnabled == nil {
break
@@ -682,6 +723,13 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in
return e.complexity.Meta.IsGoogleLoginEnabled(childComplexity), true
case "Meta.is_linkedin_login_enabled":
if e.complexity.Meta.IsLinkedinLoginEnabled == nil {
break
}
return e.complexity.Meta.IsLinkedinLoginEnabled(childComplexity), true
case "Meta.is_magic_link_login_enabled":
if e.complexity.Meta.IsMagicLinkLoginEnabled == nil {
break
@@ -1352,6 +1400,8 @@ type Meta {
is_google_login_enabled: Boolean!
is_facebook_login_enabled: Boolean!
is_github_login_enabled: Boolean!
is_linkedin_login_enabled: Boolean!
is_apple_login_enabled: Boolean!
is_email_verification_enabled: Boolean!
is_basic_authentication_enabled: Boolean!
is_magic_link_login_enabled: Boolean!
@@ -1462,6 +1512,10 @@ type Env {
GITHUB_CLIENT_SECRET: String
FACEBOOK_CLIENT_ID: String
FACEBOOK_CLIENT_SECRET: String
LINKEDIN_CLIENT_ID: String
LINKEDIN_CLIENT_SECRET: String
APPLE_CLIENT_ID: String
APPLE_CLIENT_SECRET: String
ORGANIZATION_NAME: String
ORGANIZATION_LOGO: String
}
@@ -1509,6 +1563,10 @@ input UpdateEnvInput {
GITHUB_CLIENT_SECRET: String
FACEBOOK_CLIENT_ID: String
FACEBOOK_CLIENT_SECRET: String
LINKEDIN_CLIENT_ID: String
LINKEDIN_CLIENT_SECRET: String
APPLE_CLIENT_ID: String
APPLE_CLIENT_SECRET: String
ORGANIZATION_NAME: String
ORGANIZATION_LOGO: String
}
@@ -3602,6 +3660,134 @@ func (ec *executionContext) _Env_FACEBOOK_CLIENT_SECRET(ctx context.Context, fie
return ec.marshalOString2ᚖstring(ctx, field.Selections, res)
}
func (ec *executionContext) _Env_LINKEDIN_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.LinkedinClientID, 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_LINKEDIN_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.LinkedinClientSecret, nil
})
if err != nil {
ec.Error(ctx, err)
return graphql.Null
}
if resTmp == nil {
return graphql.Null
}
res := resTmp.(*string)
fc.Result = res
return ec.marshalOString2ᚖstring(ctx, field.Selections, res)
}
func (ec *executionContext) _Env_APPLE_CLIENT_ID(ctx context.Context, field graphql.CollectedField, obj *model.Env) (ret graphql.Marshaler) {
defer func() {
if r := recover(); r != nil {
ec.Error(ctx, ec.Recover(ctx, r))
ret = graphql.Null
}
}()
fc := &graphql.FieldContext{
Object: "Env",
Field: field,
Args: nil,
IsMethod: false,
IsResolver: false,
}
ctx = graphql.WithFieldContext(ctx, fc)
resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) {
ctx = rctx // use context from middleware stack in children
return obj.AppleClientID, nil
})
if err != nil {
ec.Error(ctx, err)
return graphql.Null
}
if resTmp == nil {
return graphql.Null
}
res := resTmp.(*string)
fc.Result = res
return ec.marshalOString2ᚖstring(ctx, field.Selections, res)
}
func (ec *executionContext) _Env_APPLE_CLIENT_SECRET(ctx context.Context, field graphql.CollectedField, obj *model.Env) (ret graphql.Marshaler) {
defer func() {
if r := recover(); r != nil {
ec.Error(ctx, ec.Recover(ctx, r))
ret = graphql.Null
}
}()
fc := &graphql.FieldContext{
Object: "Env",
Field: field,
Args: nil,
IsMethod: false,
IsResolver: false,
}
ctx = graphql.WithFieldContext(ctx, fc)
resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) {
ctx = rctx // use context from middleware stack in children
return obj.AppleClientSecret, nil
})
if err != nil {
ec.Error(ctx, err)
return graphql.Null
}
if resTmp == nil {
return graphql.Null
}
res := resTmp.(*string)
fc.Result = res
return ec.marshalOString2ᚖstring(ctx, field.Selections, res)
}
func (ec *executionContext) _Env_ORGANIZATION_NAME(ctx context.Context, field graphql.CollectedField, obj *model.Env) (ret graphql.Marshaler) {
defer func() {
if r := recover(); r != nil {
@@ -4007,6 +4193,76 @@ func (ec *executionContext) _Meta_is_github_login_enabled(ctx context.Context, f
return ec.marshalNBoolean2bool(ctx, field.Selections, res)
}
func (ec *executionContext) _Meta_is_linkedin_login_enabled(ctx context.Context, field graphql.CollectedField, obj *model.Meta) (ret graphql.Marshaler) {
defer func() {
if r := recover(); r != nil {
ec.Error(ctx, ec.Recover(ctx, r))
ret = graphql.Null
}
}()
fc := &graphql.FieldContext{
Object: "Meta",
Field: field,
Args: nil,
IsMethod: false,
IsResolver: false,
}
ctx = graphql.WithFieldContext(ctx, fc)
resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) {
ctx = rctx // use context from middleware stack in children
return obj.IsLinkedinLoginEnabled, nil
})
if err != nil {
ec.Error(ctx, err)
return graphql.Null
}
if resTmp == nil {
if !graphql.HasFieldError(ctx, fc) {
ec.Errorf(ctx, "must not be null")
}
return graphql.Null
}
res := resTmp.(bool)
fc.Result = res
return ec.marshalNBoolean2bool(ctx, field.Selections, res)
}
func (ec *executionContext) _Meta_is_apple_login_enabled(ctx context.Context, field graphql.CollectedField, obj *model.Meta) (ret graphql.Marshaler) {
defer func() {
if r := recover(); r != nil {
ec.Error(ctx, ec.Recover(ctx, r))
ret = graphql.Null
}
}()
fc := &graphql.FieldContext{
Object: "Meta",
Field: field,
Args: nil,
IsMethod: false,
IsResolver: false,
}
ctx = graphql.WithFieldContext(ctx, fc)
resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) {
ctx = rctx // use context from middleware stack in children
return obj.IsAppleLoginEnabled, nil
})
if err != nil {
ec.Error(ctx, err)
return graphql.Null
}
if resTmp == nil {
if !graphql.HasFieldError(ctx, fc) {
ec.Errorf(ctx, "must not be null")
}
return graphql.Null
}
res := resTmp.(bool)
fc.Result = res
return ec.marshalNBoolean2bool(ctx, field.Selections, res)
}
func (ec *executionContext) _Meta_is_email_verification_enabled(ctx context.Context, field graphql.CollectedField, obj *model.Meta) (ret graphql.Marshaler) {
defer func() {
if r := recover(); r != nil {
@@ -8563,6 +8819,38 @@ func (ec *executionContext) unmarshalInputUpdateEnvInput(ctx context.Context, ob
if err != nil {
return it, err
}
case "LINKEDIN_CLIENT_ID":
var err error
ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("LINKEDIN_CLIENT_ID"))
it.LinkedinClientID, err = ec.unmarshalOString2ᚖstring(ctx, v)
if err != nil {
return it, err
}
case "LINKEDIN_CLIENT_SECRET":
var err error
ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("LINKEDIN_CLIENT_SECRET"))
it.LinkedinClientSecret, err = ec.unmarshalOString2ᚖstring(ctx, v)
if err != nil {
return it, err
}
case "APPLE_CLIENT_ID":
var err error
ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("APPLE_CLIENT_ID"))
it.AppleClientID, err = ec.unmarshalOString2ᚖstring(ctx, v)
if err != nil {
return it, err
}
case "APPLE_CLIENT_SECRET":
var err error
ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("APPLE_CLIENT_SECRET"))
it.AppleClientSecret, err = ec.unmarshalOString2ᚖstring(ctx, v)
if err != nil {
return it, err
}
case "ORGANIZATION_NAME":
var err error
@@ -9031,6 +9319,14 @@ func (ec *executionContext) _Env(ctx context.Context, sel ast.SelectionSet, obj
out.Values[i] = ec._Env_FACEBOOK_CLIENT_ID(ctx, field, obj)
case "FACEBOOK_CLIENT_SECRET":
out.Values[i] = ec._Env_FACEBOOK_CLIENT_SECRET(ctx, field, obj)
case "LINKEDIN_CLIENT_ID":
out.Values[i] = ec._Env_LINKEDIN_CLIENT_ID(ctx, field, obj)
case "LINKEDIN_CLIENT_SECRET":
out.Values[i] = ec._Env_LINKEDIN_CLIENT_SECRET(ctx, field, obj)
case "APPLE_CLIENT_ID":
out.Values[i] = ec._Env_APPLE_CLIENT_ID(ctx, field, obj)
case "APPLE_CLIENT_SECRET":
out.Values[i] = ec._Env_APPLE_CLIENT_SECRET(ctx, field, obj)
case "ORGANIZATION_NAME":
out.Values[i] = ec._Env_ORGANIZATION_NAME(ctx, field, obj)
case "ORGANIZATION_LOGO":
@@ -9142,6 +9438,16 @@ func (ec *executionContext) _Meta(ctx context.Context, sel ast.SelectionSet, obj
if out.Values[i] == graphql.Null {
invalids++
}
case "is_linkedin_login_enabled":
out.Values[i] = ec._Meta_is_linkedin_login_enabled(ctx, field, obj)
if out.Values[i] == graphql.Null {
invalids++
}
case "is_apple_login_enabled":
out.Values[i] = ec._Meta_is_apple_login_enabled(ctx, field, obj)
if out.Values[i] == graphql.Null {
invalids++
}
case "is_email_verification_enabled":
out.Values[i] = ec._Meta_is_email_verification_enabled(ctx, field, obj)
if out.Values[i] == graphql.Null {

View File

@@ -65,6 +65,10 @@ type Env struct {
GithubClientSecret *string `json:"GITHUB_CLIENT_SECRET"`
FacebookClientID *string `json:"FACEBOOK_CLIENT_ID"`
FacebookClientSecret *string `json:"FACEBOOK_CLIENT_SECRET"`
LinkedinClientID *string `json:"LINKEDIN_CLIENT_ID"`
LinkedinClientSecret *string `json:"LINKEDIN_CLIENT_SECRET"`
AppleClientID *string `json:"APPLE_CLIENT_ID"`
AppleClientSecret *string `json:"APPLE_CLIENT_SECRET"`
OrganizationName *string `json:"ORGANIZATION_NAME"`
OrganizationLogo *string `json:"ORGANIZATION_LOGO"`
}
@@ -116,6 +120,8 @@ type Meta struct {
IsGoogleLoginEnabled bool `json:"is_google_login_enabled"`
IsFacebookLoginEnabled bool `json:"is_facebook_login_enabled"`
IsGithubLoginEnabled bool `json:"is_github_login_enabled"`
IsLinkedinLoginEnabled bool `json:"is_linkedin_login_enabled"`
IsAppleLoginEnabled bool `json:"is_apple_login_enabled"`
IsEmailVerificationEnabled bool `json:"is_email_verification_enabled"`
IsBasicAuthenticationEnabled bool `json:"is_basic_authentication_enabled"`
IsMagicLinkLoginEnabled bool `json:"is_magic_link_login_enabled"`
@@ -216,6 +222,10 @@ type UpdateEnvInput struct {
GithubClientSecret *string `json:"GITHUB_CLIENT_SECRET"`
FacebookClientID *string `json:"FACEBOOK_CLIENT_ID"`
FacebookClientSecret *string `json:"FACEBOOK_CLIENT_SECRET"`
LinkedinClientID *string `json:"LINKEDIN_CLIENT_ID"`
LinkedinClientSecret *string `json:"LINKEDIN_CLIENT_SECRET"`
AppleClientID *string `json:"APPLE_CLIENT_ID"`
AppleClientSecret *string `json:"APPLE_CLIENT_SECRET"`
OrganizationName *string `json:"ORGANIZATION_NAME"`
OrganizationLogo *string `json:"ORGANIZATION_LOGO"`
}

View File

@@ -18,6 +18,8 @@ type Meta {
is_google_login_enabled: Boolean!
is_facebook_login_enabled: Boolean!
is_github_login_enabled: Boolean!
is_linkedin_login_enabled: Boolean!
is_apple_login_enabled: Boolean!
is_email_verification_enabled: Boolean!
is_basic_authentication_enabled: Boolean!
is_magic_link_login_enabled: Boolean!
@@ -128,6 +130,10 @@ type Env {
GITHUB_CLIENT_SECRET: String
FACEBOOK_CLIENT_ID: String
FACEBOOK_CLIENT_SECRET: String
LINKEDIN_CLIENT_ID: String
LINKEDIN_CLIENT_SECRET: String
APPLE_CLIENT_ID: String
APPLE_CLIENT_SECRET: String
ORGANIZATION_NAME: String
ORGANIZATION_LOGO: String
}
@@ -175,6 +181,10 @@ input UpdateEnvInput {
GITHUB_CLIENT_SECRET: String
FACEBOOK_CLIENT_ID: String
FACEBOOK_CLIENT_SECRET: String
LINKEDIN_CLIENT_ID: String
LINKEDIN_CLIENT_SECRET: String
APPLE_CLIENT_ID: String
APPLE_CLIENT_SECRET: String
ORGANIZATION_NAME: String
ORGANIZATION_LOGO: String
}

View File

@@ -222,10 +222,7 @@ func AuthorizeHandler() gin.HandlerFunc {
// based on the response type, generate the response
if isResponseTypeCode {
// rollover the session for security
err = memorystore.Provider.RemoveState(sessionToken)
if err != nil {
log.Debug("Failed to remove state: ", err)
}
go memorystore.Provider.DeleteUserSession(user.ID, claims.Nonce)
nonce := uuid.New().String()
newSessionTokenData, newSessionToken, err := token.CreateSessionToken(user, nonce, claims.Roles, scope)
if err != nil {
@@ -246,7 +243,7 @@ func AuthorizeHandler() gin.HandlerFunc {
return
}
memorystore.Provider.SetState(newSessionToken, newSessionTokenData.Nonce+"@"+user.ID)
memorystore.Provider.SetUserSession(user.ID, constants.TokenTypeSessionToken+"_"+newSessionTokenData.Nonce, newSessionToken)
cookie.SetSession(gc, newSessionToken)
code := uuid.New().String()
memorystore.Provider.SetState(codeChallenge, code+"@"+newSessionToken)
@@ -283,9 +280,9 @@ func AuthorizeHandler() gin.HandlerFunc {
}
return
}
memorystore.Provider.RemoveState(sessionToken)
memorystore.Provider.SetState(authToken.FingerPrintHash, authToken.FingerPrint+"@"+user.ID)
memorystore.Provider.SetState(authToken.AccessToken.Token, authToken.FingerPrint+"@"+user.ID)
go memorystore.Provider.DeleteUserSession(user.ID, claims.Nonce)
memorystore.Provider.SetUserSession(user.ID, constants.TokenTypeSessionToken+"_"+authToken.FingerPrint, authToken.FingerPrintHash)
memorystore.Provider.SetUserSession(user.ID, constants.TokenTypeAccessToken+"_"+authToken.FingerPrint, authToken.AccessToken.Token)
cookie.SetSession(gc, authToken.FingerPrintHash)
expiresIn := authToken.AccessToken.ExpiresAt - time.Now().Unix()
@@ -308,7 +305,7 @@ func AuthorizeHandler() gin.HandlerFunc {
if authToken.RefreshToken != nil {
res["refresh_token"] = authToken.RefreshToken.Token
params += "&refresh_token=" + authToken.RefreshToken.Token
memorystore.Provider.SetState(authToken.RefreshToken.Token, authToken.FingerPrint+"@"+user.ID)
memorystore.Provider.SetUserSession(user.ID, constants.TokenTypeRefreshToken+"_"+authToken.FingerPrint, authToken.RefreshToken.Token)
}
if isQuery {

View File

@@ -1,6 +1,7 @@
package handlers
import (
"encoding/json"
"net/http"
"strings"
@@ -10,6 +11,7 @@ import (
"github.com/authorizerdev/authorizer/server/cookie"
"github.com/authorizerdev/authorizer/server/crypto"
"github.com/authorizerdev/authorizer/server/memorystore"
"github.com/authorizerdev/authorizer/server/token"
)
// Handler to logout user
@@ -35,12 +37,17 @@ func LogoutHandler() gin.HandlerFunc {
return
}
fingerPrint := string(decryptedFingerPrint)
err = memorystore.Provider.RemoveState(fingerPrint)
var sessionData token.SessionData
err = json.Unmarshal([]byte(decryptedFingerPrint), &sessionData)
if err != nil {
log.Debug("Failed to remove state: ", err)
log.Debug("Failed to decrypt fingerprint: ", err)
gc.JSON(http.StatusUnauthorized, gin.H{
"error": err.Error(),
})
return
}
memorystore.Provider.DeleteUserSession(sessionData.Subject, sessionData.Nonce)
cookie.DeleteSession(gc)
if redirectURL != "" {

View File

@@ -17,6 +17,7 @@ import (
"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/db/models"
"github.com/authorizerdev/authorizer/server/memorystore"
@@ -36,7 +37,6 @@ func OAuthCallbackHandler() gin.HandlerFunc {
log.Debug("Invalid oauth state: ", state)
c.JSON(400, gin.H{"error": "invalid oauth state"})
}
memorystore.Provider.GetState(state)
// contains random token, redirect url, role
sessionSplit := strings.Split(state, "___")
@@ -46,6 +46,9 @@ func OAuthCallbackHandler() gin.HandlerFunc {
return
}
// remove state from store
go memorystore.Provider.RemoveState(state)
stateValue := sessionSplit[0]
redirectURL := sessionSplit[1]
inputRoles := strings.Split(sessionSplit[2], ",")
@@ -60,6 +63,10 @@ func OAuthCallbackHandler() gin.HandlerFunc {
user, err = processGithubUserInfo(code)
case constants.SignupMethodFacebook:
user, err = processFacebookUserInfo(code)
case constants.SignupMethodLinkedIn:
user, err = processLinkedInUserInfo(code)
case constants.SignupMethodApple:
user, err = processAppleUserInfo(code)
default:
log.Info("Invalid oauth provider")
err = fmt.Errorf(`invalid oauth provider`)
@@ -115,9 +122,11 @@ func OAuthCallbackHandler() gin.HandlerFunc {
user.EmailVerifiedAt = &now
user, _ = db.Provider.AddUser(user)
} else {
user = existingUser
if user.RevokedTimestamp != nil {
log.Debug("User access revoked at: ", user.RevokedTimestamp)
c.JSON(400, gin.H{"error": "user access has been revoked"})
return
}
// user exists in db, check if method was google
@@ -126,7 +135,6 @@ func OAuthCallbackHandler() gin.HandlerFunc {
if !strings.Contains(signupMethod, provider) {
signupMethod = signupMethod + "," + provider
}
user = existingUser
user.SignupMethods = signupMethod
if user.EmailVerifiedAt == nil {
@@ -198,12 +206,12 @@ func OAuthCallbackHandler() gin.HandlerFunc {
params := "access_token=" + authToken.AccessToken.Token + "&token_type=bearer&expires_in=" + strconv.FormatInt(expiresIn, 10) + "&state=" + stateValue + "&id_token=" + authToken.IDToken.Token
cookie.SetSession(c, authToken.FingerPrintHash)
memorystore.Provider.SetState(authToken.FingerPrintHash, authToken.FingerPrint+"@"+user.ID)
memorystore.Provider.SetState(authToken.AccessToken.Token, authToken.FingerPrint+"@"+user.ID)
memorystore.Provider.SetUserSession(user.ID, constants.TokenTypeSessionToken+"_"+authToken.FingerPrint, authToken.FingerPrintHash)
memorystore.Provider.SetUserSession(user.ID, constants.TokenTypeAccessToken+"_"+authToken.FingerPrint, authToken.AccessToken.Token)
if authToken.RefreshToken != nil {
params = params + `&refresh_token=` + authToken.RefreshToken.Token
memorystore.Provider.SetState(authToken.RefreshToken.Token, authToken.FingerPrint+"@"+user.ID)
memorystore.Provider.SetUserSession(user.ID, constants.TokenTypeRefreshToken+"_"+authToken.FingerPrint, authToken.RefreshToken.Token)
}
go db.Provider.AddSession(models.Session{
@@ -256,7 +264,7 @@ func processGoogleUserInfo(code string) (models.User, error) {
func processGithubUserInfo(code string) (models.User, error) {
user := models.User{}
token, err := oauth.OAuthProviders.GithubConfig.Exchange(oauth2.NoContext, code)
oauth2Token, err := oauth.OAuthProviders.GithubConfig.Exchange(oauth2.NoContext, code)
if err != nil {
log.Debug("Failed to exchange code for token: ", err)
return user, fmt.Errorf("invalid github exchange code: %s", err.Error())
@@ -268,7 +276,7 @@ func processGithubUserInfo(code string) (models.User, error) {
return user, fmt.Errorf("error creating github user info request: %s", err.Error())
}
req.Header = http.Header{
"Authorization": []string{fmt.Sprintf("token %s", token.AccessToken)},
"Authorization": []string{fmt.Sprintf("token %s", oauth2Token.AccessToken)},
}
response, err := client.Do(req)
@@ -283,6 +291,10 @@ func processGithubUserInfo(code string) (models.User, error) {
log.Debug("Failed to read github user info response body: ", err)
return user, fmt.Errorf("failed to read github response body: %s", err.Error())
}
if response.StatusCode >= 400 {
log.Debug("Failed to request github user info: ", string(body))
return user, fmt.Errorf("failed to request github user info: %s", string(body))
}
userRawData := make(map[string]string)
json.Unmarshal(body, &userRawData)
@@ -311,13 +323,13 @@ func processGithubUserInfo(code string) (models.User, error) {
func processFacebookUserInfo(code string) (models.User, error) {
user := models.User{}
token, err := oauth.OAuthProviders.FacebookConfig.Exchange(oauth2.NoContext, code)
oauth2Token, err := oauth.OAuthProviders.FacebookConfig.Exchange(oauth2.NoContext, code)
if err != nil {
log.Debug("Invalid facebook exchange code: ", err)
return user, fmt.Errorf("invalid facebook exchange code: %s", err.Error())
}
client := http.Client{}
req, err := http.NewRequest("GET", constants.FacebookUserInfoURL+token.AccessToken, nil)
req, err := http.NewRequest("GET", constants.FacebookUserInfoURL+oauth2Token.AccessToken, nil)
if err != nil {
log.Debug("Error creating facebook user info request: ", err)
return user, fmt.Errorf("error creating facebook user info request: %s", err.Error())
@@ -335,7 +347,10 @@ func processFacebookUserInfo(code string) (models.User, error) {
log.Debug("Failed to read facebook response: ", err)
return user, fmt.Errorf("failed to read facebook response body: %s", err.Error())
}
if response.StatusCode >= 400 {
log.Debug("Failed to request facebook user info: ", string(body))
return user, fmt.Errorf("failed to request facebook user info: %s", string(body))
}
userRawData := make(map[string]interface{})
json.Unmarshal(body, &userRawData)
@@ -356,3 +371,140 @@ func processFacebookUserInfo(code string) (models.User, error) {
return user, nil
}
func processLinkedInUserInfo(code string) (models.User, error) {
user := models.User{}
oauth2Token, err := oauth.OAuthProviders.LinkedInConfig.Exchange(oauth2.NoContext, code)
if err != nil {
log.Debug("Failed to exchange code for token: ", err)
return user, fmt.Errorf("invalid linkedin exchange code: %s", err.Error())
}
client := http.Client{}
req, err := http.NewRequest("GET", constants.LinkedInUserInfoURL, nil)
if err != nil {
log.Debug("Failed to create linkedin user info request: ", err)
return user, fmt.Errorf("error creating linkedin user info request: %s", err.Error())
}
req.Header = http.Header{
"Authorization": []string{fmt.Sprintf("Bearer %s", oauth2Token.AccessToken)},
}
response, err := client.Do(req)
if err != nil {
log.Debug("Failed to request linkedin user info: ", err)
return user, err
}
defer response.Body.Close()
body, err := ioutil.ReadAll(response.Body)
if err != nil {
log.Debug("Failed to read linkedin user info response body: ", err)
return user, fmt.Errorf("failed to read linkedin response body: %s", err.Error())
}
if response.StatusCode >= 400 {
log.Debug("Failed to request linkedin user info: ", string(body))
return user, fmt.Errorf("failed to request linkedin user info: %s", string(body))
}
userRawData := make(map[string]interface{})
json.Unmarshal(body, &userRawData)
req, err = http.NewRequest("GET", constants.LinkedInEmailURL, nil)
if err != nil {
log.Debug("Failed to create linkedin email info request: ", err)
return user, fmt.Errorf("error creating linkedin user info request: %s", err.Error())
}
req.Header = http.Header{
"Authorization": []string{fmt.Sprintf("Bearer %s", oauth2Token.AccessToken)},
}
response, err = client.Do(req)
if err != nil {
log.Debug("Failed to request linkedin email info: ", err)
return user, err
}
defer response.Body.Close()
body, err = ioutil.ReadAll(response.Body)
if err != nil {
log.Debug("Failed to read linkedin email info response body: ", err)
return user, fmt.Errorf("failed to read linkedin email response body: %s", err.Error())
}
if response.StatusCode >= 400 {
log.Debug("Failed to request linkedin user info: ", string(body))
return user, fmt.Errorf("failed to request linkedin user info: %s", string(body))
}
emailRawData := make(map[string]interface{})
json.Unmarshal(body, &emailRawData)
firstName := userRawData["localizedFirstName"].(string)
lastName := userRawData["localizedLastName"].(string)
profilePicture := userRawData["profilePicture"].(map[string]interface{})["displayImage~"].(map[string]interface{})["elements"].([]interface{})[0].(map[string]interface{})["identifiers"].([]interface{})[0].(map[string]interface{})["identifier"].(string)
emailAddress := emailRawData["elements"].([]interface{})[0].(map[string]interface{})["handle~"].(map[string]interface{})["emailAddress"].(string)
user = models.User{
GivenName: &firstName,
FamilyName: &lastName,
Picture: &profilePicture,
Email: emailAddress,
}
return user, nil
}
func processAppleUserInfo(code string) (models.User, error) {
user := models.User{}
oauth2Token, err := oauth.OAuthProviders.AppleConfig.Exchange(oauth2.NoContext, code)
if err != nil {
log.Debug("Failed to exchange code for token: ", err)
return user, fmt.Errorf("invalid apple exchange code: %s", err.Error())
}
fmt.Println("=> token", oauth2Token.AccessToken)
// Extract the ID Token from OAuth2 token.
rawIDToken, ok := oauth2Token.Extra("id_token").(string)
if !ok {
log.Debug("Failed to extract ID Token from OAuth2 token")
return user, fmt.Errorf("unable to extract id_token")
}
fmt.Println("=> rawIDToken", rawIDToken)
tokenSplit := strings.Split(rawIDToken, ".")
claimsData := tokenSplit[1]
decodedClaimsData, err := crypto.DecryptB64(claimsData)
if err != nil {
log.Debug("Failed to decrypt claims data: ", err)
return user, fmt.Errorf("failed to decrypt claims data: %s", err.Error())
}
fmt.Println("=> decoded claims data", decodedClaimsData)
claims := make(map[string]interface{})
err = json.Unmarshal([]byte(decodedClaimsData), &claims)
if err != nil {
log.Debug("Failed to unmarshal claims data: ", err)
return user, fmt.Errorf("failed to unmarshal claims data: %s", err.Error())
}
fmt.Println("=> claims", claims)
if val, ok := claims["email"]; !ok {
log.Debug("Failed to extract email from claims")
return user, fmt.Errorf("unable to extract email")
} else {
user.Email = val.(string)
}
if val, ok := claims["name"]; ok {
nameData := val.(map[string]interface{})
givenName := nameData["firstName"].(string)
familyName := nameData["lastName"].(string)
user.GivenName = &givenName
user.FamilyName = &familyName
}
return user, err
}

View File

@@ -6,6 +6,7 @@ import (
"github.com/gin-gonic/gin"
log "github.com/sirupsen/logrus"
"golang.org/x/oauth2"
"github.com/authorizerdev/authorizer/server/constants"
"github.com/authorizerdev/authorizer/server/memorystore"
@@ -114,7 +115,7 @@ func OAuthLoginHandler() gin.HandlerFunc {
return
}
// during the init of OAuthProvider authorizer url might be empty
oauth.OAuthProviders.GoogleConfig.RedirectURL = hostname + "/oauth_callback/google"
oauth.OAuthProviders.GoogleConfig.RedirectURL = hostname + "/oauth_callback/" + constants.SignupMethodGoogle
url := oauth.OAuthProviders.GoogleConfig.AuthCodeURL(oauthStateString)
c.Redirect(http.StatusTemporaryRedirect, url)
case constants.SignupMethodGithub:
@@ -131,7 +132,7 @@ func OAuthLoginHandler() gin.HandlerFunc {
})
return
}
oauth.OAuthProviders.GithubConfig.RedirectURL = hostname + "/oauth_callback/github"
oauth.OAuthProviders.GithubConfig.RedirectURL = hostname + "/oauth_callback/" + constants.SignupMethodGithub
url := oauth.OAuthProviders.GithubConfig.AuthCodeURL(oauthStateString)
c.Redirect(http.StatusTemporaryRedirect, url)
case constants.SignupMethodFacebook:
@@ -148,9 +149,43 @@ func OAuthLoginHandler() gin.HandlerFunc {
})
return
}
oauth.OAuthProviders.FacebookConfig.RedirectURL = hostname + "/oauth_callback/facebook"
oauth.OAuthProviders.FacebookConfig.RedirectURL = hostname + "/oauth_callback/" + constants.SignupMethodFacebook
url := oauth.OAuthProviders.FacebookConfig.AuthCodeURL(oauthStateString)
c.Redirect(http.StatusTemporaryRedirect, url)
case constants.SignupMethodLinkedIn:
if oauth.OAuthProviders.LinkedInConfig == nil {
log.Debug("Linkedin OAuth provider is not configured")
isProviderConfigured = false
break
}
err := memorystore.Provider.SetState(oauthStateString, constants.SignupMethodLinkedIn)
if err != nil {
log.Debug("Error setting state: ", err)
c.JSON(500, gin.H{
"error": "internal server error",
})
return
}
oauth.OAuthProviders.LinkedInConfig.RedirectURL = hostname + "/oauth_callback/" + constants.SignupMethodLinkedIn
url := oauth.OAuthProviders.LinkedInConfig.AuthCodeURL(oauthStateString)
c.Redirect(http.StatusTemporaryRedirect, url)
case constants.SignupMethodApple:
if oauth.OAuthProviders.AppleConfig == nil {
log.Debug("Apple OAuth provider is not configured")
isProviderConfigured = false
break
}
err := memorystore.Provider.SetState(oauthStateString, constants.SignupMethodApple)
if err != nil {
log.Debug("Error setting state: ", err)
c.JSON(500, gin.H{
"error": "internal server error",
})
return
}
oauth.OAuthProviders.AppleConfig.RedirectURL = hostname + "/oauth_callback/" + constants.SignupMethodApple
url := oauth.OAuthProviders.AppleConfig.AuthCodeURL(oauthStateString, oauth2.SetAuthURLParam("response_mode", "form_post"))
c.Redirect(http.StatusTemporaryRedirect, url)
default:
log.Debug("Invalid oauth provider: ", provider)
c.JSON(422, gin.H{

View File

@@ -9,6 +9,7 @@ import (
"github.com/authorizerdev/authorizer/server/constants"
"github.com/authorizerdev/authorizer/server/memorystore"
"github.com/authorizerdev/authorizer/server/token"
)
// Revoke handler to revoke refresh token
@@ -45,7 +46,17 @@ func RevokeHandler() gin.HandlerFunc {
return
}
memorystore.Provider.RemoveState(refreshToken)
claims, err := token.ParseJWTToken(refreshToken)
if err != nil {
log.Debug("Client ID is invalid: ", clientID)
gc.JSON(http.StatusBadRequest, gin.H{
"error": err.Error(),
"error_description": "Failed to parse jwt",
})
return
}
memorystore.Provider.DeleteUserSession(claims["sub"].(string), claims["nonce"].(string))
gc.JSON(http.StatusOK, gin.H{
"message": "Token revoked successfully",

View File

@@ -107,6 +107,7 @@ func TokenHandler() gin.HandlerFunc {
return
}
go memorystore.Provider.RemoveState(encryptedCode)
// split session data
// it contains code@sessiontoken
sessionDataSplit := strings.Split(sessionData, "@")
@@ -130,11 +131,11 @@ func TokenHandler() gin.HandlerFunc {
})
return
}
// rollover the session for security
memorystore.Provider.RemoveState(sessionDataSplit[1])
userID = claims.Subject
roles = claims.Roles
scope = claims.Scope
// rollover the session for security
go memorystore.Provider.DeleteUserSession(userID, claims.Nonce)
} else {
// validate refresh token
if refreshToken == "" {
@@ -163,7 +164,7 @@ func TokenHandler() gin.HandlerFunc {
scope = append(scope, v.(string))
}
// remove older refresh token and rotate it for security
memorystore.Provider.RemoveState(refreshToken)
go memorystore.Provider.DeleteUserSession(userID, claims["nonce"].(string))
}
user, err := db.Provider.GetUserByID(userID)
@@ -185,8 +186,8 @@ func TokenHandler() gin.HandlerFunc {
})
return
}
memorystore.Provider.SetState(authToken.FingerPrintHash, authToken.FingerPrint+"@"+user.ID)
memorystore.Provider.SetState(authToken.AccessToken.Token, authToken.FingerPrint+"@"+user.ID)
memorystore.Provider.SetUserSession(user.ID, constants.TokenTypeSessionToken+"_"+authToken.FingerPrint, authToken.FingerPrintHash)
memorystore.Provider.SetUserSession(user.ID, constants.TokenTypeAccessToken+"_"+authToken.FingerPrint, authToken.AccessToken.Token)
cookie.SetSession(gc, authToken.FingerPrintHash)
expiresIn := authToken.AccessToken.ExpiresAt - time.Now().Unix()
@@ -204,7 +205,7 @@ func TokenHandler() gin.HandlerFunc {
if authToken.RefreshToken != nil {
res["refresh_token"] = authToken.RefreshToken.Token
memorystore.Provider.SetState(authToken.RefreshToken.Token, authToken.FingerPrint+"@"+user.ID)
memorystore.Provider.SetUserSession(user.ID, constants.TokenTypeRefreshToken+"_"+authToken.FingerPrint, authToken.RefreshToken.Token)
}
gc.JSON(http.StatusOK, res)

View File

@@ -9,6 +9,7 @@ import (
"github.com/gin-gonic/gin"
log "github.com/sirupsen/logrus"
"github.com/authorizerdev/authorizer/server/constants"
"github.com/authorizerdev/authorizer/server/cookie"
"github.com/authorizerdev/authorizer/server/db"
"github.com/authorizerdev/authorizer/server/db/models"
@@ -42,7 +43,7 @@ func VerifyEmailHandler() gin.HandlerFunc {
// verify if token exists in db
hostname := parsers.GetHost(c)
claim, err := token.ParseJWTToken(tokenInQuery, hostname, verificationRequest.Nonce, verificationRequest.Email)
claim, err := token.ParseJWTToken(tokenInQuery)
if err != nil {
log.Debug("Error parsing token: ", err)
errorRes["error_description"] = err.Error()
@@ -50,7 +51,14 @@ func VerifyEmailHandler() gin.HandlerFunc {
return
}
user, err := db.Provider.GetUserByEmail(claim["sub"].(string))
if ok, err := token.ValidateJWTClaims(claim, hostname, verificationRequest.Nonce, verificationRequest.Email); !ok || err != nil {
log.Debug("Error validating jwt claims: ", err)
errorRes["error_description"] = err.Error()
c.JSON(400, errorRes)
return
}
user, err := db.Provider.GetUserByEmail(verificationRequest.Email)
if err != nil {
log.Debug("Error getting user: ", err)
errorRes["error_description"] = err.Error()
@@ -100,12 +108,12 @@ func VerifyEmailHandler() gin.HandlerFunc {
params := "access_token=" + authToken.AccessToken.Token + "&token_type=bearer&expires_in=" + strconv.FormatInt(expiresIn, 10) + "&state=" + state + "&id_token=" + authToken.IDToken.Token
cookie.SetSession(c, authToken.FingerPrintHash)
memorystore.Provider.SetState(authToken.FingerPrintHash, authToken.FingerPrint+"@"+user.ID)
memorystore.Provider.SetState(authToken.AccessToken.Token, authToken.FingerPrint+"@"+user.ID)
memorystore.Provider.SetUserSession(user.ID, constants.TokenTypeSessionToken+"_"+authToken.FingerPrint, authToken.FingerPrintHash)
memorystore.Provider.SetUserSession(user.ID, constants.TokenTypeAccessToken+"_"+authToken.FingerPrint, authToken.AccessToken.Token)
if authToken.RefreshToken != nil {
params = params + `&refresh_token=${refresh_token}`
memorystore.Provider.SetState(authToken.RefreshToken.Token, authToken.FingerPrint+"@"+user.ID)
params = params + `&refresh_token=` + authToken.RefreshToken.Token
memorystore.Provider.SetUserSession(user.ID, constants.TokenTypeRefreshToken+"_"+authToken.FingerPrint, authToken.RefreshToken.Token)
}
if redirectURL == "" {

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,67 @@
package stores
import (
"os"
"sync"
"github.com/authorizerdev/authorizer/server/constants"
)
// SessionStore struct to store the env variables
type SessionStore struct {
mutex sync.Mutex
store map[string]map[string]string
}
// NewSessionStore create a new session store
func NewSessionStore() *SessionStore {
return &SessionStore{
mutex: sync.Mutex{},
store: make(map[string]map[string]string),
}
}
// Get returns the value of the key in state store
func (s *SessionStore) Get(key, subKey string) string {
return s.store[key][subKey]
}
// Set sets the value of the key in state store
func (s *SessionStore) Set(key string, subKey, value string) {
if os.Getenv("ENV") != constants.TestEnv {
s.mutex.Lock()
defer s.mutex.Unlock()
}
if _, ok := s.store[key]; !ok {
s.store[key] = make(map[string]string)
}
s.store[key][subKey] = value
}
// RemoveAll all values for given key
func (s *SessionStore) RemoveAll(key string) {
if os.Getenv("ENV") != constants.TestEnv {
s.mutex.Lock()
defer s.mutex.Unlock()
}
delete(s.store, key)
}
// Remove value for given key and subkey
func (s *SessionStore) Remove(key, subKey string) {
if os.Getenv("ENV") != constants.TestEnv {
s.mutex.Lock()
defer s.mutex.Unlock()
}
if _, ok := s.store[key]; ok {
delete(s.store[key], subKey)
}
}
// Get all the values for given key
func (s *SessionStore) GetAll(key string) map[string]string {
if _, ok := s.store[key]; !ok {
s.store[key] = make(map[string]string)
}
return s.store[key]
}

View File

@@ -0,0 +1,46 @@
package stores
import (
"os"
"sync"
"github.com/authorizerdev/authorizer/server/constants"
)
// StateStore struct to store the env variables
type StateStore struct {
mutex sync.Mutex
store map[string]string
}
// NewStateStore create a new state store
func NewStateStore() *StateStore {
return &StateStore{
mutex: sync.Mutex{},
store: make(map[string]string),
}
}
// Get returns the value of the key in state store
func (s *StateStore) Get(key string) string {
return s.store[key]
}
// Set sets the value of the key in state store
func (s *StateStore) Set(key string, value string) {
if os.Getenv("ENV") != constants.TestEnv {
s.mutex.Lock()
defer s.mutex.Unlock()
}
s.store[key] = value
}
// Remove removes the key from state store
func (s *StateStore) Remove(key string) {
if os.Getenv("ENV") != constants.TestEnv {
s.mutex.Lock()
defer s.mutex.Unlock()
}
delete(s.store, key)
}

View File

@@ -2,12 +2,17 @@ package providers
// Provider defines current memory store provider
type Provider interface {
// SetUserSession sets the user session
SetUserSession(userId, key, token string) error
// GetAllUserSessions returns all the user sessions from the session store
GetAllUserSessions(userId string) (map[string]string, error)
// GetUserSession returns the session token for given token
GetUserSession(userId, key string) (string, error)
// DeleteUserSession deletes the user session
DeleteUserSession(userId, key string) error
// DeleteAllSessions deletes all the sessions from the session store
DeleteAllUserSession(userId string) error
// GetUserSessions returns all the user sessions from the session store
GetUserSessions(userId string) map[string]string
// ClearStore clears the session store for authorizer tokens
ClearStore() error
DeleteAllUserSessions(userId string) error
// SetState sets the login state (key, value form) in the session store
SetState(key, state string) error
// GetState returns the state from the session store

View File

@@ -2,67 +2,78 @@ package redis
import (
"strconv"
"strings"
"github.com/authorizerdev/authorizer/server/constants"
log "github.com/sirupsen/logrus"
)
var (
// session store prefix
sessionStorePrefix = "authorizer_session:"
// state store prefix
stateStorePrefix = "authorizer_state:"
// env store prefix
envStorePrefix = "authorizer_env"
)
// ClearStore clears the redis store for authorizer related tokens
func (c *provider) ClearStore() error {
err := c.store.Del(c.ctx, sessionStorePrefix+"*").Err()
// SetUserSession sets the user session in redis store.
func (c *provider) SetUserSession(userId, key, token string) error {
err := c.store.HSet(c.ctx, userId, key, token).Err()
if err != nil {
log.Debug("Error clearing redis store: ", err)
log.Debug("Error saving to redis: ", err)
return err
}
return nil
}
// GetUserSessions returns all the user session token from the redis store.
func (c *provider) GetUserSessions(userID string) map[string]string {
data, err := c.store.HGetAll(c.ctx, "*").Result()
// GetAllUserSessions returns all the user session token from the redis store.
func (c *provider) GetAllUserSessions(userID string) (map[string]string, error) {
data, err := c.store.HGetAll(c.ctx, userID).Result()
if err != nil {
log.Debug("error getting token from redis store: ", err)
log.Debug("error getting all user sessions from redis store: ", err)
return nil, err
}
res := map[string]string{}
for k, v := range data {
split := strings.Split(v, "@")
if split[1] == userID {
res[k] = split[0]
}
}
return res
return data, nil
}
// DeleteAllUserSession deletes all the user session from redis
func (c *provider) DeleteAllUserSession(userId string) error {
sessions := c.GetUserSessions(userId)
for k, v := range sessions {
if k == "token" {
err := c.store.Del(c.ctx, v).Err()
if err != nil {
log.Debug("Error deleting redis token: ", err)
return err
}
}
// GetUserSession returns the user session from redis store.
func (c *provider) GetUserSession(userId, key string) (string, error) {
data, err := c.store.HGet(c.ctx, userId, key).Result()
if err != nil {
return "", err
}
return data, nil
}
// DeleteUserSession deletes the user session from redis store.
func (c *provider) DeleteUserSession(userId, key string) error {
if err := c.store.HDel(c.ctx, userId, constants.TokenTypeSessionToken+"_"+key).Err(); err != nil {
log.Debug("Error deleting user session from redis: ", err)
return err
}
if err := c.store.HDel(c.ctx, userId, constants.TokenTypeAccessToken+"_"+key).Err(); err != nil {
log.Debug("Error deleting user session from redis: ", err)
return err
}
if err := c.store.HDel(c.ctx, userId, constants.TokenTypeRefreshToken+"_"+key).Err(); err != nil {
log.Debug("Error deleting user session from redis: ", err)
return err
}
return nil
}
// DeleteAllUserSessions deletes all the user session from redis
func (c *provider) DeleteAllUserSessions(userID string) error {
err := c.store.Del(c.ctx, userID).Err()
if err != nil {
log.Debug("Error deleting all user sessions from redis: ", err)
return err
}
return nil
}
// SetState sets the state in redis store.
func (c *provider) SetState(key, value string) error {
err := c.store.Set(c.ctx, sessionStorePrefix+key, value, 0).Err()
err := c.store.Set(c.ctx, stateStorePrefix+key, value, 0).Err()
if err != nil {
log.Debug("Error saving redis token: ", err)
return err
@@ -73,18 +84,18 @@ func (c *provider) SetState(key, value string) error {
// GetState gets the state from redis store.
func (c *provider) GetState(key string) (string, error) {
var res string
err := c.store.Get(c.ctx, sessionStorePrefix+key).Scan(&res)
data, err := c.store.Get(c.ctx, stateStorePrefix+key).Result()
if err != nil {
log.Debug("error getting token from redis store: ", err)
return "", err
}
return res, err
return data, err
}
// RemoveState removes the state from redis store.
func (c *provider) RemoveState(key string) error {
err := c.store.Del(c.ctx, sessionStorePrefix+key).Err()
err := c.store.Del(c.ctx, stateStorePrefix+key).Err()
if err != nil {
log.Fatalln("Error deleting redis token: ", err)
return err
@@ -137,22 +148,20 @@ func (c *provider) UpdateEnvVariable(key string, value interface{}) error {
// GetStringStoreEnvVariable to get the string env variable from env store
func (c *provider) GetStringStoreEnvVariable(key string) (string, error) {
var res string
err := c.store.HGet(c.ctx, envStorePrefix, key).Scan(&res)
data, err := c.store.HGet(c.ctx, envStorePrefix, key).Result()
if err != nil {
return "", nil
}
return res, nil
return data, nil
}
// GetBoolStoreEnvVariable to get the bool env variable from env store
func (c *provider) GetBoolStoreEnvVariable(key string) (bool, error) {
var res bool
err := c.store.HGet(c.ctx, envStorePrefix, key).Scan(res)
data, err := c.store.HGet(c.ctx, envStorePrefix, key).Result()
if err != nil {
return false, nil
}
return res, nil
return data == "1", nil
}

View File

@@ -7,6 +7,7 @@ import (
"golang.org/x/oauth2"
facebookOAuth2 "golang.org/x/oauth2/facebook"
githubOAuth2 "golang.org/x/oauth2/github"
linkedInOAuth2 "golang.org/x/oauth2/linkedin"
"github.com/authorizerdev/authorizer/server/constants"
"github.com/authorizerdev/authorizer/server/memorystore"
@@ -17,6 +18,8 @@ type OAuthProvider struct {
GoogleConfig *oauth2.Config
GithubConfig *oauth2.Config
FacebookConfig *oauth2.Config
LinkedInConfig *oauth2.Config
AppleConfig *oauth2.Config
}
// OIDCProviders is a struct that contains reference all the OpenID providers
@@ -92,5 +95,44 @@ func InitOAuth() error {
}
}
linkedInClientID, err := memorystore.Provider.GetStringStoreEnvVariable(constants.EnvKeyLinkedInClientID)
if err != nil {
linkedInClientID = ""
}
linkedInClientSecret, err := memorystore.Provider.GetStringStoreEnvVariable(constants.EnvKeyLinkedInClientSecret)
if err != nil {
linkedInClientSecret = ""
}
if linkedInClientID != "" && linkedInClientSecret != "" {
OAuthProviders.LinkedInConfig = &oauth2.Config{
ClientID: linkedInClientID,
ClientSecret: linkedInClientSecret,
RedirectURL: "/oauth_callback/linkedin",
Endpoint: linkedInOAuth2.Endpoint,
Scopes: []string{"r_liteprofile", "r_emailaddress"},
}
}
appleClientID, err := memorystore.Provider.GetStringStoreEnvVariable(constants.EnvKeyAppleClientID)
if err != nil {
appleClientID = ""
}
appleClientSecret, err := memorystore.Provider.GetStringStoreEnvVariable(constants.EnvKeyAppleClientSecret)
if err != nil {
appleClientSecret = ""
}
if appleClientID != "" && appleClientSecret != "" {
OAuthProviders.AppleConfig = &oauth2.Config{
ClientID: appleClientID,
ClientSecret: appleClientSecret,
RedirectURL: "/oauth_callback/apple",
Endpoint: oauth2.Endpoint{
AuthURL: "https://appleid.apple.com/auth/authorize",
TokenURL: "https://appleid.apple.com/auth/token",
},
Scopes: []string{"openid", "name", "email"},
}
}
return nil
}

View File

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

View File

@@ -130,6 +130,18 @@ func EnvResolver(ctx context.Context) (*model.Env, error) {
if val, ok := store[constants.EnvKeyGithubClientSecret]; ok {
res.GithubClientSecret = utils.NewStringRef(val.(string))
}
if val, ok := store[constants.EnvKeyLinkedInClientID]; ok {
res.LinkedinClientID = utils.NewStringRef(val.(string))
}
if val, ok := store[constants.EnvKeyLinkedInClientSecret]; ok {
res.LinkedinClientSecret = utils.NewStringRef(val.(string))
}
if val, ok := store[constants.EnvKeyAppleClientID]; ok {
res.AppleClientID = utils.NewStringRef(val.(string))
}
if val, ok := store[constants.EnvKeyAppleClientSecret]; ok {
res.AppleClientSecret = utils.NewStringRef(val.(string))
}
if val, ok := store[constants.EnvKeyOrganizationName]; ok {
res.OrganizationName = utils.NewStringRef(val.(string))
}
@@ -141,7 +153,14 @@ func EnvResolver(ctx context.Context) (*model.Env, error) {
res.AllowedOrigins = strings.Split(store[constants.EnvKeyAllowedOrigins].(string), ",")
res.Roles = strings.Split(store[constants.EnvKeyRoles].(string), ",")
res.DefaultRoles = strings.Split(store[constants.EnvKeyDefaultRoles].(string), ",")
res.ProtectedRoles = strings.Split(store[constants.EnvKeyProtectedRoles].(string), ",")
// since protected role is optional default split gives array with empty string
protectedRoles := strings.Split(store[constants.EnvKeyProtectedRoles].(string), ",")
res.ProtectedRoles = []string{}
for _, role := range protectedRoles {
if strings.Trim(role, " ") != "" {
res.ProtectedRoles = append(res.ProtectedRoles, strings.Trim(role, " "))
}
}
// bool vars
res.DisableEmailVerification = store[constants.EnvKeyDisableEmailVerification].(bool)

View File

@@ -117,12 +117,12 @@ func LoginResolver(ctx context.Context, params model.LoginInput) (*model.AuthRes
}
cookie.SetSession(gc, authToken.FingerPrintHash)
memorystore.Provider.SetState(authToken.FingerPrintHash, authToken.FingerPrint+"@"+user.ID)
memorystore.Provider.SetState(authToken.AccessToken.Token, authToken.FingerPrint+"@"+user.ID)
memorystore.Provider.SetUserSession(user.ID, constants.TokenTypeSessionToken+"_"+authToken.FingerPrint, authToken.FingerPrintHash)
memorystore.Provider.SetUserSession(user.ID, constants.TokenTypeAccessToken+"_"+authToken.FingerPrint, authToken.AccessToken.Token)
if authToken.RefreshToken != nil {
res.RefreshToken = &authToken.RefreshToken.Token
memorystore.Provider.SetState(authToken.RefreshToken.Token, authToken.FingerPrint+"@"+user.ID)
memorystore.Provider.SetUserSession(user.ID, constants.TokenTypeRefreshToken+"_"+authToken.FingerPrint, authToken.RefreshToken.Token)
}
go db.Provider.AddSession(models.Session{

View File

@@ -2,6 +2,7 @@ package resolvers
import (
"context"
"encoding/json"
log "github.com/sirupsen/logrus"
@@ -9,38 +10,41 @@ import (
"github.com/authorizerdev/authorizer/server/crypto"
"github.com/authorizerdev/authorizer/server/graph/model"
"github.com/authorizerdev/authorizer/server/memorystore"
"github.com/authorizerdev/authorizer/server/token"
"github.com/authorizerdev/authorizer/server/utils"
)
// LogoutResolver is a resolver for logout mutation
func LogoutResolver(ctx context.Context) (*model.Response, error) {
var res *model.Response
gc, err := utils.GinContextFromContext(ctx)
if err != nil {
log.Debug("Failed to get GinContext: ", err)
return res, err
return nil, err
}
// get fingerprint hash
fingerprintHash, err := cookie.GetSession(gc)
if err != nil {
log.Debug("Failed to get fingerprint hash: ", err)
return res, err
return nil, err
}
decryptedFingerPrint, err := crypto.DecryptAES(fingerprintHash)
if err != nil {
log.Debug("Failed to decrypt fingerprint hash: ", err)
return res, err
return nil, err
}
fingerPrint := string(decryptedFingerPrint)
var sessionData token.SessionData
err = json.Unmarshal([]byte(decryptedFingerPrint), &sessionData)
if err != nil {
return nil, err
}
memorystore.Provider.RemoveState(fingerPrint)
memorystore.Provider.DeleteUserSession(sessionData.Subject, sessionData.Nonce)
cookie.DeleteSession(gc)
res = &model.Response{
res := &model.Response{
Message: "Logged out successfully",
}

View File

@@ -41,6 +41,30 @@ func MetaResolver(ctx context.Context) (*model.Meta, error) {
facebookClientSecret = ""
}
linkedClientID, err := memorystore.Provider.GetStringStoreEnvVariable(constants.EnvKeyLinkedInClientID)
if err != nil {
log.Debug("Failed to get LinkedIn Client ID from environment variable", err)
linkedClientID = ""
}
linkedInClientSecret, err := memorystore.Provider.GetStringStoreEnvVariable(constants.EnvKeyLinkedInClientSecret)
if err != nil {
log.Debug("Failed to get LinkedIn Client Secret from environment variable", err)
linkedInClientSecret = ""
}
appleClientID, err := memorystore.Provider.GetStringStoreEnvVariable(constants.EnvKeyAppleClientID)
if err != nil {
log.Debug("Failed to get Apple Client ID from environment variable", err)
appleClientID = ""
}
appleClientSecret, err := memorystore.Provider.GetStringStoreEnvVariable(constants.EnvKeyAppleClientSecret)
if err != nil {
log.Debug("Failed to get Apple Client Secret from environment variable", err)
appleClientSecret = ""
}
githubClientID, err := memorystore.Provider.GetStringStoreEnvVariable(constants.EnvKeyGithubClientID)
if err != nil {
log.Debug("Failed to get Github Client ID from environment variable", err)
@@ -83,6 +107,8 @@ func MetaResolver(ctx context.Context) (*model.Meta, error) {
IsGoogleLoginEnabled: googleClientID != "" && googleClientSecret != "",
IsGithubLoginEnabled: githubClientID != "" && githubClientSecret != "",
IsFacebookLoginEnabled: facebookClientID != "" && facebookClientSecret != "",
IsLinkedinLoginEnabled: linkedClientID != "" && linkedInClientSecret != "",
IsAppleLoginEnabled: appleClientID != "" && appleClientSecret != "",
IsBasicAuthenticationEnabled: !isBasicAuthDisabled,
IsEmailVerificationEnabled: !isEmailVerificationDisabled,
IsMagicLinkLoginEnabled: !isMagicLinkLoginDisabled,

View File

@@ -57,12 +57,17 @@ func ResetPasswordResolver(ctx context.Context, params model.ResetPasswordInput)
// verify if token exists in db
hostname := parsers.GetHost(gc)
claim, err := token.ParseJWTToken(params.Token, hostname, verificationRequest.Nonce, verificationRequest.Email)
claim, err := token.ParseJWTToken(params.Token)
if err != nil {
log.Debug("Failed to parse token: ", err)
return res, fmt.Errorf(`invalid token`)
}
if ok, err := token.ValidateJWTClaims(claim, hostname, verificationRequest.Nonce, verificationRequest.Email); !ok || err != nil {
log.Debug("Failed to validate jwt claims: ", err)
return res, fmt.Errorf(`invalid token`)
}
email := claim["sub"].(string)
log := log.WithFields(log.Fields{
"email": email,

View File

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

View File

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

View File

@@ -100,7 +100,7 @@ func SignupResolver(ctx context.Context, params model.SignUpInput) (*model.AuthR
} else {
roles = strings.Split(rolesString, ",")
}
if !validators.IsValidRoles(roles, params.Roles) {
if !validators.IsValidRoles(params.Roles, roles) {
log.Debug("Invalid roles: ", params.Roles)
return res, fmt.Errorf(`invalid roles`)
} else {
@@ -225,8 +225,6 @@ func SignupResolver(ctx context.Context, params model.SignUpInput) (*model.AuthR
return res, err
}
memorystore.Provider.SetState(authToken.FingerPrintHash, authToken.FingerPrint+"@"+user.ID)
cookie.SetSession(gc, authToken.FingerPrintHash)
go db.Provider.AddSession(models.Session{
UserID: user.ID,
UserAgent: utils.GetUserAgent(gc.Request),
@@ -244,6 +242,15 @@ func SignupResolver(ctx context.Context, params model.SignUpInput) (*model.AuthR
ExpiresIn: &expiresIn,
User: userToReturn,
}
cookie.SetSession(gc, authToken.FingerPrintHash)
memorystore.Provider.SetUserSession(user.ID, constants.TokenTypeSessionToken+"_"+authToken.FingerPrint, authToken.FingerPrintHash)
memorystore.Provider.SetUserSession(user.ID, constants.TokenTypeAccessToken+"_"+authToken.FingerPrint, authToken.AccessToken.Token)
if authToken.RefreshToken != nil {
res.RefreshToken = &authToken.RefreshToken.Token
memorystore.Provider.SetUserSession(user.ID, constants.TokenTypeRefreshToken+"_"+authToken.FingerPrint, authToken.RefreshToken.Token)
}
}
return res, nil

View File

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

View File

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

View File

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

View File

@@ -8,6 +8,7 @@ import (
log "github.com/sirupsen/logrus"
"github.com/authorizerdev/authorizer/server/constants"
"github.com/authorizerdev/authorizer/server/cookie"
"github.com/authorizerdev/authorizer/server/db"
"github.com/authorizerdev/authorizer/server/db/models"
@@ -36,12 +37,17 @@ func VerifyEmailResolver(ctx context.Context, params model.VerifyEmailInput) (*m
// verify if token exists in db
hostname := parsers.GetHost(gc)
claim, err := token.ParseJWTToken(params.Token, hostname, verificationRequest.Nonce, verificationRequest.Email)
claim, err := token.ParseJWTToken(params.Token)
if err != nil {
log.Debug("Failed to parse token: ", err)
return res, fmt.Errorf(`invalid token: %s`, err.Error())
}
if ok, err := token.ValidateJWTClaims(claim, hostname, verificationRequest.Nonce, verificationRequest.Email); !ok || err != nil {
log.Debug("Failed to validate jwt claims: ", err)
return res, fmt.Errorf(`invalid token: %s`, err.Error())
}
email := claim["sub"].(string)
log := log.WithFields(log.Fields{
"email": email,
@@ -75,9 +81,6 @@ func VerifyEmailResolver(ctx context.Context, params model.VerifyEmailInput) (*m
return res, err
}
memorystore.Provider.SetState(authToken.FingerPrintHash, authToken.FingerPrint+"@"+user.ID)
memorystore.Provider.SetState(authToken.AccessToken.Token, authToken.FingerPrint+"@"+user.ID)
cookie.SetSession(gc, authToken.FingerPrintHash)
go db.Provider.AddSession(models.Session{
UserID: user.ID,
UserAgent: utils.GetUserAgent(gc.Request),
@@ -96,5 +99,14 @@ func VerifyEmailResolver(ctx context.Context, params model.VerifyEmailInput) (*m
ExpiresIn: &expiresIn,
User: user.AsAPIUser(),
}
cookie.SetSession(gc, authToken.FingerPrintHash)
memorystore.Provider.SetUserSession(user.ID, constants.TokenTypeSessionToken+"_"+authToken.FingerPrint, authToken.FingerPrintHash)
memorystore.Provider.SetUserSession(user.ID, constants.TokenTypeAccessToken+"_"+authToken.FingerPrint, authToken.AccessToken.Token)
if authToken.RefreshToken != nil {
res.RefreshToken = &authToken.RefreshToken.Token
memorystore.Provider.SetUserSession(user.ID, constants.TokenTypeRefreshToken+"_"+authToken.FingerPrint, authToken.RefreshToken.Token)
}
return res, nil
}

View File

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

View File

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

View File

@@ -2,6 +2,7 @@ package test
import (
"fmt"
"strings"
"testing"
"github.com/authorizerdev/authorizer/server/constants"
@@ -9,6 +10,7 @@ import (
"github.com/authorizerdev/authorizer/server/graph/model"
"github.com/authorizerdev/authorizer/server/memorystore"
"github.com/authorizerdev/authorizer/server/resolvers"
"github.com/authorizerdev/authorizer/server/token"
"github.com/stretchr/testify/assert"
)
@@ -27,15 +29,19 @@ func logoutTests(t *testing.T, s TestSetup) {
Token: verificationRequest.Token,
})
token := *verifyRes.AccessToken
sessions := memorystore.Provider.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;", constants.AppCookieName+"_session", key)
}
}
accessToken := *verifyRes.AccessToken
assert.NotEmpty(t, accessToken)
claims, err := token.ParseJWTToken(accessToken)
assert.NoError(t, err)
assert.NotEmpty(t, claims)
sessionToken, err := memorystore.Provider.GetUserSession(verifyRes.User.ID, constants.TokenTypeSessionToken+"_"+claims["nonce"].(string))
assert.NoError(t, err)
assert.NotEmpty(t, sessionToken)
cookie := fmt.Sprintf("%s=%s;", constants.AppCookieName+"_session", sessionToken)
cookie = strings.TrimSuffix(cookie, ";")
req.Header.Set("Cookie", cookie)
_, err = resolvers.LogoutResolver(ctx)

View File

@@ -10,6 +10,7 @@ import (
"github.com/authorizerdev/authorizer/server/graph/model"
"github.com/authorizerdev/authorizer/server/memorystore"
"github.com/authorizerdev/authorizer/server/resolvers"
"github.com/authorizerdev/authorizer/server/token"
"github.com/stretchr/testify/assert"
)
@@ -33,19 +34,21 @@ func sessionTests(t *testing.T, s TestSetup) {
Token: verificationRequest.Token,
})
sessions := memorystore.Provider.GetUserSessions(verifyRes.User.ID)
cookie := ""
token := *verifyRes.AccessToken
// 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;", constants.AppCookieName+"_session", key)
}
}
accessToken := *verifyRes.AccessToken
assert.NotEmpty(t, accessToken)
claims, err := token.ParseJWTToken(accessToken)
assert.NoError(t, err)
assert.NotEmpty(t, claims)
sessionToken, err := memorystore.Provider.GetUserSession(verifyRes.User.ID, constants.TokenTypeSessionToken+"_"+claims["nonce"].(string))
assert.NoError(t, err)
assert.NotEmpty(t, sessionToken)
cookie := fmt.Sprintf("%s=%s;", constants.AppCookieName+"_session", sessionToken)
cookie = strings.TrimSuffix(cookie, ";")
req.Header.Set("Cookie", cookie)
_, err = resolvers.SessionResolver(ctx, &model.SessionQueryInput{})
assert.Nil(t, err)

View File

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

View File

@@ -198,18 +198,24 @@ func ValidateAccessToken(gc *gin.Context, accessToken string) (map[string]interf
return res, fmt.Errorf(`unauthorized`)
}
savedSession, err := memorystore.Provider.GetState(accessToken)
if savedSession == "" || err != nil {
res, err := ParseJWTToken(accessToken)
if err != nil {
return res, err
}
userID := res["sub"].(string)
nonce := res["nonce"].(string)
token, err := memorystore.Provider.GetUserSession(userID, constants.TokenTypeAccessToken+"_"+nonce)
if nonce == "" || err != nil {
return res, fmt.Errorf(`unauthorized`)
}
savedSessionSplit := strings.Split(savedSession, "@")
nonce := savedSessionSplit[0]
userID := savedSessionSplit[1]
if token != accessToken {
return res, fmt.Errorf(`unauthorized`)
}
hostname := parsers.GetHost(gc)
res, err = ParseJWTToken(accessToken, hostname, nonce, userID)
if err != nil {
if ok, err := ValidateJWTClaims(res, hostname, nonce, userID); !ok || err != nil {
return res, err
}
@@ -228,18 +234,24 @@ func ValidateRefreshToken(gc *gin.Context, refreshToken string) (map[string]inte
return res, fmt.Errorf(`unauthorized`)
}
savedSession, err := memorystore.Provider.GetState(refreshToken)
if savedSession == "" || err != nil {
res, err := ParseJWTToken(refreshToken)
if err != nil {
return res, err
}
userID := res["sub"].(string)
nonce := res["nonce"].(string)
token, err := memorystore.Provider.GetUserSession(userID, constants.TokenTypeRefreshToken+"_"+nonce)
if nonce == "" || err != nil {
return res, fmt.Errorf(`unauthorized`)
}
savedSessionSplit := strings.Split(savedSession, "@")
nonce := savedSessionSplit[0]
userID := savedSessionSplit[1]
if token != refreshToken {
return res, fmt.Errorf(`unauthorized`)
}
hostname := parsers.GetHost(gc)
res, err = ParseJWTToken(refreshToken, hostname, nonce, userID)
if err != nil {
if ok, err := ValidateJWTClaims(res, hostname, nonce, userID); !ok || err != nil {
return res, err
}
@@ -255,15 +267,6 @@ func ValidateBrowserSession(gc *gin.Context, encryptedSession string) (*SessionD
return nil, fmt.Errorf(`unauthorized`)
}
savedSession, err := memorystore.Provider.GetState(encryptedSession)
if savedSession == "" || err != nil {
return nil, fmt.Errorf(`unauthorized`)
}
savedSessionSplit := strings.Split(savedSession, "@")
nonce := savedSessionSplit[0]
userID := savedSessionSplit[1]
decryptedFingerPrint, err := crypto.DecryptAES(encryptedSession)
if err != nil {
return nil, err
@@ -275,23 +278,20 @@ func ValidateBrowserSession(gc *gin.Context, encryptedSession string) (*SessionD
return nil, err
}
if res.Nonce != nonce {
return nil, fmt.Errorf(`unauthorized: invalid nonce`)
token, err := memorystore.Provider.GetUserSession(res.Subject, constants.TokenTypeSessionToken+"_"+res.Nonce)
if token == "" || err != nil {
log.Debug("invalid browser session:", err)
return nil, fmt.Errorf(`unauthorized`)
}
if res.Subject != userID {
return nil, fmt.Errorf(`unauthorized: invalid user id`)
if encryptedSession != token {
return nil, fmt.Errorf(`unauthorized: invalid nonce`)
}
if res.ExpiresAt < time.Now().Unix() {
return nil, fmt.Errorf(`unauthorized: token expired`)
}
// TODO validate scope
// if !reflect.DeepEqual(res.Roles, roles) {
// return res, "", fmt.Errorf(`unauthorized`)
// }
return &res, nil
}

View File

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