Compare commits

...

17 Commits

Author SHA1 Message Date
Lakhan Samani
29c6003ea3 fix: remove test log 2022-07-01 22:04:58 +05:30
Lakhan Samani
ae34fc7c2b fix: update_env resolver 2022-07-01 22:02:34 +05:30
Lakhan Samani
2a5d5d43b0 fix: add namespace to session token keys 2022-06-29 22:24:00 +05:30
Lakhan Samani
e6a4670ba9 fix: add provider to token creation 2022-06-29 09:54:12 +05:30
Lakhan Samani
64d64b4099 feat: add ability to disable strong password 2022-06-18 15:31:57 +05:30
Lakhan Samani
88f9a10f21 Merge pull request #192 from authorizerdev/feat/apple-login
feat: add apple login
2022-06-16 09:48:02 +05:30
Lakhan Samani
4e08d4f8fd fix: update app 2022-06-16 09:47:16 +05:30
Lakhan Samani
1c4dda9299 fix: remove debug logs 2022-06-16 07:34:10 +05:30
Lakhan Samani
ab18fa5832 fix: use raw base64 url decoding 2022-06-15 21:55:41 +05:30
Lakhan Samani
484d0c0882 chore: update app 2022-06-14 16:39:06 +05:30
Lakhan Samani
be59c3615f fix: add comment for scope 2022-06-14 15:47:08 +05:30
Lakhan Samani
db351f7771 fix: remove debug logs 2022-06-14 15:45:06 +05:30
Lakhan Samani
91c29c4092 fix: redirect 2022-06-14 15:43:23 +05:30
Lakhan Samani
415b97535e fix: update scope param 2022-06-14 15:05:56 +05:30
Lakhan Samani
7d1272d815 fix: update scope for apple login 2022-06-14 14:41:31 +05:30
Lakhan Samani
c9ba0b13f8 fix: update scope for apple login 2022-06-14 13:37:05 +05:30
Lakhan Samani
fadd9f6168 fix: update scope for apple login 2022-06-14 13:11:39 +05:30
46 changed files with 600 additions and 210 deletions

30
app/package-lock.json generated
View File

@@ -9,7 +9,7 @@
"version": "1.0.0",
"license": "ISC",
"dependencies": {
"@authorizerdev/authorizer-react": "^0.24.0-beta.1",
"@authorizerdev/authorizer-react": "^0.25.0",
"@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.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==",
"version": "0.14.0",
"resolved": "https://registry.npmjs.org/@authorizerdev/authorizer-js/-/authorizer-js-0.14.0.tgz",
"integrity": "sha512-cpeeFrmG623QPLn+nf+ACHayZYqW8xokIidGikeboBDJtuAAQB50a54/7HwLHriG2FB7WvPuHQ/9LFFX//N1lg==",
"dependencies": {
"node-fetch": "^2.6.1"
},
@@ -37,11 +37,11 @@
}
},
"node_modules/@authorizerdev/authorizer-react": {
"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==",
"version": "0.25.0",
"resolved": "https://registry.npmjs.org/@authorizerdev/authorizer-react/-/authorizer-react-0.25.0.tgz",
"integrity": "sha512-Dt2rZf+cGCVb8dqcJ/9l8Trx+QeXnTdfhER6r/cq0iOnFC9MqWzQPB3RgrlUoMLHtZvKNDXIk1HvfD5hSX9lhw==",
"dependencies": {
"@authorizerdev/authorizer-js": "^0.13.0-beta.2",
"@authorizerdev/authorizer-js": "^0.14.0",
"final-form": "^4.20.2",
"react-final-form": "^6.5.3",
"styled-components": "^5.3.0"
@@ -852,19 +852,19 @@
},
"dependencies": {
"@authorizerdev/authorizer-js": {
"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==",
"version": "0.14.0",
"resolved": "https://registry.npmjs.org/@authorizerdev/authorizer-js/-/authorizer-js-0.14.0.tgz",
"integrity": "sha512-cpeeFrmG623QPLn+nf+ACHayZYqW8xokIidGikeboBDJtuAAQB50a54/7HwLHriG2FB7WvPuHQ/9LFFX//N1lg==",
"requires": {
"node-fetch": "^2.6.1"
}
},
"@authorizerdev/authorizer-react": {
"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==",
"version": "0.25.0",
"resolved": "https://registry.npmjs.org/@authorizerdev/authorizer-react/-/authorizer-react-0.25.0.tgz",
"integrity": "sha512-Dt2rZf+cGCVb8dqcJ/9l8Trx+QeXnTdfhER6r/cq0iOnFC9MqWzQPB3RgrlUoMLHtZvKNDXIk1HvfD5hSX9lhw==",
"requires": {
"@authorizerdev/authorizer-js": "^0.13.0-beta.2",
"@authorizerdev/authorizer-js": "^0.14.0",
"final-form": "^4.20.2",
"react-final-form": "^6.5.3",
"styled-components": "^5.3.0"

View File

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

View File

@@ -2529,7 +2529,8 @@
"@chakra-ui/css-reset": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/@chakra-ui/css-reset/-/css-reset-1.1.1.tgz",
"integrity": "sha512-+KNNHL4OWqeKia5SL858K3Qbd8WxMij9mWIilBzLD4j2KFrl/+aWFw8syMKth3NmgIibrjsljo+PU3fy2o50dg=="
"integrity": "sha512-+KNNHL4OWqeKia5SL858K3Qbd8WxMij9mWIilBzLD4j2KFrl/+aWFw8syMKth3NmgIibrjsljo+PU3fy2o50dg==",
"requires": {}
},
"@chakra-ui/descendant": {
"version": "2.1.1",
@@ -3133,7 +3134,8 @@
"@graphql-typed-document-node/core": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/@graphql-typed-document-node/core/-/core-3.1.1.tgz",
"integrity": "sha512-NQ17ii0rK1b34VZonlmT2QMJFI70m0TRwbknO/ihlbatXyaktDhN/98vBiUU6kNBPljqGqyIrl2T4nY2RpFANg=="
"integrity": "sha512-NQ17ii0rK1b34VZonlmT2QMJFI70m0TRwbknO/ihlbatXyaktDhN/98vBiUU6kNBPljqGqyIrl2T4nY2RpFANg==",
"requires": {}
},
"@popperjs/core": {
"version": "2.11.0",
@@ -3843,7 +3845,8 @@
"react-icons": {
"version": "4.3.1",
"resolved": "https://registry.npmjs.org/react-icons/-/react-icons-4.3.1.tgz",
"integrity": "sha512-cB10MXLTs3gVuXimblAdI71jrJx8njrJZmNMEMC+sQu5B/BIOmlsAjskdqpn81y8UBVEGuHODd7/ci5DvoSzTQ=="
"integrity": "sha512-cB10MXLTs3gVuXimblAdI71jrJx8njrJZmNMEMC+sQu5B/BIOmlsAjskdqpn81y8UBVEGuHODd7/ci5DvoSzTQ==",
"requires": {}
},
"react-is": {
"version": "16.13.1",
@@ -4029,7 +4032,8 @@
"use-callback-ref": {
"version": "1.2.5",
"resolved": "https://registry.npmjs.org/use-callback-ref/-/use-callback-ref-1.2.5.tgz",
"integrity": "sha512-gN3vgMISAgacF7sqsLPByqoePooY3n2emTH59Ur5d/M8eg4WTWu1xp8i8DHjohftIyEx0S08RiYxbffr4j8Peg=="
"integrity": "sha512-gN3vgMISAgacF7sqsLPByqoePooY3n2emTH59Ur5d/M8eg4WTWu1xp8i8DHjohftIyEx0S08RiYxbffr4j8Peg==",
"requires": {}
},
"use-sidecar": {
"version": "1.0.5",

View File

@@ -71,6 +71,18 @@ const Features = ({ variables, setVariables }: any) => {
/>
</Flex>
</Flex>
<Flex>
<Flex w="100%" justifyContent="start" alignItems="center">
<Text fontSize="sm">Disable Strong Password:</Text>
</Flex>
<Flex justifyContent="start" mb={3}>
<InputField
variables={variables}
setVariables={setVariables}
inputType={SwitchInputType.DISABLE_STRONG_PASSWORD}
/>
</Flex>
</Flex>
</Stack>
</div>
);

View File

@@ -67,6 +67,7 @@ export const SwitchInputType = {
DISABLE_BASIC_AUTHENTICATION: 'DISABLE_BASIC_AUTHENTICATION',
DISABLE_SIGN_UP: 'DISABLE_SIGN_UP',
DISABLE_REDIS_FOR_ENV: 'DISABLE_REDIS_FOR_ENV',
DISABLE_STRONG_PASSWORD: 'DISABLE_STRONG_PASSWORD',
};
export const DateInputType = {
@@ -131,6 +132,7 @@ export interface envVarTypes {
DISABLE_EMAIL_VERIFICATION: boolean;
DISABLE_BASIC_AUTHENTICATION: boolean;
DISABLE_SIGN_UP: boolean;
DISABLE_STRONG_PASSWORD: boolean;
OLD_ADMIN_SECRET: string;
DATABASE_NAME: string;
DATABASE_TYPE: string;

View File

@@ -53,6 +53,7 @@ export const EnvVariablesQuery = `
DISABLE_EMAIL_VERIFICATION,
DISABLE_BASIC_AUTHENTICATION,
DISABLE_SIGN_UP,
DISABLE_STRONG_PASSWORD,
DISABLE_REDIS_FOR_ENV,
CUSTOM_ACCESS_TOKEN_SCRIPT,
DATABASE_NAME,

View File

@@ -74,6 +74,7 @@ const Environment = () => {
DISABLE_EMAIL_VERIFICATION: false,
DISABLE_BASIC_AUTHENTICATION: false,
DISABLE_SIGN_UP: false,
DISABLE_STRONG_PASSWORD: false,
OLD_ADMIN_SECRET: '',
DATABASE_NAME: '',
DATABASE_TYPE: '',

View File

@@ -0,0 +1,18 @@
package constants
const (
// AuthRecipeMethodBasicAuth is the basic_auth auth method
AuthRecipeMethodBasicAuth = "basic_auth"
// AuthRecipeMethodMagicLinkLogin is the magic_link_login auth method
AuthRecipeMethodMagicLinkLogin = "magic_link_login"
// AuthRecipeMethodGoogle is the google auth method
AuthRecipeMethodGoogle = "google"
// AuthRecipeMethodGithub is the github auth method
AuthRecipeMethodGithub = "github"
// AuthRecipeMethodFacebook is the facebook auth method
AuthRecipeMethodFacebook = "facebook"
// AuthRecipeMethodLinkedin is the linkedin auth method
AuthRecipeMethodLinkedIn = "linkedin"
// AuthRecipeMethodApple is the apple auth method
AuthRecipeMethodApple = "apple"
)

View File

@@ -115,6 +115,8 @@ const (
EnvKeyDisableSignUp = "DISABLE_SIGN_UP"
// EnvKeyDisableRedisForEnv key for env variable DISABLE_REDIS_FOR_ENV
EnvKeyDisableRedisForEnv = "DISABLE_REDIS_FOR_ENV"
// EnvKeyDisableStrongPassword key for env variable DISABLE_STRONG_PASSWORD
EnvKeyDisableStrongPassword = "DISABLE_STRONG_PASSWORD"
// Slice variables
// EnvKeyRoles key for env variable ROLES

View File

@@ -1,18 +0,0 @@
package constants
const (
// SignupMethodBasicAuth is the basic_auth signup method
SignupMethodBasicAuth = "basic_auth"
// SignupMethodMagicLinkLogin is the magic_link_login signup method
SignupMethodMagicLinkLogin = "magic_link_login"
// SignupMethodGoogle is the google signup method
SignupMethodGoogle = "google"
// SignupMethodGithub is the github signup method
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"
)

14
server/env/env.go vendored
View File

@@ -83,6 +83,7 @@ func InitAllEnv() error {
osDisableLoginPage := os.Getenv(constants.EnvKeyDisableLoginPage)
osDisableSignUp := os.Getenv(constants.EnvKeyDisableSignUp)
osDisableRedisForEnv := os.Getenv(constants.EnvKeyDisableRedisForEnv)
osDisableStrongPassword := os.Getenv(constants.EnvKeyDisableStrongPassword)
// os slice vars
osAllowedOrigins := os.Getenv(constants.EnvKeyAllowedOrigins)
@@ -476,6 +477,19 @@ func InitAllEnv() error {
}
}
if _, ok := envData[constants.EnvKeyDisableStrongPassword]; !ok {
envData[constants.EnvKeyDisableStrongPassword] = osDisableStrongPassword == "true"
}
if osDisableStrongPassword != "" {
boolValue, err := strconv.ParseBool(osDisableStrongPassword)
if err != nil {
return err
}
if boolValue != envData[constants.EnvKeyDisableStrongPassword].(bool) {
envData[constants.EnvKeyDisableStrongPassword] = boolValue
}
}
// no need to add nil check as its already done above
if envData[constants.EnvKeySmtpHost] == "" || envData[constants.EnvKeySmtpUsername] == "" || envData[constants.EnvKeySmtpPassword] == "" || envData[constants.EnvKeySenderEmail] == "" && envData[constants.EnvKeySmtpPort] == "" {
envData[constants.EnvKeyDisableEmailVerification] = true

View File

@@ -198,7 +198,7 @@ func PersistEnv() error {
envValue := strings.TrimSpace(os.Getenv(key))
if envValue != "" {
switch key {
case constants.EnvKeyIsProd, constants.EnvKeyDisableBasicAuthentication, constants.EnvKeyDisableEmailVerification, constants.EnvKeyDisableLoginPage, constants.EnvKeyDisableMagicLinkLogin, constants.EnvKeyDisableSignUp, constants.EnvKeyDisableRedisForEnv:
case constants.EnvKeyIsProd, constants.EnvKeyDisableBasicAuthentication, constants.EnvKeyDisableEmailVerification, constants.EnvKeyDisableLoginPage, constants.EnvKeyDisableMagicLinkLogin, constants.EnvKeyDisableSignUp, constants.EnvKeyDisableRedisForEnv, constants.EnvKeyDisableStrongPassword:
if envValueBool, err := strconv.ParseBool(envValue); err == nil {
if value.(bool) != envValueBool {
storeData[key] = envValueBool

View File

@@ -76,6 +76,7 @@ type ComplexityRoot struct {
DisableMagicLinkLogin func(childComplexity int) int
DisableRedisForEnv func(childComplexity int) int
DisableSignUp func(childComplexity int) int
DisableStrongPassword func(childComplexity int) int
FacebookClientID func(childComplexity int) int
FacebookClientSecret func(childComplexity int) int
GithubClientID func(childComplexity int) int
@@ -124,6 +125,7 @@ type ComplexityRoot struct {
IsLinkedinLoginEnabled func(childComplexity int) int
IsMagicLinkLoginEnabled func(childComplexity int) int
IsSignUpEnabled func(childComplexity int) int
IsStrongPasswordEnabled func(childComplexity int) int
Version func(childComplexity int) int
}
@@ -471,6 +473,13 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in
return e.complexity.Env.DisableSignUp(childComplexity), true
case "Env.DISABLE_STRONG_PASSWORD":
if e.complexity.Env.DisableStrongPassword == nil {
break
}
return e.complexity.Env.DisableStrongPassword(childComplexity), true
case "Env.FACEBOOK_CLIENT_ID":
if e.complexity.Env.FacebookClientID == nil {
break
@@ -744,6 +753,13 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in
return e.complexity.Meta.IsSignUpEnabled(childComplexity), true
case "Meta.is_strong_password_enabled":
if e.complexity.Meta.IsStrongPasswordEnabled == nil {
break
}
return e.complexity.Meta.IsStrongPasswordEnabled(childComplexity), true
case "Meta.version":
if e.complexity.Meta.Version == nil {
break
@@ -1406,6 +1422,7 @@ type Meta {
is_basic_authentication_enabled: Boolean!
is_magic_link_login_enabled: Boolean!
is_sign_up_enabled: Boolean!
is_strong_password_enabled: Boolean!
}
type User {
@@ -1502,6 +1519,7 @@ type Env {
DISABLE_LOGIN_PAGE: Boolean!
DISABLE_SIGN_UP: Boolean!
DISABLE_REDIS_FOR_ENV: Boolean!
DISABLE_STRONG_PASSWORD: Boolean!
ROLES: [String!]
PROTECTED_ROLES: [String!]
DEFAULT_ROLES: [String!]
@@ -1553,6 +1571,7 @@ input UpdateEnvInput {
DISABLE_LOGIN_PAGE: Boolean
DISABLE_SIGN_UP: Boolean
DISABLE_REDIS_FOR_ENV: Boolean
DISABLE_STRONG_PASSWORD: Boolean
ROLES: [String!]
PROTECTED_ROLES: [String!]
DEFAULT_ROLES: [String!]
@@ -3340,6 +3359,41 @@ func (ec *executionContext) _Env_DISABLE_REDIS_FOR_ENV(ctx context.Context, fiel
return ec.marshalNBoolean2bool(ctx, field.Selections, res)
}
func (ec *executionContext) _Env_DISABLE_STRONG_PASSWORD(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.DisableStrongPassword, 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) _Env_ROLES(ctx context.Context, field graphql.CollectedField, obj *model.Env) (ret graphql.Marshaler) {
defer func() {
if r := recover(); r != nil {
@@ -4403,6 +4457,41 @@ func (ec *executionContext) _Meta_is_sign_up_enabled(ctx context.Context, field
return ec.marshalNBoolean2bool(ctx, field.Selections, res)
}
func (ec *executionContext) _Meta_is_strong_password_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.IsStrongPasswordEnabled, 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) _Mutation_signup(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) {
defer func() {
if r := recover(); r != nil {
@@ -8739,6 +8828,14 @@ func (ec *executionContext) unmarshalInputUpdateEnvInput(ctx context.Context, ob
if err != nil {
return it, err
}
case "DISABLE_STRONG_PASSWORD":
var err error
ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("DISABLE_STRONG_PASSWORD"))
it.DisableStrongPassword, err = ec.unmarshalOBoolean2ᚖbool(ctx, v)
if err != nil {
return it, err
}
case "ROLES":
var err error
@@ -9299,6 +9396,11 @@ func (ec *executionContext) _Env(ctx context.Context, sel ast.SelectionSet, obj
if out.Values[i] == graphql.Null {
invalids++
}
case "DISABLE_STRONG_PASSWORD":
out.Values[i] = ec._Env_DISABLE_STRONG_PASSWORD(ctx, field, obj)
if out.Values[i] == graphql.Null {
invalids++
}
case "ROLES":
out.Values[i] = ec._Env_ROLES(ctx, field, obj)
case "PROTECTED_ROLES":
@@ -9468,6 +9570,11 @@ func (ec *executionContext) _Meta(ctx context.Context, sel ast.SelectionSet, obj
if out.Values[i] == graphql.Null {
invalids++
}
case "is_strong_password_enabled":
out.Values[i] = ec._Meta_is_strong_password_enabled(ctx, field, obj)
if out.Values[i] == graphql.Null {
invalids++
}
default:
panic("unknown field " + strconv.Quote(field.Name))
}

View File

@@ -55,6 +55,7 @@ type Env struct {
DisableLoginPage bool `json:"DISABLE_LOGIN_PAGE"`
DisableSignUp bool `json:"DISABLE_SIGN_UP"`
DisableRedisForEnv bool `json:"DISABLE_REDIS_FOR_ENV"`
DisableStrongPassword bool `json:"DISABLE_STRONG_PASSWORD"`
Roles []string `json:"ROLES"`
ProtectedRoles []string `json:"PROTECTED_ROLES"`
DefaultRoles []string `json:"DEFAULT_ROLES"`
@@ -126,6 +127,7 @@ type Meta struct {
IsBasicAuthenticationEnabled bool `json:"is_basic_authentication_enabled"`
IsMagicLinkLoginEnabled bool `json:"is_magic_link_login_enabled"`
IsSignUpEnabled bool `json:"is_sign_up_enabled"`
IsStrongPasswordEnabled bool `json:"is_strong_password_enabled"`
}
type OAuthRevokeInput struct {
@@ -212,6 +214,7 @@ type UpdateEnvInput struct {
DisableLoginPage *bool `json:"DISABLE_LOGIN_PAGE"`
DisableSignUp *bool `json:"DISABLE_SIGN_UP"`
DisableRedisForEnv *bool `json:"DISABLE_REDIS_FOR_ENV"`
DisableStrongPassword *bool `json:"DISABLE_STRONG_PASSWORD"`
Roles []string `json:"ROLES"`
ProtectedRoles []string `json:"PROTECTED_ROLES"`
DefaultRoles []string `json:"DEFAULT_ROLES"`

View File

@@ -24,6 +24,7 @@ type Meta {
is_basic_authentication_enabled: Boolean!
is_magic_link_login_enabled: Boolean!
is_sign_up_enabled: Boolean!
is_strong_password_enabled: Boolean!
}
type User {
@@ -120,6 +121,7 @@ type Env {
DISABLE_LOGIN_PAGE: Boolean!
DISABLE_SIGN_UP: Boolean!
DISABLE_REDIS_FOR_ENV: Boolean!
DISABLE_STRONG_PASSWORD: Boolean!
ROLES: [String!]
PROTECTED_ROLES: [String!]
DEFAULT_ROLES: [String!]
@@ -171,6 +173,7 @@ input UpdateEnvInput {
DISABLE_LOGIN_PAGE: Boolean
DISABLE_SIGN_UP: Boolean
DISABLE_REDIS_FOR_ENV: Boolean
DISABLE_STRONG_PASSWORD: Boolean
ROLES: [String!]
PROTECTED_ROLES: [String!]
DEFAULT_ROLES: [String!]

View File

@@ -218,13 +218,18 @@ func AuthorizeHandler() gin.HandlerFunc {
return
}
sessionKey := user.ID
if claims.LoginMethod != "" {
sessionKey = claims.LoginMethod + ":" + user.ID
}
// if user is logged in
// based on the response type, generate the response
// based on the response type code, generate the response
if isResponseTypeCode {
// rollover the session for security
go memorystore.Provider.DeleteUserSession(user.ID, claims.Nonce)
go memorystore.Provider.DeleteUserSession(sessionKey, claims.Nonce)
nonce := uuid.New().String()
newSessionTokenData, newSessionToken, err := token.CreateSessionToken(user, nonce, claims.Roles, scope)
newSessionTokenData, newSessionToken, err := token.CreateSessionToken(user, nonce, claims.Roles, scope, claims.LoginMethod)
if err != nil {
if isQuery {
gc.Redirect(http.StatusFound, loginURL)
@@ -262,7 +267,7 @@ func AuthorizeHandler() gin.HandlerFunc {
if isResponseTypeToken {
// rollover the session for security
authToken, err := token.CreateAuthToken(gc, user, claims.Roles, scope)
authToken, err := token.CreateAuthToken(gc, user, claims.Roles, scope, claims.LoginMethod)
if err != nil {
if isQuery {
gc.Redirect(http.StatusFound, loginURL)
@@ -280,9 +285,10 @@ func AuthorizeHandler() gin.HandlerFunc {
}
return
}
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)
go memorystore.Provider.DeleteUserSession(sessionKey, claims.Nonce)
memorystore.Provider.SetUserSession(sessionKey, constants.TokenTypeSessionToken+"_"+authToken.FingerPrint, authToken.FingerPrintHash)
memorystore.Provider.SetUserSession(sessionKey, constants.TokenTypeAccessToken+"_"+authToken.FingerPrint, authToken.AccessToken.Token)
cookie.SetSession(gc, authToken.FingerPrintHash)
expiresIn := authToken.AccessToken.ExpiresAt - time.Now().Unix()
@@ -305,7 +311,7 @@ func AuthorizeHandler() gin.HandlerFunc {
if authToken.RefreshToken != nil {
res["refresh_token"] = authToken.RefreshToken.Token
params += "&refresh_token=" + authToken.RefreshToken.Token
memorystore.Provider.SetUserSession(user.ID, constants.TokenTypeRefreshToken+"_"+authToken.FingerPrint, authToken.RefreshToken.Token)
memorystore.Provider.SetUserSession(sessionKey, constants.TokenTypeRefreshToken+"_"+authToken.FingerPrint, authToken.RefreshToken.Token)
}
if isQuery {

View File

@@ -2,6 +2,7 @@ package handlers
import (
"context"
"encoding/base64"
"encoding/json"
"fmt"
"io/ioutil"
@@ -17,7 +18,6 @@ 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"
@@ -57,15 +57,15 @@ func OAuthCallbackHandler() gin.HandlerFunc {
user := models.User{}
code := c.Request.FormValue("code")
switch provider {
case constants.SignupMethodGoogle:
case constants.AuthRecipeMethodGoogle:
user, err = processGoogleUserInfo(code)
case constants.SignupMethodGithub:
case constants.AuthRecipeMethodGithub:
user, err = processGithubUserInfo(code)
case constants.SignupMethodFacebook:
case constants.AuthRecipeMethodFacebook:
user, err = processFacebookUserInfo(code)
case constants.SignupMethodLinkedIn:
case constants.AuthRecipeMethodLinkedIn:
user, err = processLinkedInUserInfo(code)
case constants.SignupMethodApple:
case constants.AuthRecipeMethodApple:
user, err = processAppleUserInfo(code)
default:
log.Info("Invalid oauth provider")
@@ -192,7 +192,7 @@ func OAuthCallbackHandler() gin.HandlerFunc {
}
}
authToken, err := token.CreateAuthToken(c, user, inputRoles, scopes)
authToken, err := token.CreateAuthToken(c, user, inputRoles, scopes, provider)
if err != nil {
log.Debug("Failed to create auth token: ", err)
c.JSON(500, gin.H{"error": err.Error()})
@@ -205,13 +205,14 @@ 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
sessionKey := provider + ":" + user.ID
cookie.SetSession(c, 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)
memorystore.Provider.SetUserSession(sessionKey, constants.TokenTypeSessionToken+"_"+authToken.FingerPrint, authToken.FingerPrintHash)
memorystore.Provider.SetUserSession(sessionKey, constants.TokenTypeAccessToken+"_"+authToken.FingerPrint, authToken.AccessToken.Token)
if authToken.RefreshToken != nil {
params = params + `&refresh_token=` + authToken.RefreshToken.Token
memorystore.Provider.SetUserSession(user.ID, constants.TokenTypeRefreshToken+"_"+authToken.FingerPrint, authToken.RefreshToken.Token)
memorystore.Provider.SetUserSession(sessionKey, constants.TokenTypeRefreshToken+"_"+authToken.FingerPrint, authToken.RefreshToken.Token)
}
go db.Provider.AddSession(models.Session{
@@ -225,7 +226,7 @@ func OAuthCallbackHandler() gin.HandlerFunc {
redirectURL = redirectURL + "?" + strings.TrimPrefix(params, "&")
}
c.Redirect(http.StatusTemporaryRedirect, redirectURL)
c.Redirect(http.StatusFound, redirectURL)
}
}
@@ -462,8 +463,6 @@ func processAppleUserInfo(code string) (models.User, error) {
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 {
@@ -471,36 +470,39 @@ func processAppleUserInfo(code string) (models.User, error) {
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)
decodedClaimsData, err := base64.RawURLEncoding.DecodeString(claimsData)
if err != nil {
log.Debug("Failed to decrypt claims data: ", err)
log.Debugf("Failed to decrypt claims %s: %s", claimsData, err.Error())
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)
err = json.Unmarshal(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")
log.Debug("Failed to extract email from claims.")
return user, fmt.Errorf("unable to extract email, please check the scopes enabled for your app. It needs `email`, `name` scopes")
} else {
user.Email = val.(string)
}
if val, ok := claims["name"]; ok {
givenName := val.(string)
user.GivenName = &givenName
nameData := val.(map[string]interface{})
if nameVal, ok := nameData["firstName"]; ok {
givenName := nameVal.(string)
user.GivenName = &givenName
}
if nameVal, ok := nameData["lastName"]; ok {
familyName := nameVal.(string)
user.FamilyName = &familyName
}
}
return user, err

View File

@@ -100,13 +100,13 @@ func OAuthLoginHandler() gin.HandlerFunc {
provider := c.Param("oauth_provider")
isProviderConfigured := true
switch provider {
case constants.SignupMethodGoogle:
case constants.AuthRecipeMethodGoogle:
if oauth.OAuthProviders.GoogleConfig == nil {
log.Debug("Google OAuth provider is not configured")
isProviderConfigured = false
break
}
err := memorystore.Provider.SetState(oauthStateString, constants.SignupMethodGoogle)
err := memorystore.Provider.SetState(oauthStateString, constants.AuthRecipeMethodGoogle)
if err != nil {
log.Debug("Error setting state: ", err)
c.JSON(500, gin.H{
@@ -115,16 +115,16 @@ func OAuthLoginHandler() gin.HandlerFunc {
return
}
// during the init of OAuthProvider authorizer url might be empty
oauth.OAuthProviders.GoogleConfig.RedirectURL = hostname + "/oauth_callback/" + constants.SignupMethodGoogle
oauth.OAuthProviders.GoogleConfig.RedirectURL = hostname + "/oauth_callback/" + constants.AuthRecipeMethodGoogle
url := oauth.OAuthProviders.GoogleConfig.AuthCodeURL(oauthStateString)
c.Redirect(http.StatusTemporaryRedirect, url)
case constants.SignupMethodGithub:
case constants.AuthRecipeMethodGithub:
if oauth.OAuthProviders.GithubConfig == nil {
log.Debug("Github OAuth provider is not configured")
isProviderConfigured = false
break
}
err := memorystore.Provider.SetState(oauthStateString, constants.SignupMethodGithub)
err := memorystore.Provider.SetState(oauthStateString, constants.AuthRecipeMethodGithub)
if err != nil {
log.Debug("Error setting state: ", err)
c.JSON(500, gin.H{
@@ -132,16 +132,16 @@ func OAuthLoginHandler() gin.HandlerFunc {
})
return
}
oauth.OAuthProviders.GithubConfig.RedirectURL = hostname + "/oauth_callback/" + constants.SignupMethodGithub
oauth.OAuthProviders.GithubConfig.RedirectURL = hostname + "/oauth_callback/" + constants.AuthRecipeMethodGithub
url := oauth.OAuthProviders.GithubConfig.AuthCodeURL(oauthStateString)
c.Redirect(http.StatusTemporaryRedirect, url)
case constants.SignupMethodFacebook:
case constants.AuthRecipeMethodFacebook:
if oauth.OAuthProviders.FacebookConfig == nil {
log.Debug("Facebook OAuth provider is not configured")
isProviderConfigured = false
break
}
err := memorystore.Provider.SetState(oauthStateString, constants.SignupMethodFacebook)
err := memorystore.Provider.SetState(oauthStateString, constants.AuthRecipeMethodFacebook)
if err != nil {
log.Debug("Error setting state: ", err)
c.JSON(500, gin.H{
@@ -149,16 +149,16 @@ func OAuthLoginHandler() gin.HandlerFunc {
})
return
}
oauth.OAuthProviders.FacebookConfig.RedirectURL = hostname + "/oauth_callback/" + constants.SignupMethodFacebook
oauth.OAuthProviders.FacebookConfig.RedirectURL = hostname + "/oauth_callback/" + constants.AuthRecipeMethodFacebook
url := oauth.OAuthProviders.FacebookConfig.AuthCodeURL(oauthStateString)
c.Redirect(http.StatusTemporaryRedirect, url)
case constants.SignupMethodLinkedIn:
case constants.AuthRecipeMethodLinkedIn:
if oauth.OAuthProviders.LinkedInConfig == nil {
log.Debug("Linkedin OAuth provider is not configured")
isProviderConfigured = false
break
}
err := memorystore.Provider.SetState(oauthStateString, constants.SignupMethodLinkedIn)
err := memorystore.Provider.SetState(oauthStateString, constants.AuthRecipeMethodLinkedIn)
if err != nil {
log.Debug("Error setting state: ", err)
c.JSON(500, gin.H{
@@ -166,16 +166,16 @@ func OAuthLoginHandler() gin.HandlerFunc {
})
return
}
oauth.OAuthProviders.LinkedInConfig.RedirectURL = hostname + "/oauth_callback/" + constants.SignupMethodLinkedIn
oauth.OAuthProviders.LinkedInConfig.RedirectURL = hostname + "/oauth_callback/" + constants.AuthRecipeMethodLinkedIn
url := oauth.OAuthProviders.LinkedInConfig.AuthCodeURL(oauthStateString)
c.Redirect(http.StatusTemporaryRedirect, url)
case constants.SignupMethodApple:
case constants.AuthRecipeMethodApple:
if oauth.OAuthProviders.AppleConfig == nil {
log.Debug("Apple OAuth provider is not configured")
isProviderConfigured = false
break
}
err := memorystore.Provider.SetState(oauthStateString, constants.SignupMethodApple)
err := memorystore.Provider.SetState(oauthStateString, constants.AuthRecipeMethodApple)
if err != nil {
log.Debug("Error setting state: ", err)
c.JSON(500, gin.H{
@@ -183,8 +183,10 @@ func OAuthLoginHandler() gin.HandlerFunc {
})
return
}
oauth.OAuthProviders.AppleConfig.RedirectURL = hostname + "/oauth_callback/" + constants.SignupMethodApple
url := oauth.OAuthProviders.AppleConfig.AuthCodeURL(oauthStateString, oauth2.SetAuthURLParam("response_mode", "form_post"))
oauth.OAuthProviders.AppleConfig.RedirectURL = hostname + "/oauth_callback/" + constants.AuthRecipeMethodApple
// there is scope encoding issue with oauth2 and how apple expects, hence added scope manually
// check: https://github.com/golang/oauth2/issues/449
url := oauth.OAuthProviders.AppleConfig.AuthCodeURL(oauthStateString, oauth2.SetAuthURLParam("response_mode", "form_post")) + "&scope=name email"
c.Redirect(http.StatusTemporaryRedirect, url)
default:
log.Debug("Invalid oauth provider: ", provider)

View File

@@ -56,7 +56,14 @@ func RevokeHandler() gin.HandlerFunc {
return
}
memorystore.Provider.DeleteUserSession(claims["sub"].(string), claims["nonce"].(string))
userID := claims["sub"].(string)
loginMethod := claims["login_method"]
sessionToken := userID
if loginMethod != nil && loginMethod != "" {
sessionToken = loginMethod.(string) + ":" + userID
}
memorystore.Provider.DeleteUserSession(sessionToken, claims["nonce"].(string))
gc.JSON(http.StatusOK, gin.H{
"message": "Token revoked successfully",

View File

@@ -72,6 +72,9 @@ func TokenHandler() gin.HandlerFunc {
var userID string
var roles, scope []string
loginMethod := ""
sessionKey := ""
if isAuthorizationCodeGrant {
if codeVerifier == "" {
@@ -134,8 +137,13 @@ func TokenHandler() gin.HandlerFunc {
userID = claims.Subject
roles = claims.Roles
scope = claims.Scope
loginMethod = claims.LoginMethod
// rollover the session for security
go memorystore.Provider.DeleteUserSession(userID, claims.Nonce)
sessionKey = userID
if loginMethod != "" {
sessionKey = loginMethod + ":" + userID
}
go memorystore.Provider.DeleteUserSession(sessionKey, claims.Nonce)
} else {
// validate refresh token
if refreshToken == "" {
@@ -155,6 +163,7 @@ func TokenHandler() gin.HandlerFunc {
})
}
userID = claims["sub"].(string)
loginMethod := claims["login_method"]
rolesInterface := claims["roles"].([]interface{})
scopeInterface := claims["scope"].([]interface{})
for _, v := range rolesInterface {
@@ -163,8 +172,22 @@ func TokenHandler() gin.HandlerFunc {
for _, v := range scopeInterface {
scope = append(scope, v.(string))
}
sessionKey = userID
if loginMethod != nil && loginMethod != "" {
sessionKey = loginMethod.(string) + ":" + sessionKey
}
// remove older refresh token and rotate it for security
go memorystore.Provider.DeleteUserSession(userID, claims["nonce"].(string))
go memorystore.Provider.DeleteUserSession(sessionKey, claims["nonce"].(string))
}
if sessionKey == "" {
log.Debug("Error getting sessionKey: ", sessionKey, loginMethod)
gc.JSON(http.StatusUnauthorized, gin.H{
"error": "unauthorized",
"error_description": "User not found",
})
return
}
user, err := db.Provider.GetUserByID(userID)
@@ -177,7 +200,7 @@ func TokenHandler() gin.HandlerFunc {
return
}
authToken, err := token.CreateAuthToken(gc, user, roles, scope)
authToken, err := token.CreateAuthToken(gc, user, roles, scope, loginMethod)
if err != nil {
log.Debug("Error creating auth token: ", err)
gc.JSON(http.StatusUnauthorized, gin.H{
@@ -186,8 +209,8 @@ func TokenHandler() gin.HandlerFunc {
})
return
}
memorystore.Provider.SetUserSession(user.ID, constants.TokenTypeSessionToken+"_"+authToken.FingerPrint, authToken.FingerPrintHash)
memorystore.Provider.SetUserSession(user.ID, constants.TokenTypeAccessToken+"_"+authToken.FingerPrint, authToken.AccessToken.Token)
memorystore.Provider.SetUserSession(sessionKey, constants.TokenTypeSessionToken+"_"+authToken.FingerPrint, authToken.FingerPrintHash)
memorystore.Provider.SetUserSession(sessionKey, constants.TokenTypeAccessToken+"_"+authToken.FingerPrint, authToken.AccessToken.Token)
cookie.SetSession(gc, authToken.FingerPrintHash)
expiresIn := authToken.AccessToken.ExpiresAt - time.Now().Unix()
@@ -205,7 +228,7 @@ func TokenHandler() gin.HandlerFunc {
if authToken.RefreshToken != nil {
res["refresh_token"] = authToken.RefreshToken.Token
memorystore.Provider.SetUserSession(user.ID, constants.TokenTypeRefreshToken+"_"+authToken.FingerPrint, authToken.RefreshToken.Token)
memorystore.Provider.SetUserSession(sessionKey, constants.TokenTypeRefreshToken+"_"+authToken.FingerPrint, authToken.RefreshToken.Token)
}
gc.JSON(http.StatusOK, res)

View File

@@ -92,7 +92,11 @@ func VerifyEmailHandler() gin.HandlerFunc {
} else {
scope = strings.Split(scopeString, " ")
}
authToken, err := token.CreateAuthToken(c, user, roles, scope)
loginMethod := constants.AuthRecipeMethodBasicAuth
if verificationRequest.Identifier == constants.VerificationTypeMagicLinkLogin {
loginMethod = constants.AuthRecipeMethodMagicLinkLogin
}
authToken, err := token.CreateAuthToken(c, user, roles, scope, loginMethod)
if err != nil {
log.Debug("Error creating auth token: ", err)
errorRes["error_description"] = err.Error()
@@ -107,13 +111,14 @@ 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
sessionKey := loginMethod + ":" + user.ID
cookie.SetSession(c, 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)
memorystore.Provider.SetUserSession(sessionKey, constants.TokenTypeSessionToken+"_"+authToken.FingerPrint, authToken.FingerPrintHash)
memorystore.Provider.SetUserSession(sessionKey, constants.TokenTypeAccessToken+"_"+authToken.FingerPrint, authToken.AccessToken.Token)
if authToken.RefreshToken != nil {
params = params + `&refresh_token=` + authToken.RefreshToken.Token
memorystore.Provider.SetUserSession(user.ID, constants.TokenTypeRefreshToken+"_"+authToken.FingerPrint, authToken.RefreshToken.Token)
memorystore.Provider.SetUserSession(sessionKey, constants.TokenTypeRefreshToken+"_"+authToken.FingerPrint, authToken.RefreshToken.Token)
}
if redirectURL == "" {

View File

@@ -30,6 +30,7 @@ func InitMemStore() error {
constants.EnvKeyDisableEmailVerification: false,
constants.EnvKeyDisableLoginPage: false,
constants.EnvKeyDisableSignUp: false,
constants.EnvKeyDisableStrongPassword: false,
}
requiredEnvs := RequiredEnvStoreObj.GetRequiredEnv()

View File

@@ -26,24 +26,34 @@ func (c *provider) GetUserSession(userId, sessionToken string) (string, error) {
// 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()
namespaces := []string{
constants.AuthRecipeMethodBasicAuth,
constants.AuthRecipeMethodMagicLinkLogin,
constants.AuthRecipeMethodApple,
constants.AuthRecipeMethodFacebook,
constants.AuthRecipeMethodGithub,
constants.AuthRecipeMethodGoogle,
constants.AuthRecipeMethodLinkedIn,
}
for _, namespace := range namespaces {
c.sessionStore.RemoveAll(namespace + ":" + userId)
}
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
}
// DeleteSessionForNamespace to delete session for a given namespace example google,github
func (c *provider) DeleteSessionForNamespace(namespace string) error {
c.sessionStore.RemoveByNamespace(namespace)
return nil
}
// SetState sets the state in the in-memory store.
func (c *provider) SetState(key, state string) error {
if os.Getenv("ENV") != constants.TestEnv {

View File

@@ -2,6 +2,7 @@ package stores
import (
"os"
"strings"
"sync"
"github.com/authorizerdev/authorizer/server/constants"
@@ -65,3 +66,18 @@ func (s *SessionStore) GetAll(key string) map[string]string {
}
return s.store[key]
}
// RemoveByNamespace to delete session for a given namespace example google,github
func (s *SessionStore) RemoveByNamespace(namespace string) error {
if os.Getenv("ENV") != constants.TestEnv {
s.mutex.Lock()
defer s.mutex.Unlock()
}
for key := range s.store {
if strings.Contains(key, namespace+":") {
delete(s.store, key)
}
}
return nil
}

View File

@@ -12,6 +12,8 @@ type Provider interface {
DeleteUserSession(userId, key string) error
// DeleteAllSessions deletes all the sessions from the session store
DeleteAllUserSessions(userId string) error
// DeleteSessionForNamespace deletes the session for a given namespace
DeleteSessionForNamespace(namespace string) error
// SetState sets the login state (key, value form) in the session store
SetState(key, state string) error

View File

@@ -63,11 +63,48 @@ func (c *provider) DeleteUserSession(userId, key string) error {
// 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
namespaces := []string{
constants.AuthRecipeMethodBasicAuth,
constants.AuthRecipeMethodMagicLinkLogin,
constants.AuthRecipeMethodApple,
constants.AuthRecipeMethodFacebook,
constants.AuthRecipeMethodGithub,
constants.AuthRecipeMethodGoogle,
constants.AuthRecipeMethodLinkedIn,
}
for _, namespace := range namespaces {
err := c.store.Del(c.ctx, namespace+":"+userID).Err()
if err != nil {
log.Debug("Error deleting all user sessions from redis: ", err)
return err
}
}
return nil
}
// DeleteSessionForNamespace to delete session for a given namespace example google,github
func (c *provider) DeleteSessionForNamespace(namespace string) error {
var cursor uint64
for {
keys := []string{}
keys, cursor, err := c.store.Scan(c.ctx, cursor, namespace+":*", 0).Result()
if err != nil {
log.Debugf("Error scanning keys for %s namespace: %s", namespace, err.Error())
return err
}
for _, key := range keys {
err := c.store.Del(c.ctx, key).Err()
if err != nil {
log.Debugf("Error deleting sessions for %s namespace: %s", namespace, err.Error())
return err
}
}
if cursor == 0 { // no more keys
break
}
}
return nil
}
@@ -123,7 +160,7 @@ func (c *provider) GetEnvStore() (map[string]interface{}, error) {
return nil, err
}
for key, value := range data {
if key == constants.EnvKeyDisableBasicAuthentication || key == constants.EnvKeyDisableEmailVerification || key == constants.EnvKeyDisableLoginPage || key == constants.EnvKeyDisableMagicLinkLogin || key == constants.EnvKeyDisableRedisForEnv || key == constants.EnvKeyDisableSignUp {
if key == constants.EnvKeyDisableBasicAuthentication || key == constants.EnvKeyDisableEmailVerification || key == constants.EnvKeyDisableLoginPage || key == constants.EnvKeyDisableMagicLinkLogin || key == constants.EnvKeyDisableRedisForEnv || key == constants.EnvKeyDisableSignUp || key == constants.EnvKeyDisableStrongPassword {
boolValue, err := strconv.ParseBool(value)
if err != nil {
return res, err

View File

@@ -130,7 +130,6 @@ func InitOAuth() error {
AuthURL: "https://appleid.apple.com/auth/authorize",
TokenURL: "https://appleid.apple.com/auth/token",
},
Scopes: []string{"name", "email"},
}
}

View File

@@ -168,6 +168,7 @@ func EnvResolver(ctx context.Context) (*model.Env, error) {
res.DisableMagicLinkLogin = store[constants.EnvKeyDisableMagicLinkLogin].(bool)
res.DisableLoginPage = store[constants.EnvKeyDisableLoginPage].(bool)
res.DisableSignUp = store[constants.EnvKeyDisableSignUp].(bool)
res.DisableStrongPassword = store[constants.EnvKeyDisableStrongPassword].(bool)
return res, nil
}

View File

@@ -129,11 +129,11 @@ func InviteMembersResolver(ctx context.Context, params model.InviteMemberInput)
// use magic link login if that option is on
if !isMagicLinkLoginDisabled {
user.SignupMethods = constants.SignupMethodMagicLinkLogin
user.SignupMethods = constants.AuthRecipeMethodMagicLinkLogin
verificationRequest.Identifier = constants.VerificationTypeMagicLinkLogin
} else {
// use basic authentication if that option is on
user.SignupMethods = constants.SignupMethodBasicAuth
user.SignupMethods = constants.AuthRecipeMethodBasicAuth
verificationRequest.Identifier = constants.VerificationTypeForgotPassword
verifyEmailURL = appURL + "/setup-password"

View File

@@ -56,7 +56,7 @@ func LoginResolver(ctx context.Context, params model.LoginInput) (*model.AuthRes
return res, fmt.Errorf(`user access has been revoked`)
}
if !strings.Contains(user.SignupMethods, constants.SignupMethodBasicAuth) {
if !strings.Contains(user.SignupMethods, constants.AuthRecipeMethodBasicAuth) {
log.Debug("User signup method is not basic auth")
return res, fmt.Errorf(`user has not signed up email & password`)
}
@@ -97,7 +97,7 @@ func LoginResolver(ctx context.Context, params model.LoginInput) (*model.AuthRes
scope = params.Scope
}
authToken, err := token.CreateAuthToken(gc, user, roles, scope)
authToken, err := token.CreateAuthToken(gc, user, roles, scope, constants.AuthRecipeMethodBasicAuth)
if err != nil {
log.Debug("Failed to create auth token", err)
return res, err
@@ -117,12 +117,13 @@ func LoginResolver(ctx context.Context, params model.LoginInput) (*model.AuthRes
}
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)
sessionStoreKey := constants.AuthRecipeMethodBasicAuth + ":" + user.ID
memorystore.Provider.SetUserSession(sessionStoreKey, constants.TokenTypeSessionToken+"_"+authToken.FingerPrint, authToken.FingerPrintHash)
memorystore.Provider.SetUserSession(sessionStoreKey, 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)
memorystore.Provider.SetUserSession(sessionStoreKey, constants.TokenTypeRefreshToken+"_"+authToken.FingerPrint, authToken.RefreshToken.Token)
}
go db.Provider.AddSession(models.Session{

View File

@@ -41,7 +41,12 @@ func LogoutResolver(ctx context.Context) (*model.Response, error) {
return nil, err
}
memorystore.Provider.DeleteUserSession(sessionData.Subject, sessionData.Nonce)
sessionKey := sessionData.Subject
if sessionData.LoginMethod != "" {
sessionKey = sessionData.LoginMethod + ":" + sessionData.Subject
}
memorystore.Provider.DeleteUserSession(sessionKey, sessionData.Nonce)
cookie.DeleteSession(gc)
res := &model.Response{

View File

@@ -70,7 +70,7 @@ func MagicLinkLoginResolver(ctx context.Context, params model.MagicLinkLoginInpu
return res, fmt.Errorf(`signup is disabled for this instance`)
}
user.SignupMethods = constants.SignupMethodMagicLinkLogin
user.SignupMethods = constants.AuthRecipeMethodMagicLinkLogin
// define roles for new user
if len(params.Roles) > 0 {
// check if roles exists
@@ -158,8 +158,8 @@ func MagicLinkLoginResolver(ctx context.Context, params model.MagicLinkLoginInpu
}
signupMethod := existingUser.SignupMethods
if !strings.Contains(signupMethod, constants.SignupMethodMagicLinkLogin) {
signupMethod = signupMethod + "," + constants.SignupMethodMagicLinkLogin
if !strings.Contains(signupMethod, constants.AuthRecipeMethodMagicLinkLogin) {
signupMethod = signupMethod + "," + constants.AuthRecipeMethodMagicLinkLogin
}
user.SignupMethods = signupMethod

View File

@@ -101,6 +101,12 @@ func MetaResolver(ctx context.Context) (*model.Meta, error) {
isSignUpDisabled = true
}
isStrongPasswordDisabled, err := memorystore.Provider.GetBoolStoreEnvVariable(constants.EnvKeyDisableStrongPassword)
if err != nil {
log.Debug("Failed to get Disable Signup from environment variable", err)
isSignUpDisabled = true
}
metaInfo := model.Meta{
Version: constants.VERSION,
ClientID: clientID,
@@ -113,6 +119,7 @@ func MetaResolver(ctx context.Context) (*model.Meta, error) {
IsEmailVerificationEnabled: !isEmailVerificationDisabled,
IsMagicLinkLoginEnabled: !isMagicLinkLoginDisabled,
IsSignUpEnabled: !isSignUpDisabled,
IsStrongPasswordEnabled: !isStrongPasswordDisabled,
}
return &metaInfo, nil
}

View File

@@ -50,9 +50,9 @@ func ResetPasswordResolver(ctx context.Context, params model.ResetPasswordInput)
return res, fmt.Errorf(`passwords don't match`)
}
if !validators.IsValidPassword(params.Password) {
if err := validators.IsValidPassword(params.Password); err != nil {
log.Debug("Invalid password")
return res, fmt.Errorf(`password is not valid. It needs to be at least 6 characters long and contain at least one number, one uppercase letter, one lowercase letter and one special character`)
return res, err
}
// verify if token exists in db
@@ -82,8 +82,8 @@ func ResetPasswordResolver(ctx context.Context, params model.ResetPasswordInput)
user.Password = &password
signupMethod := user.SignupMethods
if !strings.Contains(signupMethod, constants.SignupMethodBasicAuth) {
signupMethod = signupMethod + "," + constants.SignupMethodBasicAuth
if !strings.Contains(signupMethod, constants.AuthRecipeMethodBasicAuth) {
signupMethod = signupMethod + "," + constants.AuthRecipeMethodBasicAuth
}
user.SignupMethods = signupMethod

View File

@@ -70,14 +70,18 @@ func SessionResolver(ctx context.Context, params *model.SessionQueryInput) (*mod
scope = params.Scope
}
authToken, err := token.CreateAuthToken(gc, user, claimRoles, scope)
authToken, err := token.CreateAuthToken(gc, user, claimRoles, scope, claims.LoginMethod)
if err != nil {
log.Debug("Failed to create auth token: ", err)
return res, err
}
// rollover the session for security
go memorystore.Provider.DeleteUserSession(userID, claims.Nonce)
sessionKey := userID
if claims.LoginMethod != "" {
sessionKey = claims.LoginMethod + ":" + userID
}
go memorystore.Provider.DeleteUserSession(sessionKey, claims.Nonce)
expiresIn := authToken.AccessToken.ExpiresAt - time.Now().Unix()
if expiresIn <= 0 {
@@ -93,12 +97,12 @@ func SessionResolver(ctx context.Context, params *model.SessionQueryInput) (*mod
}
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)
memorystore.Provider.SetUserSession(sessionKey, constants.TokenTypeSessionToken+"_"+authToken.FingerPrint, authToken.FingerPrintHash)
memorystore.Provider.SetUserSession(sessionKey, 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)
memorystore.Provider.SetUserSession(sessionKey, constants.TokenTypeRefreshToken+"_"+authToken.FingerPrint, authToken.RefreshToken.Token)
}
return res, nil
}

View File

@@ -58,9 +58,9 @@ func SignupResolver(ctx context.Context, params model.SignUpInput) (*model.AuthR
return res, fmt.Errorf(`password and confirm password does not match`)
}
if !validators.IsValidPassword(params.Password) {
if err := validators.IsValidPassword(params.Password); err != nil {
log.Debug("Invalid password")
return res, fmt.Errorf(`password is not valid. It needs to be at least 6 characters long and contain at least one number, one uppercase letter, one lowercase letter and one special character`)
return res, err
}
params.Email = strings.ToLower(params.Email)
@@ -157,7 +157,7 @@ func SignupResolver(ctx context.Context, params model.SignUpInput) (*model.AuthR
user.Picture = params.Picture
}
user.SignupMethods = constants.SignupMethodBasicAuth
user.SignupMethods = constants.AuthRecipeMethodBasicAuth
isEmailVerificationDisabled, err := memorystore.Provider.GetBoolStoreEnvVariable(constants.EnvKeyDisableEmailVerification)
if err != nil {
log.Debug("Error getting email verification disabled: ", err)
@@ -219,7 +219,7 @@ func SignupResolver(ctx context.Context, params model.SignUpInput) (*model.AuthR
scope = params.Scope
}
authToken, err := token.CreateAuthToken(gc, user, roles, scope)
authToken, err := token.CreateAuthToken(gc, user, roles, scope, constants.AuthRecipeMethodBasicAuth)
if err != nil {
log.Debug("Failed to create auth token: ", err)
return res, err
@@ -243,13 +243,14 @@ func SignupResolver(ctx context.Context, params model.SignUpInput) (*model.AuthR
User: userToReturn,
}
sessionKey := constants.AuthRecipeMethodBasicAuth + ":" + user.ID
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)
memorystore.Provider.SetUserSession(sessionKey, constants.TokenTypeSessionToken+"_"+authToken.FingerPrint, authToken.FingerPrintHash)
memorystore.Provider.SetUserSession(sessionKey, 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)
memorystore.Provider.SetUserSession(sessionKey, constants.TokenTypeRefreshToken+"_"+authToken.FingerPrint, authToken.RefreshToken.Token)
}
}

View File

@@ -21,6 +21,54 @@ import (
"github.com/authorizerdev/authorizer/server/utils"
)
// check if login methods have been disabled
// remove the session tokens for those methods
func clearSessionIfRequired(currentData, updatedData map[string]interface{}) {
isCurrentBasicAuthEnabled := !currentData[constants.EnvKeyDisableBasicAuthentication].(bool)
isCurrentMagicLinkLoginEnabled := !currentData[constants.EnvKeyDisableMagicLinkLogin].(bool)
isCurrentAppleLoginEnabled := currentData[constants.EnvKeyAppleClientID] != nil && currentData[constants.EnvKeyAppleClientSecret] != nil && currentData[constants.EnvKeyAppleClientID].(string) != "" && currentData[constants.EnvKeyAppleClientSecret].(string) != ""
isCurrentFacebookLoginEnabled := currentData[constants.EnvKeyFacebookClientID] != nil && currentData[constants.EnvKeyFacebookClientSecret] != nil && currentData[constants.EnvKeyFacebookClientID].(string) != "" && currentData[constants.EnvKeyFacebookClientSecret].(string) != ""
isCurrentGoogleLoginEnabled := currentData[constants.EnvKeyGoogleClientID] != nil && currentData[constants.EnvKeyGoogleClientSecret] != nil && currentData[constants.EnvKeyGoogleClientID].(string) != "" && currentData[constants.EnvKeyGoogleClientSecret].(string) != ""
isCurrentGithubLoginEnabled := currentData[constants.EnvKeyGithubClientID] != nil && currentData[constants.EnvKeyGithubClientSecret] != nil && currentData[constants.EnvKeyGithubClientID].(string) != "" && currentData[constants.EnvKeyGithubClientSecret].(string) != ""
isCurrentLinkedInLoginEnabled := currentData[constants.EnvKeyLinkedInClientID] != nil && currentData[constants.EnvKeyLinkedInClientSecret] != nil && currentData[constants.EnvKeyLinkedInClientID].(string) != "" && currentData[constants.EnvKeyLinkedInClientSecret].(string) != ""
isUpdatedBasicAuthEnabled := !updatedData[constants.EnvKeyDisableBasicAuthentication].(bool)
isUpdatedMagicLinkLoginEnabled := !updatedData[constants.EnvKeyDisableMagicLinkLogin].(bool)
isUpdatedAppleLoginEnabled := updatedData[constants.EnvKeyAppleClientID] != nil && updatedData[constants.EnvKeyAppleClientSecret] != nil && updatedData[constants.EnvKeyAppleClientID].(string) != "" && updatedData[constants.EnvKeyAppleClientSecret].(string) != ""
isUpdatedFacebookLoginEnabled := updatedData[constants.EnvKeyFacebookClientID] != nil && updatedData[constants.EnvKeyFacebookClientSecret] != nil && updatedData[constants.EnvKeyFacebookClientID].(string) != "" && updatedData[constants.EnvKeyFacebookClientSecret].(string) != ""
isUpdatedGoogleLoginEnabled := updatedData[constants.EnvKeyGoogleClientID] != nil && updatedData[constants.EnvKeyGoogleClientSecret] != nil && updatedData[constants.EnvKeyGoogleClientID].(string) != "" && updatedData[constants.EnvKeyGoogleClientSecret].(string) != ""
isUpdatedGithubLoginEnabled := updatedData[constants.EnvKeyGithubClientID] != nil && updatedData[constants.EnvKeyGithubClientSecret] != nil && updatedData[constants.EnvKeyGithubClientID].(string) != "" && updatedData[constants.EnvKeyGithubClientSecret].(string) != ""
isUpdatedLinkedInLoginEnabled := updatedData[constants.EnvKeyLinkedInClientID] != nil && updatedData[constants.EnvKeyLinkedInClientSecret] != nil && updatedData[constants.EnvKeyLinkedInClientID].(string) != "" && updatedData[constants.EnvKeyLinkedInClientSecret].(string) != ""
if isCurrentBasicAuthEnabled && !isUpdatedBasicAuthEnabled {
memorystore.Provider.DeleteSessionForNamespace(constants.AuthRecipeMethodBasicAuth)
}
if isCurrentMagicLinkLoginEnabled && !isUpdatedMagicLinkLoginEnabled {
memorystore.Provider.DeleteSessionForNamespace(constants.AuthRecipeMethodMagicLinkLogin)
}
if isCurrentAppleLoginEnabled && !isUpdatedAppleLoginEnabled {
memorystore.Provider.DeleteSessionForNamespace(constants.AuthRecipeMethodApple)
}
if isCurrentFacebookLoginEnabled && !isUpdatedFacebookLoginEnabled {
memorystore.Provider.DeleteSessionForNamespace(constants.AuthRecipeMethodFacebook)
}
if isCurrentGoogleLoginEnabled && !isUpdatedGoogleLoginEnabled {
memorystore.Provider.DeleteSessionForNamespace(constants.AuthRecipeMethodGoogle)
}
if isCurrentGithubLoginEnabled && !isUpdatedGithubLoginEnabled {
memorystore.Provider.DeleteSessionForNamespace(constants.AuthRecipeMethodGithub)
}
if isCurrentLinkedInLoginEnabled && !isUpdatedLinkedInLoginEnabled {
memorystore.Provider.DeleteSessionForNamespace(constants.AuthRecipeMethodLinkedIn)
}
}
// UpdateEnvResolver is a resolver for update config mutation
// This is admin only mutation
func UpdateEnvResolver(ctx context.Context, params model.UpdateEnvInput) (*model.Response, error) {
@@ -37,12 +85,19 @@ func UpdateEnvResolver(ctx context.Context, params model.UpdateEnvInput) (*model
return res, fmt.Errorf("unauthorized")
}
updatedData, err := memorystore.Provider.GetEnvStore()
currentData, err := memorystore.Provider.GetEnvStore()
if err != nil {
log.Debug("Failed to get env store: ", err)
return res, err
}
// clone currentData in new var
// that will be updated based on the req
updatedData := make(map[string]interface{})
for key, val := range currentData {
updatedData[key] = val
}
isJWTUpdated := false
algo := updatedData[constants.EnvKeyJwtType].(string)
if params.JwtType != nil {
@@ -210,6 +265,8 @@ func UpdateEnvResolver(ctx context.Context, params model.UpdateEnvInput) (*model
}
}
go clearSessionIfRequired(currentData, updatedData)
// Update local store
memorystore.Provider.UpdateEnvStore(updatedData)
jwk, err := crypto.GenerateJWKBasedOnEnv()
@@ -224,12 +281,6 @@ func UpdateEnvResolver(ctx context.Context, params model.UpdateEnvInput) (*model
return res, err
}
// TODO check how to update session store based on env change.
// err = sessionstore.InitSession()
// if err != nil {
// log.Debug("Failed to init session store: ", err)
// return res, err
// }
err = oauth.InitOAuth()
if err != nil {
return res, err

View File

@@ -50,7 +50,12 @@ func ValidateJwtTokenResolver(ctx context.Context, params model.ValidateJWTToken
// access_token and refresh_token should be validated from session store as well
if tokenType == constants.TokenTypeAccessToken || tokenType == constants.TokenTypeRefreshToken {
nonce = claims["nonce"].(string)
token, err := memorystore.Provider.GetUserSession(userID, tokenType+"_"+claims["nonce"].(string))
loginMethod := claims["login_method"]
sessionKey := userID
if loginMethod != nil && loginMethod != "" {
sessionKey = loginMethod.(string) + ":" + userID
}
token, err := memorystore.Provider.GetUserSession(sessionKey, tokenType+"_"+claims["nonce"].(string))
if err != nil || token == "" {
log.Debug("Failed to get user session: ", err)
return nil, errors.New("invalid token")

View File

@@ -73,9 +73,14 @@ func VerifyEmailResolver(ctx context.Context, params model.VerifyEmailInput) (*m
return res, err
}
loginMethod := constants.AuthRecipeMethodBasicAuth
if loginMethod == constants.VerificationTypeMagicLinkLogin {
loginMethod = constants.AuthRecipeMethodMagicLinkLogin
}
roles := strings.Split(user.Roles, ",")
scope := []string{"openid", "email", "profile"}
authToken, err := token.CreateAuthToken(gc, user, roles, scope)
authToken, err := token.CreateAuthToken(gc, user, roles, scope, loginMethod)
if err != nil {
log.Debug("Failed to create auth token: ", err)
return res, err
@@ -100,13 +105,14 @@ func VerifyEmailResolver(ctx context.Context, params model.VerifyEmailInput) (*m
User: user.AsAPIUser(),
}
sessionKey := loginMethod + ":" + user.ID
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)
memorystore.Provider.SetUserSession(sessionKey, constants.TokenTypeSessionToken+"_"+authToken.FingerPrint, authToken.FingerPrintHash)
memorystore.Provider.SetUserSession(sessionKey, 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)
memorystore.Provider.SetUserSession(sessionKey, constants.TokenTypeRefreshToken+"_"+authToken.FingerPrint, authToken.RefreshToken.Token)
}
return res, nil
}

View File

@@ -36,7 +36,13 @@ func logoutTests(t *testing.T, s TestSetup) {
assert.NoError(t, err)
assert.NotEmpty(t, claims)
sessionToken, err := memorystore.Provider.GetUserSession(verifyRes.User.ID, constants.TokenTypeSessionToken+"_"+claims["nonce"].(string))
loginMethod := claims["login_method"]
sessionKey := verifyRes.User.ID
if loginMethod != nil && loginMethod != "" {
sessionKey = loginMethod.(string) + ":" + verifyRes.User.ID
}
sessionToken, err := memorystore.Provider.GetUserSession(sessionKey, constants.TokenTypeSessionToken+"_"+claims["nonce"].(string))
assert.NoError(t, err)
assert.NotEmpty(t, sessionToken)

View File

@@ -37,9 +37,9 @@ func profileTests(t *testing.T, s TestSetup) {
ctx = context.WithValue(req.Context(), "GinContextKey", s.GinContext)
profileRes, err := resolvers.ProfileResolver(ctx)
assert.Nil(t, err)
assert.NotNil(t, profileRes)
s.GinContext.Request.Header.Set("Authorization", "")
newEmail := *&profileRes.Email
newEmail := profileRes.Email
assert.Equal(t, email, newEmail, "emails should be equal")
cleanData(email)

View File

@@ -41,7 +41,8 @@ func sessionTests(t *testing.T, s TestSetup) {
assert.NoError(t, err)
assert.NotEmpty(t, claims)
sessionToken, err := memorystore.Provider.GetUserSession(verifyRes.User.ID, constants.TokenTypeSessionToken+"_"+claims["nonce"].(string))
sessionKey := constants.AuthRecipeMethodBasicAuth + ":" + verifyRes.User.ID
sessionToken, err := memorystore.Provider.GetUserSession(sessionKey, constants.TokenTypeSessionToken+"_"+claims["nonce"].(string))
assert.NoError(t, err)
assert.NotEmpty(t, sessionToken)

View File

@@ -50,12 +50,13 @@ func validateJwtTokenTest(t *testing.T, s TestSetup) {
roles := []string{"user"}
gc, err := utils.GinContextFromContext(ctx)
assert.NoError(t, err)
authToken, err := token.CreateAuthToken(gc, user, roles, scope)
memorystore.Provider.SetUserSession(user.ID, constants.TokenTypeSessionToken+"_"+authToken.FingerPrint, authToken.FingerPrintHash)
memorystore.Provider.SetUserSession(user.ID, constants.TokenTypeAccessToken+"_"+authToken.FingerPrint, authToken.AccessToken.Token)
sessionKey := constants.AuthRecipeMethodBasicAuth + ":" + user.ID
authToken, err := token.CreateAuthToken(gc, user, roles, scope, constants.AuthRecipeMethodBasicAuth)
memorystore.Provider.SetUserSession(sessionKey, constants.TokenTypeSessionToken+"_"+authToken.FingerPrint, authToken.FingerPrintHash)
memorystore.Provider.SetUserSession(sessionKey, constants.TokenTypeAccessToken+"_"+authToken.FingerPrint, authToken.AccessToken.Token)
if authToken.RefreshToken != nil {
memorystore.Provider.SetUserSession(user.ID, constants.TokenTypeRefreshToken+"_"+authToken.FingerPrint, authToken.RefreshToken.Token)
memorystore.Provider.SetUserSession(sessionKey, constants.TokenTypeRefreshToken+"_"+authToken.FingerPrint, authToken.RefreshToken.Token)
}
t.Run(`should validate the access token`, func(t *testing.T) {

View File

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

View File

@@ -38,23 +38,25 @@ type Token struct {
// SessionData
type SessionData struct {
Subject string `json:"sub"`
Roles []string `json:"roles"`
Scope []string `json:"scope"`
Nonce string `json:"nonce"`
IssuedAt int64 `json:"iat"`
ExpiresAt int64 `json:"exp"`
Subject string `json:"sub"`
Roles []string `json:"roles"`
Scope []string `json:"scope"`
Nonce string `json:"nonce"`
IssuedAt int64 `json:"iat"`
ExpiresAt int64 `json:"exp"`
LoginMethod string `json:"login_method"`
}
// CreateSessionToken creates a new session token
func CreateSessionToken(user models.User, nonce string, roles, scope []string) (*SessionData, string, error) {
func CreateSessionToken(user models.User, nonce string, roles, scope []string, loginMethod string) (*SessionData, string, error) {
fingerPrintMap := &SessionData{
Nonce: nonce,
Roles: roles,
Subject: user.ID,
Scope: scope,
IssuedAt: time.Now().Unix(),
ExpiresAt: time.Now().AddDate(1, 0, 0).Unix(),
Nonce: nonce,
Roles: roles,
Subject: user.ID,
Scope: scope,
LoginMethod: loginMethod,
IssuedAt: time.Now().Unix(),
ExpiresAt: time.Now().AddDate(1, 0, 0).Unix(),
}
fingerPrintBytes, _ := json.Marshal(fingerPrintMap)
fingerPrintHash, err := crypto.EncryptAES(string(fingerPrintBytes))
@@ -66,19 +68,19 @@ func CreateSessionToken(user models.User, nonce string, roles, scope []string) (
}
// CreateAuthToken creates a new auth token when userlogs in
func CreateAuthToken(gc *gin.Context, user models.User, roles, scope []string) (*Token, error) {
func CreateAuthToken(gc *gin.Context, user models.User, roles, scope []string, loginMethod string) (*Token, error) {
hostname := parsers.GetHost(gc)
nonce := uuid.New().String()
_, fingerPrintHash, err := CreateSessionToken(user, nonce, roles, scope)
_, fingerPrintHash, err := CreateSessionToken(user, nonce, roles, scope, loginMethod)
if err != nil {
return nil, err
}
accessToken, accessTokenExpiresAt, err := CreateAccessToken(user, roles, scope, hostname, nonce)
accessToken, accessTokenExpiresAt, err := CreateAccessToken(user, roles, scope, hostname, nonce, loginMethod)
if err != nil {
return nil, err
}
idToken, idTokenExpiresAt, err := CreateIDToken(user, roles, hostname, nonce)
idToken, idTokenExpiresAt, err := CreateIDToken(user, roles, hostname, nonce, loginMethod)
if err != nil {
return nil, err
}
@@ -91,7 +93,7 @@ func CreateAuthToken(gc *gin.Context, user models.User, roles, scope []string) (
}
if utils.StringSliceContains(scope, "offline_access") {
refreshToken, refreshTokenExpiresAt, err := CreateRefreshToken(user, roles, scope, hostname, nonce)
refreshToken, refreshTokenExpiresAt, err := CreateRefreshToken(user, roles, scope, hostname, nonce, loginMethod)
if err != nil {
return nil, err
}
@@ -103,7 +105,7 @@ func CreateAuthToken(gc *gin.Context, user models.User, roles, scope []string) (
}
// CreateRefreshToken util to create JWT token
func CreateRefreshToken(user models.User, roles, scopes []string, hostname, nonce string) (string, int64, error) {
func CreateRefreshToken(user models.User, roles, scopes []string, hostname, nonce, loginMethod string) (string, int64, error) {
// expires in 1 year
expiryBound := time.Hour * 8760
expiresAt := time.Now().Add(expiryBound).Unix()
@@ -112,15 +114,16 @@ func CreateRefreshToken(user models.User, roles, scopes []string, hostname, nonc
return "", 0, err
}
customClaims := jwt.MapClaims{
"iss": hostname,
"aud": clientID,
"sub": user.ID,
"exp": expiresAt,
"iat": time.Now().Unix(),
"token_type": constants.TokenTypeRefreshToken,
"roles": roles,
"scope": scopes,
"nonce": nonce,
"iss": hostname,
"aud": clientID,
"sub": user.ID,
"exp": expiresAt,
"iat": time.Now().Unix(),
"token_type": constants.TokenTypeRefreshToken,
"roles": roles,
"scope": scopes,
"nonce": nonce,
"login_method": loginMethod,
}
token, err := SignJWTToken(customClaims)
@@ -133,7 +136,7 @@ func CreateRefreshToken(user models.User, roles, scopes []string, hostname, nonc
// CreateAccessToken util to create JWT token, based on
// user information, roles config and CUSTOM_ACCESS_TOKEN_SCRIPT
func CreateAccessToken(user models.User, roles, scopes []string, hostName, nonce string) (string, int64, error) {
func CreateAccessToken(user models.User, roles, scopes []string, hostName, nonce, loginMethod string) (string, int64, error) {
expireTime, err := memorystore.Provider.GetStringStoreEnvVariable(constants.EnvKeyAccessTokenExpiryTime)
if err != nil {
return "", 0, err
@@ -150,15 +153,16 @@ func CreateAccessToken(user models.User, roles, scopes []string, hostName, nonce
return "", 0, err
}
customClaims := jwt.MapClaims{
"iss": hostName,
"aud": clientID,
"nonce": nonce,
"sub": user.ID,
"exp": expiresAt,
"iat": time.Now().Unix(),
"token_type": constants.TokenTypeAccessToken,
"scope": scopes,
"roles": roles,
"iss": hostName,
"aud": clientID,
"nonce": nonce,
"sub": user.ID,
"exp": expiresAt,
"iat": time.Now().Unix(),
"token_type": constants.TokenTypeAccessToken,
"scope": scopes,
"roles": roles,
"login_method": loginMethod,
}
token, err := SignJWTToken(customClaims)
@@ -205,7 +209,13 @@ func ValidateAccessToken(gc *gin.Context, accessToken string) (map[string]interf
userID := res["sub"].(string)
nonce := res["nonce"].(string)
token, err := memorystore.Provider.GetUserSession(userID, constants.TokenTypeAccessToken+"_"+nonce)
loginMethod := res["login_method"]
sessionKey := userID
if loginMethod != nil && loginMethod != "" {
sessionKey = loginMethod.(string) + ":" + userID
}
token, err := memorystore.Provider.GetUserSession(sessionKey, constants.TokenTypeAccessToken+"_"+nonce)
if nonce == "" || err != nil {
return res, fmt.Errorf(`unauthorized`)
}
@@ -241,7 +251,13 @@ func ValidateRefreshToken(gc *gin.Context, refreshToken string) (map[string]inte
userID := res["sub"].(string)
nonce := res["nonce"].(string)
token, err := memorystore.Provider.GetUserSession(userID, constants.TokenTypeRefreshToken+"_"+nonce)
loginMethod := res["login_method"]
sessionKey := userID
if loginMethod != nil && loginMethod != "" {
sessionKey = loginMethod.(string) + ":" + userID
}
token, err := memorystore.Provider.GetUserSession(sessionKey, constants.TokenTypeRefreshToken+"_"+nonce)
if nonce == "" || err != nil {
return res, fmt.Errorf(`unauthorized`)
}
@@ -278,7 +294,12 @@ func ValidateBrowserSession(gc *gin.Context, encryptedSession string) (*SessionD
return nil, err
}
token, err := memorystore.Provider.GetUserSession(res.Subject, constants.TokenTypeSessionToken+"_"+res.Nonce)
sessionStoreKey := res.Subject
if res.LoginMethod != "" {
sessionStoreKey = res.LoginMethod + ":" + res.Subject
}
token, err := memorystore.Provider.GetUserSession(sessionStoreKey, constants.TokenTypeSessionToken+"_"+res.Nonce)
if token == "" || err != nil {
log.Debug("invalid browser session:", err)
return nil, fmt.Errorf(`unauthorized`)
@@ -297,7 +318,7 @@ func ValidateBrowserSession(gc *gin.Context, encryptedSession string) (*SessionD
// CreateIDToken util to create JWT token, based on
// user information, roles config and CUSTOM_ACCESS_TOKEN_SCRIPT
func CreateIDToken(user models.User, roles []string, hostname, nonce string) (string, int64, error) {
func CreateIDToken(user models.User, roles []string, hostname, nonce, loginMethod string) (string, int64, error) {
expireTime, err := memorystore.Provider.GetStringStoreEnvVariable(constants.EnvKeyAccessTokenExpiryTime)
if err != nil {
return "", 0, err
@@ -332,6 +353,7 @@ func CreateIDToken(user models.User, roles []string, hostname, nonce string) (st
"iat": time.Now().Unix(),
"token_type": constants.TokenTypeIdentityToken,
"allowed_roles": strings.Split(user.Roles, ","),
"login_method": loginMethod,
claimKey: roles,
}

View File

@@ -1,5 +1,12 @@
package validators
import (
"errors"
"github.com/authorizerdev/authorizer/server/constants"
"github.com/authorizerdev/authorizer/server/memorystore"
)
// ValidatePassword to validate the password against the following policy
// min char length: 6
// max char length: 36
@@ -7,9 +14,16 @@ package validators
// at least one lower case letter
// at least one digit
// at least one special character
func IsValidPassword(password string) bool {
func IsValidPassword(password string) error {
if len(password) < 6 || len(password) > 36 {
return false
return errors.New("password must be of minimum 6 characters and maximum 36 characters")
}
// if strong password is disabled
// just check for min 6 chars & max 36
isStrongPasswordDisabled, _ := memorystore.Provider.GetBoolStoreEnvVariable(constants.EnvKeyDisableStrongPassword)
if isStrongPasswordDisabled {
return nil
}
hasUpperCase := false
@@ -29,5 +43,11 @@ func IsValidPassword(password string) bool {
}
}
return hasUpperCase && hasLowerCase && hasDigit && hasSpecialChar
isValid := hasUpperCase && hasLowerCase && hasDigit && hasSpecialChar
if isValid {
return nil
}
return errors.New(`password is not valid. It needs to be at least 6 characters long and contain at least one number, one uppercase letter, one lowercase letter and one special character`)
}