Compare commits

...

28 Commits

Author SHA1 Message Date
Lakhan Samani
b39f0b87fd Merge pull request #313 from authorizerdev/feat/add-get-user
Add get user api for admin
2023-01-05 20:17:48 +05:30
Lakhan Samani
b2423140e2 Add get user api for admin 2023-01-05 20:16:41 +05:30
Lakhan Samani
4f810d2f8b Merge pull request #307 from authorizerdev/feat/mobile-basic-auth
feat: add mobile based basic auth
2022-12-25 03:28:55 +05:30
Lakhan Samani
313b510ba1 feat: add signup + login using mobile 2022-12-25 03:22:42 +05:30
Lakhan Samani
105d9be685 Merge pull request #310 from Lentech-AS/main
Rnd warning fixes
2022-12-24 09:18:07 +05:30
Leander Gangsø
bc68b61879 fix: oauth2.NoContext is deprecated
using context.TODO instead
2022-12-23 21:12:11 +01:00
Leander Gangsø
2847300bf6 fix: update deprecated func since go 1.16
might want to update the docs if you accept this, as it states go > 1.15 is required
2022-12-23 21:12:11 +01:00
Leander Gangsø
d438480f37 fix: remove unused formatting directive 2022-12-23 21:12:11 +01:00
Lakhan Samani
da29f9d055 Merge pull request #308 from Lentech-AS/main
change toast position to top-right
2022-12-23 07:10:11 +05:30
Leander Gangsø
f29256a8f5 change toast position to top-right 2022-12-22 19:53:18 +01:00
Lakhan Samani
1eb8965f98 feat: add mobile based basic auth 2022-12-21 23:14:24 +05:30
Lakhan Samani
1c4e29fa7c fix: access_token renew + web_message redirect 2022-11-29 05:27:29 +05:30
Lakhan Samani
7a28795fa0 Merge pull request #302 from authorizerdev/fix/sql-user-deletion
fix(sql): user deletion
2022-11-25 22:46:57 +05:30
Lakhan Samani
f5db00beb0 fix(sql): user deletion
Resolves: https://github.com/authorizerdev/docs/issues/18
2022-11-25 22:45:23 +05:30
Lakhan Samani
d515a1f41d chore: update app 1.1.4 2022-11-25 22:40:14 +05:30
Lakhan Samani
c948c98e94 Merge pull request #298 from authorizerdev/fix/url-parsing
fix: remove extra slash from host
2022-11-24 19:13:50 +05:30
Lakhan Samani
e985e096bc fix: remove extra slash from host 2022-11-24 12:58:04 +05:30
Lakhan Samani
70bab70ead fix: validating id_token 2022-11-23 22:03:08 +05:30
Lakhan Samani
6ddaf88e3f Merge pull request #294 from authorizerdev/fix/sql-unique-phone-constraint
fix(server): unique constraint for phone_number on mssql
2022-11-17 23:10:55 +05:30
Lakhan Samani
f8bcd0fe51 fix(server): unique constraint for phone_number on mssql 2022-11-17 23:08:17 +05:30
Lakhan Samani
16c4b8ab76 fix(server): global logging 2022-11-17 10:35:38 +05:30
Lakhan Samani
0788c5ff5e fix(server): default loglevel via cli arg 2022-11-17 10:27:55 +05:30
Lakhan Samani
2d5d38de02 fix(server): gorm logging
- add support for LOG_LEVEL env var (Resolves #271)
2022-11-17 10:25:00 +05:30
Lakhan Samani
0dd06d9afd fix(server): basic auth check 2022-11-17 05:44:40 +05:30
Lakhan Samani
d2f472a9cf Merge pull request #293 from luclu7/main
fix: codeVerifier shouldn't be empty for basic auth
2022-11-17 05:44:01 +05:30
Luclu7
4678193300 fix: codeVerifier shouldn't be empty for basic auth 2022-11-16 23:31:39 +01:00
Lakhan Samani
67be8ae285 feat(server): allow using client_id & secret from basic auth header in token endpoint 2022-11-16 22:40:45 +05:30
Lakhan Samani
f9d2130c65 Merge pull request #270 from authorizerdev/fix/oauth-provider
fix(server): authorizer openid flow
2022-11-16 12:27:30 +05:30
57 changed files with 2584 additions and 998 deletions

101
app/package-lock.json generated
View File

@@ -9,7 +9,7 @@
"version": "1.0.0",
"license": "ISC",
"dependencies": {
"@authorizerdev/authorizer-react": "^1.1.3-beta.1",
"@authorizerdev/authorizer-react": "^1.1.4",
"@types/react": "^17.0.15",
"@types/react-dom": "^17.0.9",
"esbuild": "^0.12.17",
@@ -27,9 +27,9 @@
}
},
"node_modules/@authorizerdev/authorizer-js": {
"version": "1.1.2-beta.1",
"resolved": "https://registry.npmjs.org/@authorizerdev/authorizer-js/-/authorizer-js-1.1.2-beta.1.tgz",
"integrity": "sha512-u+O2iB3tqF1HtdJ6LfBXL9iMycqlCCL3othBQkqitGP1ldhASWLJ2pcXZAcHgyoeczKdj2XKZKdIcWB3GYR0IQ==",
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/@authorizerdev/authorizer-js/-/authorizer-js-1.1.2.tgz",
"integrity": "sha512-22qoqBaCNMn3QRWdJXmwAZeb5X9lwhZF3y23loY0eO3xUUzBaJiltENjHynbLGCg8LGgn7UaJEKDqGfL6Rzwvg==",
"dependencies": {
"cross-fetch": "^3.1.5"
},
@@ -38,14 +38,11 @@
}
},
"node_modules/@authorizerdev/authorizer-react": {
"version": "1.1.3-beta.1",
"resolved": "https://registry.npmjs.org/@authorizerdev/authorizer-react/-/authorizer-react-1.1.3-beta.1.tgz",
"integrity": "sha512-+ZsOBp6XjZVnDyeJCXgaqZ8xzFO7ygpHB6v2cblCKIA3wX5pg/Dsg1oumHGrSHIEK8No/GOtCjSx4Rv6/CweBQ==",
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/@authorizerdev/authorizer-react/-/authorizer-react-1.1.4.tgz",
"integrity": "sha512-FBH2igXFM8+TdA2hl1S/HMzt1+OL5wWUow3+Zyiq+IkG9nIjWFlM7ebo4D0zJd875IJiabYFnXqstRABo0ysIQ==",
"dependencies": {
"@authorizerdev/authorizer-js": "^1.1.2-beta.1",
"final-form": "^4.20.2",
"react-final-form": "^6.5.3",
"styled-components": "^5.3.0"
"@authorizerdev/authorizer-js": "^1.1.2"
},
"engines": {
"node": ">=10"
@@ -469,18 +466,6 @@
"node": ">=0.8.0"
}
},
"node_modules/final-form": {
"version": "4.20.4",
"resolved": "https://registry.npmjs.org/final-form/-/final-form-4.20.4.tgz",
"integrity": "sha512-hyoOVVilPLpkTvgi+FSJkFZrh0Yhy4BhE6lk/NiBwrF4aRV8/ykKEyXYvQH/pfUbRkOosvpESYouFb+FscsLrw==",
"dependencies": {
"@babel/runtime": "^7.10.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/final-form"
}
},
"node_modules/globals": {
"version": "11.12.0",
"resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz",
@@ -673,33 +658,6 @@
"react": "17.0.2"
}
},
"node_modules/react-final-form": {
"version": "6.5.7",
"resolved": "https://registry.npmjs.org/react-final-form/-/react-final-form-6.5.7.tgz",
"integrity": "sha512-o7tvJXB+McGiXOILqIC8lnOcX4aLhIBiF/Xi9Qet35b7XOS8R7KL8HLRKTfnZWQJm6MCE15v1U0SFive0NcxyA==",
"dependencies": {
"@babel/runtime": "^7.15.4"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/final-form"
},
"peerDependencies": {
"final-form": "4.20.4",
"react": "^16.8.0 || ^17.0.0"
}
},
"node_modules/react-final-form/node_modules/@babel/runtime": {
"version": "7.16.7",
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.16.7.tgz",
"integrity": "sha512-9E9FJowqAsytyOY6LG+1KuueckRL+aQW+mKvXRXnuFGyRAyepJPmEo9vgMfXUA6O9u3IeEdv9MAkppFcaQwogQ==",
"dependencies": {
"regenerator-runtime": "^0.13.4"
},
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/react-is": {
"version": "17.0.2",
"resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz",
@@ -876,22 +834,19 @@
},
"dependencies": {
"@authorizerdev/authorizer-js": {
"version": "1.1.2-beta.1",
"resolved": "https://registry.npmjs.org/@authorizerdev/authorizer-js/-/authorizer-js-1.1.2-beta.1.tgz",
"integrity": "sha512-u+O2iB3tqF1HtdJ6LfBXL9iMycqlCCL3othBQkqitGP1ldhASWLJ2pcXZAcHgyoeczKdj2XKZKdIcWB3GYR0IQ==",
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/@authorizerdev/authorizer-js/-/authorizer-js-1.1.2.tgz",
"integrity": "sha512-22qoqBaCNMn3QRWdJXmwAZeb5X9lwhZF3y23loY0eO3xUUzBaJiltENjHynbLGCg8LGgn7UaJEKDqGfL6Rzwvg==",
"requires": {
"cross-fetch": "^3.1.5"
}
},
"@authorizerdev/authorizer-react": {
"version": "1.1.3-beta.1",
"resolved": "https://registry.npmjs.org/@authorizerdev/authorizer-react/-/authorizer-react-1.1.3-beta.1.tgz",
"integrity": "sha512-+ZsOBp6XjZVnDyeJCXgaqZ8xzFO7ygpHB6v2cblCKIA3wX5pg/Dsg1oumHGrSHIEK8No/GOtCjSx4Rv6/CweBQ==",
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/@authorizerdev/authorizer-react/-/authorizer-react-1.1.4.tgz",
"integrity": "sha512-FBH2igXFM8+TdA2hl1S/HMzt1+OL5wWUow3+Zyiq+IkG9nIjWFlM7ebo4D0zJd875IJiabYFnXqstRABo0ysIQ==",
"requires": {
"@authorizerdev/authorizer-js": "^1.1.2-beta.1",
"final-form": "^4.20.2",
"react-final-form": "^6.5.3",
"styled-components": "^5.3.0"
"@authorizerdev/authorizer-js": "^1.1.2"
}
},
"@babel/code-frame": {
@@ -1231,14 +1186,6 @@
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
"integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ="
},
"final-form": {
"version": "4.20.4",
"resolved": "https://registry.npmjs.org/final-form/-/final-form-4.20.4.tgz",
"integrity": "sha512-hyoOVVilPLpkTvgi+FSJkFZrh0Yhy4BhE6lk/NiBwrF4aRV8/ykKEyXYvQH/pfUbRkOosvpESYouFb+FscsLrw==",
"requires": {
"@babel/runtime": "^7.10.0"
}
},
"globals": {
"version": "11.12.0",
"resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz",
@@ -1387,24 +1334,6 @@
"scheduler": "^0.20.2"
}
},
"react-final-form": {
"version": "6.5.7",
"resolved": "https://registry.npmjs.org/react-final-form/-/react-final-form-6.5.7.tgz",
"integrity": "sha512-o7tvJXB+McGiXOILqIC8lnOcX4aLhIBiF/Xi9Qet35b7XOS8R7KL8HLRKTfnZWQJm6MCE15v1U0SFive0NcxyA==",
"requires": {
"@babel/runtime": "^7.15.4"
},
"dependencies": {
"@babel/runtime": {
"version": "7.16.7",
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.16.7.tgz",
"integrity": "sha512-9E9FJowqAsytyOY6LG+1KuueckRL+aQW+mKvXRXnuFGyRAyepJPmEo9vgMfXUA6O9u3IeEdv9MAkppFcaQwogQ==",
"requires": {
"regenerator-runtime": "^0.13.4"
}
}
}
},
"react-is": {
"version": "17.0.2",
"resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz",

View File

@@ -12,7 +12,7 @@
"author": "Lakhan Samani",
"license": "ISC",
"dependencies": {
"@authorizerdev/authorizer-react": "^1.1.3",
"@authorizerdev/authorizer-react": "^1.1.4",
"@types/react": "^17.0.15",
"@types/react-dom": "^17.0.9",
"esbuild": "^0.12.17",

View File

@@ -44,7 +44,7 @@ const DeleteEmailTemplateModal = ({
title: capitalizeFirstLetter(res.error.message),
isClosable: true,
status: 'error',
position: 'bottom-right',
position: 'top-right',
});
return;
@@ -53,7 +53,7 @@ const DeleteEmailTemplateModal = ({
title: capitalizeFirstLetter(res.data?._delete_email_template.message),
isClosable: true,
status: 'success',
position: 'bottom-right',
position: 'top-right',
});
}
onClose();

View File

@@ -51,7 +51,7 @@ const DeleteUserModal = ({
title: capitalizeFirstLetter(res.error.message),
isClosable: true,
status: 'error',
position: 'bottom-right',
position: 'top-right',
});
return;
@@ -60,7 +60,7 @@ const DeleteUserModal = ({
title: capitalizeFirstLetter(res.data?._delete_user.message),
isClosable: true,
status: 'success',
position: 'bottom-right',
position: 'top-right',
});
}
onClose();

View File

@@ -44,7 +44,7 @@ const DeleteWebhookModal = ({
title: capitalizeFirstLetter(res.error.message),
isClosable: true,
status: 'error',
position: 'bottom-right',
position: 'top-right',
});
return;
@@ -53,7 +53,7 @@ const DeleteWebhookModal = ({
title: capitalizeFirstLetter(res.data?._delete_webhook.message),
isClosable: true,
status: 'success',
position: 'bottom-right',
position: 'top-right',
});
}
onClose();

View File

@@ -104,14 +104,14 @@ const EditUserModal = ({
title: 'User data update failed',
isClosable: true,
status: 'error',
position: 'bottom-right',
position: 'top-right',
});
} else if (res.data?._update_user?.id) {
toast({
title: 'User data update successful',
isClosable: true,
status: 'success',
position: 'bottom-right',
position: 'top-right',
});
}
onClose();

View File

@@ -43,7 +43,7 @@ const JSTConfigurations = ({
title: `JWT config copied successfully`,
isClosable: true,
status: 'success',
position: 'bottom-right',
position: 'top-right',
});
} catch (err) {
console.error({
@@ -54,7 +54,7 @@ const JSTConfigurations = ({
title: `Failed to copy JWT config`,
isClosable: true,
status: 'error',
position: 'bottom-right',
position: 'top-right',
});
}
};

View File

@@ -73,7 +73,7 @@ const GenerateKeysModal = ({ jwtType, getData }: propTypes) => {
title: 'Error occurred generating jwt keys',
isClosable: true,
status: 'error',
position: 'bottom-right',
position: 'top-right',
});
closeHandler();
} else {
@@ -107,7 +107,7 @@ const GenerateKeysModal = ({ jwtType, getData }: propTypes) => {
title: 'Error occurred setting jwt keys',
isClosable: true,
status: 'error',
position: 'bottom-right',
position: 'top-right',
});
return;
@@ -116,7 +116,7 @@ const GenerateKeysModal = ({ jwtType, getData }: propTypes) => {
title: 'JWT keys updated successfully',
isClosable: true,
status: 'success',
position: 'bottom-right',
position: 'top-right',
});
closeHandler();
};

View File

@@ -105,7 +105,7 @@ const InviteMembersModal = ({
title: 'Invites sent successfully!',
isClosable: true,
status: 'success',
position: 'bottom-right',
position: 'top-right',
});
setLoading(false);
updateUserList();
@@ -117,7 +117,7 @@ const InviteMembersModal = ({
title: error?.message || 'Error occurred, try again!',
isClosable: true,
status: 'error',
position: 'bottom-right',
position: 'top-right',
});
setLoading(false);
}

View File

@@ -173,7 +173,7 @@ const UpdateEmailTemplate = ({
title: capitalizeFirstLetter(res.error.message),
isClosable: true,
status: 'error',
position: 'bottom-right',
position: 'top-right',
});
} else if (
res.data?._add_email_template ||
@@ -186,7 +186,7 @@ const UpdateEmailTemplate = ({
),
isClosable: true,
status: 'success',
position: 'bottom-right',
position: 'top-right',
});
setTemplateData({
...initTemplateData,

View File

@@ -290,7 +290,7 @@ const UpdateWebhookModal = ({
title: capitalizeFirstLetter(res.error.message),
isClosable: true,
status: 'error',
position: 'bottom-right',
position: 'top-right',
});
} else if (res.data?._add_webhook || res.data?._update_webhook) {
toast({
@@ -299,7 +299,7 @@ const UpdateWebhookModal = ({
),
isClosable: true,
status: 'success',
position: 'bottom-right',
position: 'top-right',
});
setWebhook({
...initWebhookData,

View File

@@ -57,7 +57,7 @@ export default function Auth() {
title: capitalizeFirstLetter(error.message),
isClosable: true,
status: 'error',
position: 'bottom-right',
position: 'top-right',
});
});
}

View File

@@ -203,7 +203,7 @@ const Environment = () => {
} variables`,
isClosable: true,
status: 'success',
position: 'bottom-right',
position: 'top-right',
});
};

View File

@@ -180,14 +180,14 @@ export default function Users() {
title: 'User verification failed',
isClosable: true,
status: 'error',
position: 'bottom-right',
position: 'top-right',
});
} else if (res.data?._update_user?.id) {
toast({
title: 'User verification successful',
isClosable: true,
status: 'success',
position: 'bottom-right',
position: 'top-right',
});
}
updateUserList();
@@ -211,14 +211,14 @@ export default function Users() {
title: 'User access enable failed',
isClosable: true,
status: 'error',
position: 'bottom-right',
position: 'top-right',
});
} else {
toast({
title: 'User access enabled successfully',
isClosable: true,
status: 'success',
position: 'bottom-right',
position: 'top-right',
});
}
updateUserList();
@@ -236,14 +236,14 @@ export default function Users() {
title: 'User access revoke failed',
isClosable: true,
status: 'error',
position: 'bottom-right',
position: 'top-right',
});
} else {
toast({
title: 'User access revoked successfully',
isClosable: true,
status: 'success',
position: 'bottom-right',
position: 'top-right',
});
}
updateUserList();
@@ -268,7 +268,7 @@ export default function Users() {
} for user`,
isClosable: true,
status: 'success',
position: 'bottom-right',
position: 'top-right',
});
updateUserList();
return;
@@ -277,7 +277,7 @@ export default function Users() {
title: 'Multi factor authentication update failed for user',
isClosable: true,
status: 'error',
position: 'bottom-right',
position: 'top-right',
});
};

View File

@@ -3,6 +3,8 @@ package constants
const (
// AuthRecipeMethodBasicAuth is the basic_auth auth method
AuthRecipeMethodBasicAuth = "basic_auth"
// AuthRecipeMethodMobileBasicAuth is the mobile basic_auth method, where user can signup using mobile number and password
AuthRecipeMethodMobileBasicAuth = "mobile_basic_auth"
// AuthRecipeMethodMagicLinkLogin is the magic_link_login auth method
AuthRecipeMethodMagicLinkLogin = "magic_link_login"
// AuthRecipeMethodGoogle is the google auth method

View File

@@ -125,6 +125,8 @@ const (
EnvKeyDisableEmailVerification = "DISABLE_EMAIL_VERIFICATION"
// EnvKeyDisableBasicAuthentication key for env variable DISABLE_BASIC_AUTH
EnvKeyDisableBasicAuthentication = "DISABLE_BASIC_AUTHENTICATION"
// EnvKeyDisableBasicAuthentication key for env variable DISABLE_MOBILE_BASIC_AUTH
EnvKeyDisableMobileBasicAuthentication = "DISABLE_MOBILE_BASIC_AUTHENTICATION"
// EnvKeyDisableMagicLinkLogin key for env variable DISABLE_MAGIC_LINK_LOGIN
EnvKeyDisableMagicLinkLogin = "DISABLE_MAGIC_LINK_LOGIN"
// EnvKeyDisableLoginPage key for env variable DISABLE_LOGIN_PAGE

View File

@@ -25,7 +25,7 @@ type User struct {
Nickname *string `json:"nickname" bson:"nickname" cql:"nickname" dynamo:"nickname"`
Gender *string `json:"gender" bson:"gender" cql:"gender" dynamo:"gender"`
Birthdate *string `json:"birthdate" bson:"birthdate" cql:"birthdate" dynamo:"birthdate"`
PhoneNumber *string `gorm:"unique" json:"phone_number" bson:"phone_number" cql:"phone_number" dynamo:"phone_number"`
PhoneNumber *string `gorm:"index" json:"phone_number" bson:"phone_number" cql:"phone_number" dynamo:"phone_number"`
PhoneNumberVerifiedAt *int64 `json:"phone_number_verified_at" bson:"phone_number_verified_at" cql:"phone_number_verified_at" dynamo:"phone_number_verified_at"`
Picture *string `json:"picture" bson:"picture" cql:"picture" dynamo:"picture"`
Roles string `json:"roles" bson:"roles" cql:"roles" dynamo:"roles"`

View File

@@ -15,6 +15,7 @@ import (
"github.com/authorizerdev/authorizer/server/db/models"
"github.com/authorizerdev/authorizer/server/graph/model"
"github.com/authorizerdev/authorizer/server/memorystore"
"github.com/authorizerdev/authorizer/server/refs"
)
// AddUser to save user information in database
@@ -32,6 +33,12 @@ func (p *provider) AddUser(ctx context.Context, user models.User) (models.User,
user.Roles = defaultRoles
}
if user.PhoneNumber != nil && strings.TrimSpace(refs.StringValue(user.PhoneNumber)) != "" {
if u, _ := p.GetUserByPhoneNumber(ctx, refs.StringValue(user.PhoneNumber)); u != nil && u.ID != user.ID {
return user, fmt.Errorf("user with given phone number already exists")
}
}
user.CreatedAt = time.Now().Unix()
user.UpdatedAt = time.Now().Unix()
userCollection, _ := p.db.Collection(ctx, models.Collections.User)
@@ -48,6 +55,7 @@ func (p *provider) AddUser(ctx context.Context, user models.User) (models.User,
// UpdateUser to update user information in database
func (p *provider) UpdateUser(ctx context.Context, user models.User) (models.User, error) {
user.UpdatedAt = time.Now().Unix()
collection, _ := p.db.Collection(ctx, models.Collections.User)
meta, err := collection.UpdateDocument(ctx, user.Key, user)
if err != nil {
@@ -211,3 +219,34 @@ func (p *provider) UpdateUsers(ctx context.Context, data map[string]interface{},
return nil
}
// GetUserByPhoneNumber to get user information from database using phone number
func (p *provider) GetUserByPhoneNumber(ctx context.Context, phoneNumber string) (*models.User, error) {
var user models.User
query := fmt.Sprintf("FOR d in %s FILTER d.phone_number == @phone_number RETURN d", models.Collections.User)
bindVars := map[string]interface{}{
"phone_number": phoneNumber,
}
cursor, err := p.db.Query(ctx, query, bindVars)
if err != nil {
return nil, err
}
defer cursor.Close()
for {
if !cursor.HasMore() {
if user.Key == "" {
return nil, fmt.Errorf("user not found")
}
break
}
_, err := cursor.ReadDocument(ctx, &user)
if err != nil {
return nil, err
}
}
return &user, nil
}

View File

@@ -161,6 +161,12 @@ func NewProvider() (*provider, error) {
if err != nil {
return nil, err
}
userPhoneNumberIndexQuery := fmt.Sprintf("CREATE INDEX IF NOT EXISTS authorizer_user_phone_number ON %s.%s (phone_number)", KeySpace, models.Collections.User)
err = session.Query(userPhoneNumberIndexQuery).Exec()
if err != nil {
return nil, err
}
// add is_multi_factor_auth_enabled on users table
userTableAlterQuery := fmt.Sprintf(`ALTER TABLE %s.%s ADD is_multi_factor_auth_enabled boolean`, KeySpace, models.Collections.User)
err = session.Query(userTableAlterQuery).Exec()

View File

@@ -12,6 +12,7 @@ import (
"github.com/authorizerdev/authorizer/server/db/models"
"github.com/authorizerdev/authorizer/server/graph/model"
"github.com/authorizerdev/authorizer/server/memorystore"
"github.com/authorizerdev/authorizer/server/refs"
"github.com/gocql/gocql"
"github.com/google/uuid"
)
@@ -30,6 +31,12 @@ func (p *provider) AddUser(ctx context.Context, user models.User) (models.User,
user.Roles = defaultRoles
}
if user.PhoneNumber != nil && strings.TrimSpace(refs.StringValue(user.PhoneNumber)) != "" {
if u, _ := p.GetUserByPhoneNumber(ctx, refs.StringValue(user.PhoneNumber)); u != nil && u.ID != user.ID {
return user, fmt.Errorf("user with given phone number already exists")
}
}
user.CreatedAt = time.Now().Unix()
user.UpdatedAt = time.Now().Unix()
@@ -299,3 +306,14 @@ func (p *provider) UpdateUsers(ctx context.Context, data map[string]interface{},
return nil
}
// GetUserByPhoneNumber to get user information from database using phone number
func (p *provider) GetUserByPhoneNumber(ctx context.Context, phoneNumber string) (*models.User, error) {
var user models.User
query := fmt.Sprintf("SELECT id, email, email_verified_at, password, signup_methods, given_name, family_name, middle_name, nickname, birthdate, phone_number, phone_number_verified_at, picture, roles, revoked_timestamp, is_multi_factor_auth_enabled, created_at, updated_at FROM %s WHERE phone_number = '%s' LIMIT 1 ALLOW FILTERING", KeySpace+"."+models.Collections.User, phoneNumber)
err := p.db.Query(query).Consistency(gocql.One).Scan(&user.ID, &user.Email, &user.EmailVerifiedAt, &user.Password, &user.SignupMethods, &user.GivenName, &user.FamilyName, &user.MiddleName, &user.Nickname, &user.Birthdate, &user.PhoneNumber, &user.PhoneNumberVerifiedAt, &user.Picture, &user.Roles, &user.RevokedTimestamp, &user.IsMultiFactorAuthEnabled, &user.CreatedAt, &user.UpdatedAt)
if err != nil {
return nil, err
}
return &user, nil
}

View File

@@ -34,7 +34,6 @@ func (p *provider) AddEnv(ctx context.Context, env models.Env) (models.Env, erro
// UpdateEnv to update environment information in database
func (p *provider) UpdateEnv(ctx context.Context, env models.Env) (models.Env, error) {
collection := p.db.Table(models.Collections.Env)
env.UpdatedAt = time.Now().Unix()

View File

@@ -3,12 +3,15 @@ package dynamodb
import (
"context"
"errors"
"fmt"
"strings"
"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"
"github.com/authorizerdev/authorizer/server/refs"
"github.com/google/uuid"
"github.com/guregu/dynamo"
log "github.com/sirupsen/logrus"
@@ -30,6 +33,12 @@ func (p *provider) AddUser(ctx context.Context, user models.User) (models.User,
user.Roles = defaultRoles
}
if user.PhoneNumber != nil && strings.TrimSpace(refs.StringValue(user.PhoneNumber)) != "" {
if u, _ := p.GetUserByPhoneNumber(ctx, refs.StringValue(user.PhoneNumber)); u != nil {
return user, fmt.Errorf("user with given phone number already exists")
}
}
user.CreatedAt = time.Now().Unix()
user.UpdatedAt = time.Now().Unix()
@@ -193,3 +202,23 @@ func (p *provider) UpdateUsers(ctx context.Context, data map[string]interface{},
}
return nil
}
// GetUserByPhoneNumber to get user information from database using phone number
func (p *provider) GetUserByPhoneNumber(ctx context.Context, phoneNumber string) (*models.User, error) {
var users []models.User
var user models.User
collection := p.db.Table(models.Collections.User)
err := collection.Scan().Filter("'phone_number' = ?", phoneNumber).AllWithContext(ctx, &users)
if err != nil {
return nil, err
}
if len(users) > 0 {
user = users[0]
return &user, nil
} else {
return nil, errors.New("no record found")
}
}

View File

@@ -155,3 +155,16 @@ func (p *provider) UpdateUsers(ctx context.Context, data map[string]interface{},
}
return nil
}
// GetUserByPhoneNumber to get user information from database using phone number
func (p *provider) GetUserByPhoneNumber(ctx context.Context, phoneNumber string) (*models.User, error) {
var user models.User
userCollection := p.db.Collection(models.Collections.User, options.Collection())
err := userCollection.FindOne(ctx, bson.M{"phone_number": phoneNumber}).Decode(&user)
if err != nil {
return nil, err
}
return &user, nil
}

View File

@@ -69,3 +69,10 @@ func (p *provider) UpdateUsers(ctx context.Context, data map[string]interface{},
return nil
}
// GetUserByPhoneNumber to get user information from database using phone number
func (p *provider) GetUserByPhoneNumber(ctx context.Context, phoneNumber string) (*models.User, error) {
var user *models.User
return user, nil
}

View File

@@ -18,6 +18,8 @@ type Provider interface {
ListUsers(ctx context.Context, pagination model.Pagination) (*model.Users, error)
// GetUserByEmail to get user information from database using email address
GetUserByEmail(ctx context.Context, email string) (models.User, error)
// GetUserByPhoneNumber to get user information from database using phone number
GetUserByPhoneNumber(ctx context.Context, phoneNumber string) (*models.User, error)
// GetUserByID to get user information from database using user ID
GetUserByID(ctx context.Context, id string) (models.User, error)
// UpdateUsers to update multiple users, with parameters of user IDs slice

View File

@@ -1,15 +1,13 @@
package sql
import (
"fmt"
"log"
"os"
"time"
"github.com/authorizerdev/authorizer/server/constants"
"github.com/authorizerdev/authorizer/server/db/models"
"github.com/authorizerdev/authorizer/server/memorystore"
"github.com/glebarez/sqlite"
"github.com/sirupsen/logrus"
"gorm.io/driver/mysql"
"gorm.io/driver/postgres"
"gorm.io/driver/sqlserver"
@@ -37,10 +35,10 @@ func NewProvider() (*provider, error) {
var sqlDB *gorm.DB
var err error
customLogger := logger.New(
log.New(os.Stdout, "\r\n", log.LstdFlags), // io writer
logrus.StandardLogger(),
logger.Config{
SlowThreshold: time.Second, // Slow SQL threshold
LogLevel: logger.Silent, // Log level
LogLevel: logger.Error, // Log level
IgnoreRecordNotFoundError: true, // Ignore ErrRecordNotFound error for logger
Colorful: false, // Disable color
},
@@ -72,35 +70,43 @@ func NewProvider() (*provider, error) {
return nil, err
}
// For sqlserver, handle uniqueness of phone_number manually via extra db call
// during create and update mutation.
if sqlDB.Migrator().HasConstraint(&models.User{}, "authorizer_users_phone_number_key") {
err = sqlDB.Migrator().DropConstraint(&models.User{}, "authorizer_users_phone_number_key")
logrus.Debug("Failed to drop phone number constraint:", err)
}
err = sqlDB.AutoMigrate(&models.User{}, &models.VerificationRequest{}, &models.Session{}, &models.Env{}, &models.Webhook{}, models.WebhookLog{}, models.EmailTemplate{}, &models.OTP{})
if err != nil {
return nil, err
}
// IMPACT: Request user to manually delete: UQ_phone_number constraint
// unique constraint on phone number does not work with multiple null values for sqlserver
// for more information check https://stackoverflow.com/a/767702
if dbType == constants.DbTypeSqlserver {
var indexInfos []indexInfo
// remove index on phone number if present with different name
res := sqlDB.Raw("SELECT i.name AS index_name, i.type_desc AS index_algorithm, CASE i.is_unique WHEN 1 THEN 'TRUE' ELSE 'FALSE' END AS is_unique, ac.Name AS column_name FROM sys.tables AS t INNER JOIN sys.indexes AS i ON t.object_id = i.object_id INNER JOIN sys.index_columns AS ic ON ic.object_id = i.object_id AND ic.index_id = i.index_id INNER JOIN sys.all_columns AS ac ON ic.object_id = ac.object_id AND ic.column_id = ac.column_id WHERE t.name = 'authorizer_users' AND SCHEMA_NAME(t.schema_id) = 'dbo';").Scan(&indexInfos)
if res.Error != nil {
return nil, res.Error
}
// if dbType == constants.DbTypeSqlserver {
// var indexInfos []indexInfo
// // remove index on phone number if present with different name
// res := sqlDB.Raw("SELECT i.name AS index_name, i.type_desc AS index_algorithm, CASE i.is_unique WHEN 1 THEN 'TRUE' ELSE 'FALSE' END AS is_unique, ac.Name AS column_name FROM sys.tables AS t INNER JOIN sys.indexes AS i ON t.object_id = i.object_id INNER JOIN sys.index_columns AS ic ON ic.object_id = i.object_id AND ic.index_id = i.index_id INNER JOIN sys.all_columns AS ac ON ic.object_id = ac.object_id AND ic.column_id = ac.column_id WHERE t.name = 'authorizer_users' AND SCHEMA_NAME(t.schema_id) = 'dbo';").Scan(&indexInfos)
// if res.Error != nil {
// return nil, res.Error
// }
for _, val := range indexInfos {
if val.ColumnName == phoneNumberColumnName && val.IndexName != phoneNumberIndexName {
// drop index & create new
if res := sqlDB.Exec(fmt.Sprintf(`ALTER TABLE authorizer_users DROP CONSTRAINT "%s";`, val.IndexName)); res.Error != nil {
return nil, res.Error
}
// for _, val := range indexInfos {
// if val.ColumnName == phoneNumberColumnName && val.IndexName != phoneNumberIndexName {
// // drop index & create new
// if res := sqlDB.Exec(fmt.Sprintf(`ALTER TABLE authorizer_users DROP CONSTRAINT "%s";`, val.IndexName)); res.Error != nil {
// return nil, res.Error
// }
// create index
if res := sqlDB.Exec(fmt.Sprintf("CREATE UNIQUE NONCLUSTERED INDEX %s ON authorizer_users(phone_number) WHERE phone_number IS NOT NULL;", phoneNumberIndexName)); res.Error != nil {
return nil, res.Error
}
}
}
}
// // create index
// if res := sqlDB.Exec(fmt.Sprintf("CREATE UNIQUE NONCLUSTERED INDEX %s ON authorizer_users(phone_number) WHERE phone_number IS NOT NULL;", phoneNumberIndexName)); res.Error != nil {
// return nil, res.Error
// }
// }
// }
// }
return &provider{
db: sqlDB,

View File

@@ -2,12 +2,15 @@ package sql
import (
"context"
"fmt"
"strings"
"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"
"github.com/authorizerdev/authorizer/server/refs"
"github.com/google/uuid"
"gorm.io/gorm"
"gorm.io/gorm/clause"
@@ -27,6 +30,12 @@ func (p *provider) AddUser(ctx context.Context, user models.User) (models.User,
user.Roles = defaultRoles
}
if user.PhoneNumber != nil && strings.TrimSpace(refs.StringValue(user.PhoneNumber)) != "" {
if u, _ := p.GetUserByPhoneNumber(ctx, refs.StringValue(user.PhoneNumber)); u != nil {
return user, fmt.Errorf("user with given phone number already exists")
}
}
user.CreatedAt = time.Now().Unix()
user.UpdatedAt = time.Now().Unix()
user.Key = user.ID
@@ -58,13 +67,12 @@ func (p *provider) UpdateUser(ctx context.Context, user models.User) (models.Use
// DeleteUser to delete user information from database
func (p *provider) DeleteUser(ctx context.Context, user models.User) error {
result := p.db.Delete(&user)
result := p.db.Where("user_id = ?", user.ID).Delete(&models.Session{})
if result.Error != nil {
return result.Error
}
result = p.db.Where("user_id = ?", user.ID).Delete(&models.Session{})
result = p.db.Delete(&user)
if result.Error != nil {
return result.Error
}
@@ -141,3 +149,15 @@ func (p *provider) UpdateUsers(ctx context.Context, data map[string]interface{},
}
return nil
}
// GetUserByPhoneNumber to get user information from database using phone number
func (p *provider) GetUserByPhoneNumber(ctx context.Context, phoneNumber string) (*models.User, error) {
var user *models.User
result := p.db.Where("phone_number = ?", phoneNumber).First(&user)
if result.Error != nil {
return nil, result.Error
}
return user, nil
}

16
server/env/env.go vendored
View File

@@ -86,6 +86,7 @@ func InitAllEnv() error {
osAppCookieSecure := os.Getenv(constants.EnvKeyAppCookieSecure)
osAdminCookieSecure := os.Getenv(constants.EnvKeyAdminCookieSecure)
osDisableBasicAuthentication := os.Getenv(constants.EnvKeyDisableBasicAuthentication)
osDisableMobileBasicAuthentication := os.Getenv(constants.AuthRecipeMethodMobileBasicAuth)
osDisableEmailVerification := os.Getenv(constants.EnvKeyDisableEmailVerification)
osDisableMagicLinkLogin := os.Getenv(constants.EnvKeyDisableMagicLinkLogin)
osDisableLoginPage := os.Getenv(constants.EnvKeyDisableLoginPage)
@@ -332,7 +333,7 @@ func InitAllEnv() error {
envData[constants.EnvKeyJwtRoleClaim] = osJwtRoleClaim
if envData[constants.EnvKeyJwtRoleClaim] == "" {
envData[constants.EnvKeyJwtRoleClaim] = "role"
envData[constants.EnvKeyJwtRoleClaim] = "roles"
}
}
if osJwtRoleClaim != "" && envData[constants.EnvKeyJwtRoleClaim] != osJwtRoleClaim {
@@ -498,6 +499,19 @@ func InitAllEnv() error {
}
}
if _, ok := envData[constants.EnvKeyDisableMobileBasicAuthentication]; !ok {
envData[constants.EnvKeyDisableMobileBasicAuthentication] = osDisableBasicAuthentication == "true"
}
if osDisableMobileBasicAuthentication != "" {
boolValue, err := strconv.ParseBool(osDisableMobileBasicAuthentication)
if err != nil {
return err
}
if boolValue != envData[constants.EnvKeyDisableMobileBasicAuthentication].(bool) {
envData[constants.EnvKeyDisableMobileBasicAuthentication] = boolValue
}
}
if _, ok := envData[constants.EnvKeyDisableEmailVerification]; !ok {
envData[constants.EnvKeyDisableEmailVerification] = osDisableEmailVerification == "true"
}

View File

@@ -75,7 +75,6 @@ func GetEnvData() (map[string]interface{}, error) {
}
memorystore.Provider.UpdateEnvVariable(constants.EnvKeyEncryptionKey, decryptedEncryptionKey)
b64DecryptedConfig, err := crypto.DecryptB64(env.EnvData)
if err != nil {
log.Debug("Error while decrypting env data from B64: ", err)
@@ -201,7 +200,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, constants.EnvKeyDisableStrongPassword, constants.EnvKeyIsEmailServiceEnabled, constants.EnvKeyEnforceMultiFactorAuthentication, constants.EnvKeyDisableMultiFactorAuthentication, constants.EnvKeyAdminCookieSecure, constants.EnvKeyAppCookieSecure:
case constants.EnvKeyIsProd, constants.EnvKeyDisableBasicAuthentication, constants.EnvKeyDisableMobileBasicAuthentication, constants.EnvKeyDisableEmailVerification, constants.EnvKeyDisableLoginPage, constants.EnvKeyDisableMagicLinkLogin, constants.EnvKeyDisableSignUp, constants.EnvKeyDisableRedisForEnv, constants.EnvKeyDisableStrongPassword, constants.EnvKeyIsEmailServiceEnabled, constants.EnvKeyEnforceMultiFactorAuthentication, constants.EnvKeyDisableMultiFactorAuthentication, constants.EnvKeyAdminCookieSecure, constants.EnvKeyAppCookieSecure:
if envValueBool, err := strconv.ParseBool(envValue); err == nil {
if value.(bool) != envValueBool {
storeData[key] = envValueBool

View File

@@ -25,10 +25,8 @@ require (
github.com/stretchr/testify v1.8.0
github.com/vektah/gqlparser/v2 v2.5.1
go.mongodb.org/mongo-driver v1.8.1
golang.org/x/crypto v0.0.0-20221005025214-4161e89ecf1b
golang.org/x/net v0.0.0-20220930213112-107f3e3c3b0b // indirect
golang.org/x/crypto v0.3.0
golang.org/x/oauth2 v0.0.0-20210628180205-a41e5a781914
golang.org/x/sys v0.0.0-20220928140112-f11e5e49a4ec // indirect
google.golang.org/appengine v1.6.7 // indirect
google.golang.org/protobuf v1.28.1 // indirect
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect

View File

@@ -447,8 +447,9 @@ golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5y
golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/crypto v0.0.0-20220511200225-c6db032c6c88/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/crypto v0.0.0-20221005025214-4161e89ecf1b h1:huxqepDufQpLLIRXiVkTvnxrzJlpwmIWAObmcCcUFr0=
golang.org/x/crypto v0.0.0-20221005025214-4161e89ecf1b/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/crypto v0.3.0 h1:a06MkbcxBrEFc0w0QIZWXrH/9cCX6KJyWbBOIwAn+7A=
golang.org/x/crypto v0.3.0/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
@@ -522,8 +523,8 @@ golang.org/x/net v0.0.0-20211216030914-fe4d6282115f/go.mod h1:9nx3DQGgdP8bBQD5qx
golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.0.0-20220930213112-107f3e3c3b0b h1:uKO3Js8lXGjpjdc4J3rqs0/Ex5yDKUGfk43tTYWVLas=
golang.org/x/net v0.0.0-20220930213112-107f3e3c3b0b/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk=
golang.org/x/net v0.2.0 h1:sZfSu1wtKLGlWI4ZZayP0ck9Y73K1ynO6gqzTdBVdPU=
golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
@@ -594,14 +595,13 @@ golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20220224120231-95c6836cb0e7/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220928140112-f11e5e49a4ec h1:BkDtF2Ih9xZ7le9ndzTA7KJow28VbQW3odyk/8drmuI=
golang.org/x/sys v0.0.0-20220928140112-f11e5e49a4ec/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.2.0 h1:ljd4t30dBnAvMZaQCevtY0xLLD0A+bRZXbgLMLU1F/A=
golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc=
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
@@ -610,8 +610,9 @@ golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.4.0 h1:BrVqGRd7+k1DiOgtnFvAkoQEWQvBc25ouMJM6429SFg=
golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=

View File

@@ -171,6 +171,8 @@ type ComplexityRoot struct {
Login func(childComplexity int, params model.LoginInput) int
Logout func(childComplexity int) int
MagicLinkLogin func(childComplexity int, params model.MagicLinkLoginInput) int
MobileLogin func(childComplexity int, params model.MobileLoginInput) int
MobileSignup func(childComplexity int, params *model.MobileSignUpInput) int
ResendOtp func(childComplexity int, params model.ResendOTPRequest) int
ResendVerifyEmail func(childComplexity int, params model.ResendVerifyEmailInput) int
ResetPassword func(childComplexity int, params model.ResetPasswordInput) int
@@ -201,6 +203,7 @@ type ComplexityRoot struct {
Meta func(childComplexity int) int
Profile func(childComplexity int) int
Session func(childComplexity int, params *model.SessionQueryInput) int
User func(childComplexity int, params model.GetUserRequest) int
Users func(childComplexity int, params *model.PaginatedInput) int
ValidateJwtToken func(childComplexity int, params model.ValidateJWTTokenInput) int
VerificationRequests func(childComplexity int, params *model.PaginatedInput) int
@@ -300,7 +303,9 @@ type ComplexityRoot struct {
type MutationResolver interface {
Signup(ctx context.Context, params model.SignUpInput) (*model.AuthResponse, error)
MobileSignup(ctx context.Context, params *model.MobileSignUpInput) (*model.AuthResponse, error)
Login(ctx context.Context, params model.LoginInput) (*model.AuthResponse, error)
MobileLogin(ctx context.Context, params model.MobileLoginInput) (*model.AuthResponse, error)
MagicLinkLogin(ctx context.Context, params model.MagicLinkLoginInput) (*model.Response, error)
Logout(ctx context.Context) (*model.Response, error)
UpdateProfile(ctx context.Context, params model.UpdateProfileInput) (*model.Response, error)
@@ -335,6 +340,7 @@ type QueryResolver interface {
Profile(ctx context.Context) (*model.User, error)
ValidateJwtToken(ctx context.Context, params model.ValidateJWTTokenInput) (*model.ValidateJWTTokenResponse, error)
Users(ctx context.Context, params *model.PaginatedInput) (*model.Users, error)
User(ctx context.Context, params model.GetUserRequest) (*model.User, error)
VerificationRequests(ctx context.Context, params *model.PaginatedInput) (*model.VerificationRequests, error)
AdminSession(ctx context.Context) (*model.Response, error)
Env(ctx context.Context) (*model.Env, error)
@@ -1159,6 +1165,30 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in
return e.complexity.Mutation.MagicLinkLogin(childComplexity, args["params"].(model.MagicLinkLoginInput)), true
case "Mutation.mobile_login":
if e.complexity.Mutation.MobileLogin == nil {
break
}
args, err := ec.field_Mutation_mobile_login_args(context.TODO(), rawArgs)
if err != nil {
return 0, false
}
return e.complexity.Mutation.MobileLogin(childComplexity, args["params"].(model.MobileLoginInput)), true
case "Mutation.mobile_signup":
if e.complexity.Mutation.MobileSignup == nil {
break
}
args, err := ec.field_Mutation_mobile_signup_args(context.TODO(), rawArgs)
if err != nil {
return 0, false
}
return e.complexity.Mutation.MobileSignup(childComplexity, args["params"].(*model.MobileSignUpInput)), true
case "Mutation.resend_otp":
if e.complexity.Mutation.ResendOtp == nil {
break
@@ -1407,6 +1437,18 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in
return e.complexity.Query.Session(childComplexity, args["params"].(*model.SessionQueryInput)), true
case "Query._user":
if e.complexity.Query.User == nil {
break
}
args, err := ec.field_Query__user_args(context.TODO(), rawArgs)
if err != nil {
return 0, false
}
return e.complexity.Query.User(childComplexity, args["params"].(model.GetUserRequest)), true
case "Query._users":
if e.complexity.Query.Users == nil {
break
@@ -1880,10 +1922,13 @@ func (e *executableSchema) Exec(ctx context.Context) graphql.ResponseHandler {
ec.unmarshalInputDeleteUserInput,
ec.unmarshalInputForgotPasswordInput,
ec.unmarshalInputGenerateJWTKeysInput,
ec.unmarshalInputGetUserRequest,
ec.unmarshalInputInviteMemberInput,
ec.unmarshalInputListWebhookLogRequest,
ec.unmarshalInputLoginInput,
ec.unmarshalInputMagicLinkLoginInput,
ec.unmarshalInputMobileLoginInput,
ec.unmarshalInputMobileSignUpInput,
ec.unmarshalInputOAuthRevokeInput,
ec.unmarshalInputPaginatedInput,
ec.unmarshalInputPaginationInput,
@@ -2234,6 +2279,28 @@ input AdminSignupInput {
admin_secret: String!
}
input MobileSignUpInput {
email: String
given_name: String
family_name: String
middle_name: String
nickname: String
gender: String
birthdate: String
phone_number: String!
picture: String
password: String!
confirm_password: String!
roles: [String!]
scope: [String!]
redirect_uri: String
is_multi_factor_auth_enabled: Boolean
# state is used for authorization code grant flow
# it is used to get code for an on-going auth process during login
# and use that code for setting ` + "`" + `c_hash` + "`" + ` in id_token
state: String
}
input SignUpInput {
email: String!
given_name: String
@@ -2267,6 +2334,17 @@ input LoginInput {
state: String
}
input MobileLoginInput {
phone_number: String!
password: String!
roles: [String!]
scope: [String!]
# state is used for authorization code grant flow
# it is used to get code for an on-going auth process during login
# and use that code for setting ` + "`" + `c_hash` + "`" + ` in id_token
state: String
}
input VerifyEmailInput {
token: String!
# state is used for authorization code grant flow
@@ -2411,6 +2489,8 @@ input AddEmailTemplateRequest {
event_name: String!
subject: String!
template: String!
# Design value is set when editor is used
# If raw HTML is used design value is set to null
design: String
}
@@ -2419,6 +2499,8 @@ input UpdateEmailTemplateRequest {
event_name: String
template: String
subject: String
# Design value is set when editor is used
# If raw HTML is used design value is set to null
design: String
}
@@ -2443,9 +2525,15 @@ input ResendOTPRequest {
state: String
}
input GetUserRequest {
id: String!
}
type Mutation {
signup(params: SignUpInput!): AuthResponse!
mobile_signup(params: MobileSignUpInput): AuthResponse!
login(params: LoginInput!): AuthResponse!
mobile_login(params: MobileLoginInput!): AuthResponse!
magic_link_login(params: MagicLinkLoginInput!): Response!
logout: Response!
update_profile(params: UpdateProfileInput!): Response!
@@ -2483,6 +2571,7 @@ type Query {
validate_jwt_token(params: ValidateJWTTokenInput!): ValidateJWTTokenResponse!
# admin only apis
_users(params: PaginatedInput): Users!
_user(params: GetUserRequest!): User!
_verification_requests(params: PaginatedInput): VerificationRequests!
_admin_session: Response!
_env: Env!
@@ -2784,6 +2873,36 @@ func (ec *executionContext) field_Mutation_magic_link_login_args(ctx context.Con
return args, nil
}
func (ec *executionContext) field_Mutation_mobile_login_args(ctx context.Context, rawArgs map[string]interface{}) (map[string]interface{}, error) {
var err error
args := map[string]interface{}{}
var arg0 model.MobileLoginInput
if tmp, ok := rawArgs["params"]; ok {
ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("params"))
arg0, err = ec.unmarshalNMobileLoginInput2githubᚗcomᚋauthorizerdevᚋauthorizerᚋserverᚋgraphᚋmodelᚐMobileLoginInput(ctx, tmp)
if err != nil {
return nil, err
}
}
args["params"] = arg0
return args, nil
}
func (ec *executionContext) field_Mutation_mobile_signup_args(ctx context.Context, rawArgs map[string]interface{}) (map[string]interface{}, error) {
var err error
args := map[string]interface{}{}
var arg0 *model.MobileSignUpInput
if tmp, ok := rawArgs["params"]; ok {
ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("params"))
arg0, err = ec.unmarshalOMobileSignUpInput2ᚖgithubᚗcomᚋauthorizerdevᚋauthorizerᚋserverᚋgraphᚋmodelᚐMobileSignUpInput(ctx, tmp)
if err != nil {
return nil, err
}
}
args["params"] = arg0
return args, nil
}
func (ec *executionContext) field_Mutation_resend_otp_args(ctx context.Context, rawArgs map[string]interface{}) (map[string]interface{}, error) {
var err error
args := map[string]interface{}{}
@@ -2934,6 +3053,21 @@ func (ec *executionContext) field_Query__email_templates_args(ctx context.Contex
return args, nil
}
func (ec *executionContext) field_Query__user_args(ctx context.Context, rawArgs map[string]interface{}) (map[string]interface{}, error) {
var err error
args := map[string]interface{}{}
var arg0 model.GetUserRequest
if tmp, ok := rawArgs["params"]; ok {
ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("params"))
arg0, err = ec.unmarshalNGetUserRequest2githubᚗcomᚋauthorizerdevᚋauthorizerᚋserverᚋgraphᚋmodelᚐGetUserRequest(ctx, tmp)
if err != nil {
return nil, err
}
}
args["params"] = arg0
return args, nil
}
func (ec *executionContext) field_Query__users_args(ctx context.Context, rawArgs map[string]interface{}) (map[string]interface{}, error) {
var err error
args := map[string]interface{}{}
@@ -7015,6 +7149,77 @@ func (ec *executionContext) fieldContext_Mutation_signup(ctx context.Context, fi
return fc, nil
}
func (ec *executionContext) _Mutation_mobile_signup(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) {
fc, err := ec.fieldContext_Mutation_mobile_signup(ctx, field)
if err != nil {
return graphql.Null
}
ctx = graphql.WithFieldContext(ctx, fc)
defer func() {
if r := recover(); r != nil {
ec.Error(ctx, ec.Recover(ctx, r))
ret = graphql.Null
}
}()
resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) {
ctx = rctx // use context from middleware stack in children
return ec.resolvers.Mutation().MobileSignup(rctx, fc.Args["params"].(*model.MobileSignUpInput))
})
if err != nil {
ec.Error(ctx, err)
return graphql.Null
}
if resTmp == nil {
if !graphql.HasFieldError(ctx, fc) {
ec.Errorf(ctx, "must not be null")
}
return graphql.Null
}
res := resTmp.(*model.AuthResponse)
fc.Result = res
return ec.marshalNAuthResponse2ᚖgithubᚗcomᚋauthorizerdevᚋauthorizerᚋserverᚋgraphᚋmodelᚐAuthResponse(ctx, field.Selections, res)
}
func (ec *executionContext) fieldContext_Mutation_mobile_signup(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) {
fc = &graphql.FieldContext{
Object: "Mutation",
Field: field,
IsMethod: true,
IsResolver: true,
Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) {
switch field.Name {
case "message":
return ec.fieldContext_AuthResponse_message(ctx, field)
case "should_show_otp_screen":
return ec.fieldContext_AuthResponse_should_show_otp_screen(ctx, field)
case "access_token":
return ec.fieldContext_AuthResponse_access_token(ctx, field)
case "id_token":
return ec.fieldContext_AuthResponse_id_token(ctx, field)
case "refresh_token":
return ec.fieldContext_AuthResponse_refresh_token(ctx, field)
case "expires_in":
return ec.fieldContext_AuthResponse_expires_in(ctx, field)
case "user":
return ec.fieldContext_AuthResponse_user(ctx, field)
}
return nil, fmt.Errorf("no field named %q was found under type AuthResponse", field.Name)
},
}
defer func() {
if r := recover(); r != nil {
err = ec.Recover(ctx, r)
ec.Error(ctx, err)
}
}()
ctx = graphql.WithFieldContext(ctx, fc)
if fc.Args, err = ec.field_Mutation_mobile_signup_args(ctx, field.ArgumentMap(ec.Variables)); err != nil {
ec.Error(ctx, err)
return
}
return fc, nil
}
func (ec *executionContext) _Mutation_login(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) {
fc, err := ec.fieldContext_Mutation_login(ctx, field)
if err != nil {
@@ -7086,6 +7291,77 @@ func (ec *executionContext) fieldContext_Mutation_login(ctx context.Context, fie
return fc, nil
}
func (ec *executionContext) _Mutation_mobile_login(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) {
fc, err := ec.fieldContext_Mutation_mobile_login(ctx, field)
if err != nil {
return graphql.Null
}
ctx = graphql.WithFieldContext(ctx, fc)
defer func() {
if r := recover(); r != nil {
ec.Error(ctx, ec.Recover(ctx, r))
ret = graphql.Null
}
}()
resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) {
ctx = rctx // use context from middleware stack in children
return ec.resolvers.Mutation().MobileLogin(rctx, fc.Args["params"].(model.MobileLoginInput))
})
if err != nil {
ec.Error(ctx, err)
return graphql.Null
}
if resTmp == nil {
if !graphql.HasFieldError(ctx, fc) {
ec.Errorf(ctx, "must not be null")
}
return graphql.Null
}
res := resTmp.(*model.AuthResponse)
fc.Result = res
return ec.marshalNAuthResponse2ᚖgithubᚗcomᚋauthorizerdevᚋauthorizerᚋserverᚋgraphᚋmodelᚐAuthResponse(ctx, field.Selections, res)
}
func (ec *executionContext) fieldContext_Mutation_mobile_login(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) {
fc = &graphql.FieldContext{
Object: "Mutation",
Field: field,
IsMethod: true,
IsResolver: true,
Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) {
switch field.Name {
case "message":
return ec.fieldContext_AuthResponse_message(ctx, field)
case "should_show_otp_screen":
return ec.fieldContext_AuthResponse_should_show_otp_screen(ctx, field)
case "access_token":
return ec.fieldContext_AuthResponse_access_token(ctx, field)
case "id_token":
return ec.fieldContext_AuthResponse_id_token(ctx, field)
case "refresh_token":
return ec.fieldContext_AuthResponse_refresh_token(ctx, field)
case "expires_in":
return ec.fieldContext_AuthResponse_expires_in(ctx, field)
case "user":
return ec.fieldContext_AuthResponse_user(ctx, field)
}
return nil, fmt.Errorf("no field named %q was found under type AuthResponse", field.Name)
},
}
defer func() {
if r := recover(); r != nil {
err = ec.Recover(ctx, r)
ec.Error(ctx, err)
}
}()
ctx = graphql.WithFieldContext(ctx, fc)
if fc.Args, err = ec.field_Mutation_mobile_login_args(ctx, field.ArgumentMap(ec.Variables)); err != nil {
ec.Error(ctx, err)
return
}
return fc, nil
}
func (ec *executionContext) _Mutation_magic_link_login(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) {
fc, err := ec.fieldContext_Mutation_magic_link_login(ctx, field)
if err != nil {
@@ -9250,6 +9526,101 @@ func (ec *executionContext) fieldContext_Query__users(ctx context.Context, field
return fc, nil
}
func (ec *executionContext) _Query__user(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) {
fc, err := ec.fieldContext_Query__user(ctx, field)
if err != nil {
return graphql.Null
}
ctx = graphql.WithFieldContext(ctx, fc)
defer func() {
if r := recover(); r != nil {
ec.Error(ctx, ec.Recover(ctx, r))
ret = graphql.Null
}
}()
resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) {
ctx = rctx // use context from middleware stack in children
return ec.resolvers.Query().User(rctx, fc.Args["params"].(model.GetUserRequest))
})
if err != nil {
ec.Error(ctx, err)
return graphql.Null
}
if resTmp == nil {
if !graphql.HasFieldError(ctx, fc) {
ec.Errorf(ctx, "must not be null")
}
return graphql.Null
}
res := resTmp.(*model.User)
fc.Result = res
return ec.marshalNUser2ᚖgithubᚗcomᚋauthorizerdevᚋauthorizerᚋserverᚋgraphᚋmodelᚐUser(ctx, field.Selections, res)
}
func (ec *executionContext) fieldContext_Query__user(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) {
fc = &graphql.FieldContext{
Object: "Query",
Field: field,
IsMethod: true,
IsResolver: true,
Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) {
switch field.Name {
case "id":
return ec.fieldContext_User_id(ctx, field)
case "email":
return ec.fieldContext_User_email(ctx, field)
case "email_verified":
return ec.fieldContext_User_email_verified(ctx, field)
case "signup_methods":
return ec.fieldContext_User_signup_methods(ctx, field)
case "given_name":
return ec.fieldContext_User_given_name(ctx, field)
case "family_name":
return ec.fieldContext_User_family_name(ctx, field)
case "middle_name":
return ec.fieldContext_User_middle_name(ctx, field)
case "nickname":
return ec.fieldContext_User_nickname(ctx, field)
case "preferred_username":
return ec.fieldContext_User_preferred_username(ctx, field)
case "gender":
return ec.fieldContext_User_gender(ctx, field)
case "birthdate":
return ec.fieldContext_User_birthdate(ctx, field)
case "phone_number":
return ec.fieldContext_User_phone_number(ctx, field)
case "phone_number_verified":
return ec.fieldContext_User_phone_number_verified(ctx, field)
case "picture":
return ec.fieldContext_User_picture(ctx, field)
case "roles":
return ec.fieldContext_User_roles(ctx, field)
case "created_at":
return ec.fieldContext_User_created_at(ctx, field)
case "updated_at":
return ec.fieldContext_User_updated_at(ctx, field)
case "revoked_timestamp":
return ec.fieldContext_User_revoked_timestamp(ctx, field)
case "is_multi_factor_auth_enabled":
return ec.fieldContext_User_is_multi_factor_auth_enabled(ctx, field)
}
return nil, fmt.Errorf("no field named %q was found under type User", field.Name)
},
}
defer func() {
if r := recover(); r != nil {
err = ec.Recover(ctx, r)
ec.Error(ctx, err)
}
}()
ctx = graphql.WithFieldContext(ctx, fc)
if fc.Args, err = ec.field_Query__user_args(ctx, field.ArgumentMap(ec.Variables)); err != nil {
ec.Error(ctx, err)
return
}
return fc, nil
}
func (ec *executionContext) _Query__verification_requests(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) {
fc, err := ec.fieldContext_Query__verification_requests(ctx, field)
if err != nil {
@@ -14400,6 +14771,34 @@ func (ec *executionContext) unmarshalInputGenerateJWTKeysInput(ctx context.Conte
return it, nil
}
func (ec *executionContext) unmarshalInputGetUserRequest(ctx context.Context, obj interface{}) (model.GetUserRequest, error) {
var it model.GetUserRequest
asMap := map[string]interface{}{}
for k, v := range obj.(map[string]interface{}) {
asMap[k] = v
}
fieldsInOrder := [...]string{"id"}
for _, k := range fieldsInOrder {
v, ok := asMap[k]
if !ok {
continue
}
switch k {
case "id":
var err error
ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("id"))
it.ID, err = ec.unmarshalNString2string(ctx, v)
if err != nil {
return it, err
}
}
}
return it, nil
}
func (ec *executionContext) unmarshalInputInviteMemberInput(ctx context.Context, obj interface{}) (model.InviteMemberInput, error) {
var it model.InviteMemberInput
asMap := map[string]interface{}{}
@@ -14592,6 +14991,214 @@ func (ec *executionContext) unmarshalInputMagicLinkLoginInput(ctx context.Contex
return it, nil
}
func (ec *executionContext) unmarshalInputMobileLoginInput(ctx context.Context, obj interface{}) (model.MobileLoginInput, error) {
var it model.MobileLoginInput
asMap := map[string]interface{}{}
for k, v := range obj.(map[string]interface{}) {
asMap[k] = v
}
fieldsInOrder := [...]string{"phone_number", "password", "roles", "scope", "state"}
for _, k := range fieldsInOrder {
v, ok := asMap[k]
if !ok {
continue
}
switch k {
case "phone_number":
var err error
ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("phone_number"))
it.PhoneNumber, err = ec.unmarshalNString2string(ctx, v)
if err != nil {
return it, err
}
case "password":
var err error
ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("password"))
it.Password, err = ec.unmarshalNString2string(ctx, v)
if err != nil {
return it, err
}
case "roles":
var err error
ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("roles"))
it.Roles, err = ec.unmarshalOString2ᚕstringᚄ(ctx, v)
if err != nil {
return it, err
}
case "scope":
var err error
ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("scope"))
it.Scope, err = ec.unmarshalOString2ᚕstringᚄ(ctx, v)
if err != nil {
return it, err
}
case "state":
var err error
ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("state"))
it.State, err = ec.unmarshalOString2ᚖstring(ctx, v)
if err != nil {
return it, err
}
}
}
return it, nil
}
func (ec *executionContext) unmarshalInputMobileSignUpInput(ctx context.Context, obj interface{}) (model.MobileSignUpInput, error) {
var it model.MobileSignUpInput
asMap := map[string]interface{}{}
for k, v := range obj.(map[string]interface{}) {
asMap[k] = v
}
fieldsInOrder := [...]string{"email", "given_name", "family_name", "middle_name", "nickname", "gender", "birthdate", "phone_number", "picture", "password", "confirm_password", "roles", "scope", "redirect_uri", "is_multi_factor_auth_enabled", "state"}
for _, k := range fieldsInOrder {
v, ok := asMap[k]
if !ok {
continue
}
switch k {
case "email":
var err error
ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("email"))
it.Email, err = ec.unmarshalOString2ᚖstring(ctx, v)
if err != nil {
return it, err
}
case "given_name":
var err error
ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("given_name"))
it.GivenName, err = ec.unmarshalOString2ᚖstring(ctx, v)
if err != nil {
return it, err
}
case "family_name":
var err error
ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("family_name"))
it.FamilyName, err = ec.unmarshalOString2ᚖstring(ctx, v)
if err != nil {
return it, err
}
case "middle_name":
var err error
ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("middle_name"))
it.MiddleName, err = ec.unmarshalOString2ᚖstring(ctx, v)
if err != nil {
return it, err
}
case "nickname":
var err error
ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("nickname"))
it.Nickname, err = ec.unmarshalOString2ᚖstring(ctx, v)
if err != nil {
return it, err
}
case "gender":
var err error
ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("gender"))
it.Gender, err = ec.unmarshalOString2ᚖstring(ctx, v)
if err != nil {
return it, err
}
case "birthdate":
var err error
ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("birthdate"))
it.Birthdate, err = ec.unmarshalOString2ᚖstring(ctx, v)
if err != nil {
return it, err
}
case "phone_number":
var err error
ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("phone_number"))
it.PhoneNumber, err = ec.unmarshalNString2string(ctx, v)
if err != nil {
return it, err
}
case "picture":
var err error
ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("picture"))
it.Picture, err = ec.unmarshalOString2ᚖstring(ctx, v)
if err != nil {
return it, err
}
case "password":
var err error
ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("password"))
it.Password, err = ec.unmarshalNString2string(ctx, v)
if err != nil {
return it, err
}
case "confirm_password":
var err error
ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("confirm_password"))
it.ConfirmPassword, err = ec.unmarshalNString2string(ctx, v)
if err != nil {
return it, err
}
case "roles":
var err error
ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("roles"))
it.Roles, err = ec.unmarshalOString2ᚕstringᚄ(ctx, v)
if err != nil {
return it, err
}
case "scope":
var err error
ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("scope"))
it.Scope, err = ec.unmarshalOString2ᚕstringᚄ(ctx, v)
if err != nil {
return it, err
}
case "redirect_uri":
var err error
ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("redirect_uri"))
it.RedirectURI, err = ec.unmarshalOString2ᚖstring(ctx, v)
if err != nil {
return it, err
}
case "is_multi_factor_auth_enabled":
var err error
ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("is_multi_factor_auth_enabled"))
it.IsMultiFactorAuthEnabled, err = ec.unmarshalOBoolean2ᚖbool(ctx, v)
if err != nil {
return it, err
}
case "state":
var err error
ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("state"))
it.State, err = ec.unmarshalOString2ᚖstring(ctx, v)
if err != nil {
return it, err
}
}
}
return it, nil
}
func (ec *executionContext) unmarshalInputOAuthRevokeInput(ctx context.Context, obj interface{}) (model.OAuthRevokeInput, error) {
var it model.OAuthRevokeInput
asMap := map[string]interface{}{}
@@ -16623,6 +17230,15 @@ func (ec *executionContext) _Mutation(ctx context.Context, sel ast.SelectionSet)
return ec._Mutation_signup(ctx, field)
})
if out.Values[i] == graphql.Null {
invalids++
}
case "mobile_signup":
out.Values[i] = ec.OperationContext.RootResolverMiddleware(innerCtx, func(ctx context.Context) (res graphql.Marshaler) {
return ec._Mutation_mobile_signup(ctx, field)
})
if out.Values[i] == graphql.Null {
invalids++
}
@@ -16632,6 +17248,15 @@ func (ec *executionContext) _Mutation(ctx context.Context, sel ast.SelectionSet)
return ec._Mutation_login(ctx, field)
})
if out.Values[i] == graphql.Null {
invalids++
}
case "mobile_login":
out.Values[i] = ec.OperationContext.RootResolverMiddleware(innerCtx, func(ctx context.Context) (res graphql.Marshaler) {
return ec._Mutation_mobile_login(ctx, field)
})
if out.Values[i] == graphql.Null {
invalids++
}
@@ -17069,6 +17694,29 @@ func (ec *executionContext) _Query(ctx context.Context, sel ast.SelectionSet) gr
return ec.OperationContext.RootResolverMiddleware(ctx, innerFunc)
}
out.Concurrently(i, func() graphql.Marshaler {
return rrm(innerCtx)
})
case "_user":
field := field
innerFunc := func(ctx context.Context) (res graphql.Marshaler) {
defer func() {
if r := recover(); r != nil {
ec.Error(ctx, ec.Recover(ctx, r))
}
}()
res = ec._Query__user(ctx, field)
if res == graphql.Null {
atomic.AddUint32(&invalids, 1)
}
return res
}
rrm := func(ctx context.Context) graphql.Marshaler {
return ec.OperationContext.RootResolverMiddleware(ctx, innerFunc)
}
out.Concurrently(i, func() graphql.Marshaler {
return rrm(innerCtx)
})
@@ -18244,6 +18892,11 @@ func (ec *executionContext) marshalNGenerateJWTKeysResponse2ᚖgithubᚗcomᚋau
return ec._GenerateJWTKeysResponse(ctx, sel, v)
}
func (ec *executionContext) unmarshalNGetUserRequest2githubᚗcomᚋauthorizerdevᚋauthorizerᚋserverᚋgraphᚋmodelᚐGetUserRequest(ctx context.Context, v interface{}) (model.GetUserRequest, error) {
res, err := ec.unmarshalInputGetUserRequest(ctx, v)
return res, graphql.ErrorOnPath(ctx, err)
}
func (ec *executionContext) unmarshalNID2string(ctx context.Context, v interface{}) (string, error) {
res, err := graphql.UnmarshalID(v)
return res, graphql.ErrorOnPath(ctx, err)
@@ -18303,6 +18956,11 @@ func (ec *executionContext) marshalNMeta2ᚖgithubᚗcomᚋauthorizerdevᚋautho
return ec._Meta(ctx, sel, v)
}
func (ec *executionContext) unmarshalNMobileLoginInput2githubᚗcomᚋauthorizerdevᚋauthorizerᚋserverᚋgraphᚋmodelᚐMobileLoginInput(ctx context.Context, v interface{}) (model.MobileLoginInput, error) {
res, err := ec.unmarshalInputMobileLoginInput(ctx, v)
return res, graphql.ErrorOnPath(ctx, err)
}
func (ec *executionContext) unmarshalNOAuthRevokeInput2githubᚗcomᚋauthorizerdevᚋauthorizerᚋserverᚋgraphᚋmodelᚐOAuthRevokeInput(ctx context.Context, v interface{}) (model.OAuthRevokeInput, error) {
res, err := ec.unmarshalInputOAuthRevokeInput(ctx, v)
return res, graphql.ErrorOnPath(ctx, err)
@@ -19097,6 +19755,14 @@ func (ec *executionContext) marshalOMap2map(ctx context.Context, sel ast.Selecti
return res
}
func (ec *executionContext) unmarshalOMobileSignUpInput2ᚖgithubᚗcomᚋauthorizerdevᚋauthorizerᚋserverᚋgraphᚋmodelᚐMobileSignUpInput(ctx context.Context, v interface{}) (*model.MobileSignUpInput, error) {
if v == nil {
return nil, nil
}
res, err := ec.unmarshalInputMobileSignUpInput(ctx, v)
return &res, graphql.ErrorOnPath(ctx, err)
}
func (ec *executionContext) unmarshalOPaginatedInput2ᚖgithubᚗcomᚋauthorizerdevᚋauthorizerᚋserverᚋgraphᚋmodelᚐPaginatedInput(ctx context.Context, v interface{}) (*model.PaginatedInput, error) {
if v == nil {
return nil, nil

View File

@@ -136,6 +136,10 @@ type GenerateJWTKeysResponse struct {
PrivateKey *string `json:"private_key"`
}
type GetUserRequest struct {
ID string `json:"id"`
}
type InviteMemberInput struct {
Emails []string `json:"emails"`
RedirectURI *string `json:"redirect_uri"`
@@ -179,6 +183,33 @@ type Meta struct {
IsMultiFactorAuthEnabled bool `json:"is_multi_factor_auth_enabled"`
}
type MobileLoginInput struct {
PhoneNumber string `json:"phone_number"`
Password string `json:"password"`
Roles []string `json:"roles"`
Scope []string `json:"scope"`
State *string `json:"state"`
}
type MobileSignUpInput struct {
Email *string `json:"email"`
GivenName *string `json:"given_name"`
FamilyName *string `json:"family_name"`
MiddleName *string `json:"middle_name"`
Nickname *string `json:"nickname"`
Gender *string `json:"gender"`
Birthdate *string `json:"birthdate"`
PhoneNumber string `json:"phone_number"`
Picture *string `json:"picture"`
Password string `json:"password"`
ConfirmPassword string `json:"confirm_password"`
Roles []string `json:"roles"`
Scope []string `json:"scope"`
RedirectURI *string `json:"redirect_uri"`
IsMultiFactorAuthEnabled *bool `json:"is_multi_factor_auth_enabled"`
State *string `json:"state"`
}
type OAuthRevokeInput struct {
RefreshToken string `json:"refresh_token"`
}

View File

@@ -269,6 +269,28 @@ input AdminSignupInput {
admin_secret: String!
}
input MobileSignUpInput {
email: String
given_name: String
family_name: String
middle_name: String
nickname: String
gender: String
birthdate: String
phone_number: String!
picture: String
password: String!
confirm_password: String!
roles: [String!]
scope: [String!]
redirect_uri: String
is_multi_factor_auth_enabled: Boolean
# state is used for authorization code grant flow
# it is used to get code for an on-going auth process during login
# and use that code for setting `c_hash` in id_token
state: String
}
input SignUpInput {
email: String!
given_name: String
@@ -302,6 +324,17 @@ input LoginInput {
state: String
}
input MobileLoginInput {
phone_number: String!
password: String!
roles: [String!]
scope: [String!]
# state is used for authorization code grant flow
# it is used to get code for an on-going auth process during login
# and use that code for setting `c_hash` in id_token
state: String
}
input VerifyEmailInput {
token: String!
# state is used for authorization code grant flow
@@ -482,9 +515,15 @@ input ResendOTPRequest {
state: String
}
input GetUserRequest {
id: String!
}
type Mutation {
signup(params: SignUpInput!): AuthResponse!
mobile_signup(params: MobileSignUpInput): AuthResponse!
login(params: LoginInput!): AuthResponse!
mobile_login(params: MobileLoginInput!): AuthResponse!
magic_link_login(params: MagicLinkLoginInput!): Response!
logout: Response!
update_profile(params: UpdateProfileInput!): Response!
@@ -522,6 +561,7 @@ type Query {
validate_jwt_token(params: ValidateJWTTokenInput!): ValidateJWTTokenResponse!
# admin only apis
_users(params: PaginatedInput): Users!
_user(params: GetUserRequest!): User!
_verification_requests(params: PaginatedInput): VerificationRequests!
_admin_session: Response!
_env: Env!

View File

@@ -16,11 +16,21 @@ func (r *mutationResolver) Signup(ctx context.Context, params model.SignUpInput)
return resolvers.SignupResolver(ctx, params)
}
// MobileSignup is the resolver for the mobile_signup field.
func (r *mutationResolver) MobileSignup(ctx context.Context, params *model.MobileSignUpInput) (*model.AuthResponse, error) {
return resolvers.MobileSignupResolver(ctx, params)
}
// Login is the resolver for the login field.
func (r *mutationResolver) Login(ctx context.Context, params model.LoginInput) (*model.AuthResponse, error) {
return resolvers.LoginResolver(ctx, params)
}
// MobileLogin is the resolver for the mobile_login field.
func (r *mutationResolver) MobileLogin(ctx context.Context, params model.MobileLoginInput) (*model.AuthResponse, error) {
return resolvers.MobileLoginResolver(ctx, params)
}
// MagicLinkLogin is the resolver for the magic_link_login field.
func (r *mutationResolver) MagicLinkLogin(ctx context.Context, params model.MagicLinkLoginInput) (*model.Response, error) {
return resolvers.MagicLinkLoginResolver(ctx, params)
@@ -181,6 +191,11 @@ func (r *queryResolver) Users(ctx context.Context, params *model.PaginatedInput)
return resolvers.UsersResolver(ctx, params)
}
// User is the resolver for the _user field.
func (r *queryResolver) User(ctx context.Context, params model.GetUserRequest) (*model.User, error) {
return resolvers.UserResolver(ctx, params)
}
// VerificationRequests is the resolver for the _verification_requests field.
func (r *queryResolver) VerificationRequests(ctx context.Context, params *model.PaginatedInput) (*model.VerificationRequests, error) {
return resolvers.VerificationRequestsResolver(ctx, params)

View File

@@ -139,6 +139,7 @@ func AuthorizeHandler() gin.HandlerFunc {
"error_description": "code challenge is required",
},
}, http.StatusOK)
return
}
loginError := map[string]interface{}{
@@ -268,7 +269,7 @@ func AuthorizeHandler() gin.HandlerFunc {
return
}
if err := memorystore.Provider.SetUserSession(sessionKey, constants.TokenTypeAccessToken+"_"+nonce, authToken.FingerPrintHash); err != nil {
if err := memorystore.Provider.SetUserSession(sessionKey, constants.TokenTypeAccessToken+"_"+nonce, authToken.AccessToken.Token); err != nil {
log.Debug("SetUserSession failed: ", err)
handleResponse(gc, responseMode, loginURL, redirectURI, loginError, http.StatusOK)
return
@@ -326,7 +327,7 @@ func AuthorizeHandler() gin.HandlerFunc {
func validateAuthorizeRequest(responseType, responseMode, clientID, state, codeChallenge string) error {
if strings.TrimSpace(state) == "" {
return fmt.Errorf("invalid state. state is required to prevent csrf attack", responseMode)
return fmt.Errorf("invalid state. state is required to prevent csrf attack")
}
if responseType != constants.ResponseTypeCode && responseType != constants.ResponseTypeToken && responseType != constants.ResponseTypeIDToken {
return fmt.Errorf("invalid response type %s. 'code' & 'token' are valid response_type", responseMode)
@@ -349,14 +350,13 @@ func handleResponse(gc *gin.Context, responseMode, loginURI, redirectURI string,
isAuthenticationRequired = true
}
if isAuthenticationRequired {
if isAuthenticationRequired && responseMode != constants.ResponseModeWebMessage {
gc.Redirect(http.StatusFound, loginURI)
return
}
switch responseMode {
case constants.ResponseModeQuery, constants.ResponseModeFragment:
gc.Redirect(http.StatusFound, redirectURI)
return
case constants.ResponseModeWebMessage:

View File

@@ -5,7 +5,7 @@ import (
"encoding/base64"
"encoding/json"
"fmt"
"io/ioutil"
"io"
"net/http"
"strconv"
"strings"
@@ -312,7 +312,7 @@ func processGoogleUserInfo(code string) (models.User, error) {
func processGithubUserInfo(code string) (models.User, error) {
user := models.User{}
oauth2Token, err := oauth.OAuthProviders.GithubConfig.Exchange(oauth2.NoContext, code)
oauth2Token, err := oauth.OAuthProviders.GithubConfig.Exchange(context.TODO(), code)
if err != nil {
log.Debug("Failed to exchange code for token: ", err)
return user, fmt.Errorf("invalid github exchange code: %s", err.Error())
@@ -334,7 +334,7 @@ func processGithubUserInfo(code string) (models.User, error) {
}
defer response.Body.Close()
body, err := ioutil.ReadAll(response.Body)
body, err := io.ReadAll(response.Body)
if err != nil {
log.Debug("Failed to read github user info response body: ", err)
return user, fmt.Errorf("failed to read github response body: %s", err.Error())
@@ -383,7 +383,7 @@ func processGithubUserInfo(code string) (models.User, error) {
}
defer response.Body.Close()
body, err := ioutil.ReadAll(response.Body)
body, err := io.ReadAll(response.Body)
if err != nil {
log.Debug("Failed to read github user email response body: ", err)
return user, fmt.Errorf("failed to read github response body: %s", err.Error())
@@ -419,7 +419,7 @@ func processGithubUserInfo(code string) (models.User, error) {
func processFacebookUserInfo(code string) (models.User, error) {
user := models.User{}
oauth2Token, err := oauth.OAuthProviders.FacebookConfig.Exchange(oauth2.NoContext, code)
oauth2Token, err := oauth.OAuthProviders.FacebookConfig.Exchange(context.TODO(), code)
if err != nil {
log.Debug("Invalid facebook exchange code: ", err)
return user, fmt.Errorf("invalid facebook exchange code: %s", err.Error())
@@ -438,7 +438,7 @@ func processFacebookUserInfo(code string) (models.User, error) {
}
defer response.Body.Close()
body, err := ioutil.ReadAll(response.Body)
body, err := io.ReadAll(response.Body)
if err != nil {
log.Debug("Failed to read facebook response: ", err)
return user, fmt.Errorf("failed to read facebook response body: %s", err.Error())
@@ -470,7 +470,7 @@ func processFacebookUserInfo(code string) (models.User, error) {
func processLinkedInUserInfo(code string) (models.User, error) {
user := models.User{}
oauth2Token, err := oauth.OAuthProviders.LinkedInConfig.Exchange(oauth2.NoContext, code)
oauth2Token, err := oauth.OAuthProviders.LinkedInConfig.Exchange(context.TODO(), code)
if err != nil {
log.Debug("Failed to exchange code for token: ", err)
return user, fmt.Errorf("invalid linkedin exchange code: %s", err.Error())
@@ -493,7 +493,7 @@ func processLinkedInUserInfo(code string) (models.User, error) {
}
defer response.Body.Close()
body, err := ioutil.ReadAll(response.Body)
body, err := io.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())
@@ -523,7 +523,7 @@ func processLinkedInUserInfo(code string) (models.User, error) {
}
defer response.Body.Close()
body, err = ioutil.ReadAll(response.Body)
body, err = io.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())
@@ -552,7 +552,7 @@ func processLinkedInUserInfo(code string) (models.User, error) {
func processAppleUserInfo(code string) (models.User, error) {
user := models.User{}
oauth2Token, err := oauth.OAuthProviders.AppleConfig.Exchange(oauth2.NoContext, code)
oauth2Token, err := oauth.OAuthProviders.AppleConfig.Exchange(context.TODO(), code)
if err != nil {
log.Debug("Failed to exchange code for token: ", err)
return user, fmt.Errorf("invalid apple exchange code: %s", err.Error())
@@ -605,7 +605,7 @@ func processAppleUserInfo(code string) (models.User, error) {
func processTwitterUserInfo(code, verifier string) (models.User, error) {
user := models.User{}
oauth2Token, err := oauth.OAuthProviders.TwitterConfig.Exchange(oauth2.NoContext, code, oauth2.SetAuthURLParam("code_verifier", verifier))
oauth2Token, err := oauth.OAuthProviders.TwitterConfig.Exchange(context.TODO(), code, oauth2.SetAuthURLParam("code_verifier", verifier))
if err != nil {
log.Debug("Failed to exchange code for token: ", err)
return user, fmt.Errorf("invalid twitter exchange code: %s", err.Error())
@@ -628,7 +628,7 @@ func processTwitterUserInfo(code, verifier string) (models.User, error) {
}
defer response.Body.Close()
body, err := ioutil.ReadAll(response.Body)
body, err := io.ReadAll(response.Body)
if err != nil {
log.Debug("Failed to read Twitter user info response body: ", err)
return user, fmt.Errorf("failed to read Twitter response body: %s", err.Error())

View File

@@ -64,6 +64,12 @@ func TokenHandler() gin.HandlerFunc {
})
}
// check if clientID & clientSecret are present as part of
// authorization header with basic auth
if clientID == "" && clientSecret == "" {
clientID, clientSecret, _ = gc.Request.BasicAuth()
}
if clientID == "" {
log.Debug("Client ID is empty")
gc.JSON(http.StatusBadRequest, gin.H{

61
server/logs/logs.go Normal file
View File

@@ -0,0 +1,61 @@
package logs
import (
"os"
"github.com/sirupsen/logrus"
log "github.com/sirupsen/logrus"
)
// LogUTCFormatter hels in setting UTC time format for the logs
type LogUTCFormatter struct {
log.Formatter
}
// Format helps fomratting time to UTC
func (u LogUTCFormatter) Format(e *log.Entry) ([]byte, error) {
e.Time = e.Time.UTC()
return u.Formatter.Format(e)
}
func InitLog(cliLogLevel string) *log.Logger {
// log instance for gin server
log := logrus.New()
log.SetFormatter(LogUTCFormatter{&logrus.JSONFormatter{}})
if cliLogLevel == "" {
cliLogLevel = os.Getenv("LOG_LEVEL")
}
var logLevel logrus.Level
switch cliLogLevel {
case "debug":
logLevel = logrus.DebugLevel
case "info":
logLevel = logrus.InfoLevel
case "warn":
logLevel = logrus.WarnLevel
case "error":
logLevel = logrus.ErrorLevel
case "fatal":
logLevel = logrus.FatalLevel
case "panic":
logLevel = logrus.PanicLevel
default:
logLevel = logrus.InfoLevel
}
// set log level globally
logrus.SetLevel(logLevel)
// set log level for go-gin middleware
log.SetLevel(logLevel)
// show file path in log for debug or other log levels.
if logLevel != logrus.InfoLevel {
logrus.SetReportCaller(true)
log.SetReportCaller(true)
}
return log
}

View File

@@ -3,84 +3,42 @@ package main
import (
"flag"
"github.com/sirupsen/logrus"
log "github.com/sirupsen/logrus"
"github.com/authorizerdev/authorizer/server/cli"
"github.com/authorizerdev/authorizer/server/constants"
"github.com/authorizerdev/authorizer/server/db"
"github.com/authorizerdev/authorizer/server/env"
"github.com/authorizerdev/authorizer/server/logs"
"github.com/authorizerdev/authorizer/server/memorystore"
"github.com/authorizerdev/authorizer/server/oauth"
"github.com/authorizerdev/authorizer/server/refs"
"github.com/authorizerdev/authorizer/server/routes"
"github.com/sirupsen/logrus"
)
// VERSION is used to define the version of authorizer from build tags
var VERSION string
// LogUTCFormatter hels in setting UTC time format for the logs
type LogUTCFormatter struct {
log.Formatter
}
// Format helps fomratting time to UTC
func (u LogUTCFormatter) Format(e *log.Entry) ([]byte, error) {
e.Time = e.Time.UTC()
return u.Formatter.Format(e)
}
func main() {
cli.ARG_DB_URL = flag.String("database_url", "", "Database connection string")
cli.ARG_DB_TYPE = flag.String("database_type", "", "Database type, possible values are postgres,mysql,sqlite")
cli.ARG_ENV_FILE = flag.String("env_file", "", "Env file path")
cli.ARG_LOG_LEVEL = flag.String("log_level", "info", "Log level, possible values are debug,info,warn,error,fatal,panic")
cli.ARG_LOG_LEVEL = flag.String("log_level", "", "Log level, possible values are debug,info,warn,error,fatal,panic")
cli.ARG_REDIS_URL = flag.String("redis_url", "", "Redis connection string")
flag.Parse()
// global log level
logrus.SetFormatter(LogUTCFormatter{&logrus.JSONFormatter{}})
// log instance for gin server
log := logrus.New()
log.SetFormatter(LogUTCFormatter{&logrus.JSONFormatter{}})
var logLevel logrus.Level
switch *cli.ARG_LOG_LEVEL {
case "debug":
logLevel = logrus.DebugLevel
case "info":
logLevel = logrus.InfoLevel
case "warn":
logLevel = logrus.WarnLevel
case "error":
logLevel = logrus.ErrorLevel
case "fatal":
logLevel = logrus.FatalLevel
case "panic":
logLevel = logrus.PanicLevel
default:
logLevel = logrus.InfoLevel
}
// set log level globally
logrus.SetLevel(logLevel)
// set log level for go-gin middleware
log.SetLevel(logLevel)
// show file path in log for debug or other log levels.
if logLevel != logrus.InfoLevel {
logrus.SetReportCaller(true)
log.SetReportCaller(true)
}
logrus.SetFormatter(logs.LogUTCFormatter{&logrus.JSONFormatter{}})
constants.VERSION = VERSION
// initialize required envs (mainly db, env file path and redis)
err := memorystore.InitRequiredEnv()
if err != nil {
log.Fatal("Error while initializing required envs: ", err)
logrus.Fatal("Error while initializing required envs: ", err)
}
log := logs.InitLog(refs.StringValue(cli.ARG_LOG_LEVEL))
// initialize memory store
err = memorystore.InitMemStore()
if err != nil {

View File

@@ -26,6 +26,7 @@ func InitMemStore() error {
// boolean envs
constants.EnvKeyDisableBasicAuthentication: false,
constants.EnvKeyDisableMobileBasicAuthentication: false,
constants.EnvKeyDisableMagicLinkLogin: false,
constants.EnvKeyDisableEmailVerification: false,
constants.EnvKeyDisableLoginPage: false,

View File

@@ -161,7 +161,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 || key == constants.EnvKeyDisableStrongPassword || key == constants.EnvKeyIsEmailServiceEnabled || key == constants.EnvKeyEnforceMultiFactorAuthentication || key == constants.EnvKeyDisableMultiFactorAuthentication || key == constants.EnvKeyAppCookieSecure || key == constants.EnvKeyAdminCookieSecure {
if key == constants.EnvKeyDisableBasicAuthentication || key == constants.EnvKeyDisableMobileBasicAuthentication || key == constants.EnvKeyDisableEmailVerification || key == constants.EnvKeyDisableLoginPage || key == constants.EnvKeyDisableMagicLinkLogin || key == constants.EnvKeyDisableRedisForEnv || key == constants.EnvKeyDisableSignUp || key == constants.EnvKeyDisableStrongPassword || key == constants.EnvKeyIsEmailServiceEnabled || key == constants.EnvKeyEnforceMultiFactorAuthentication || key == constants.EnvKeyDisableMultiFactorAuthentication || key == constants.EnvKeyAppCookieSecure || key == constants.EnvKeyAdminCookieSecure {
boolValue, err := strconv.ParseBool(value)
if err != nil {
return res, err

View File

@@ -9,7 +9,6 @@ import (
func CORSMiddleware() gin.HandlerFunc {
return func(c *gin.Context) {
origin := c.Request.Header.Get("Origin")
if validators.IsValidOrigin(origin) {
c.Writer.Header().Set("Access-Control-Allow-Origin", origin)
}

View File

@@ -20,12 +20,12 @@ func GetHost(c *gin.Context) string {
authorizerURL = ""
}
if authorizerURL != "" {
return authorizerURL
return strings.TrimSuffix(authorizerURL, "/")
}
authorizerURL = c.Request.Header.Get("X-Authorizer-URL")
if authorizerURL != "" {
return authorizerURL
return strings.TrimSuffix(authorizerURL, "/")
}
scheme := c.Request.Header.Get("X-Forwarded-Proto")
@@ -33,7 +33,7 @@ func GetHost(c *gin.Context) string {
scheme = "http"
}
host := c.Request.Host
return scheme + "://" + host
return strings.TrimSuffix(scheme+"://"+host, "/")
}
// GetHostName function returns hostname and port

View File

@@ -0,0 +1,216 @@
package resolvers
import (
"context"
"fmt"
"strings"
"time"
"github.com/google/uuid"
log "github.com/sirupsen/logrus"
"golang.org/x/crypto/bcrypt"
"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"
"github.com/authorizerdev/authorizer/server/graph/model"
"github.com/authorizerdev/authorizer/server/memorystore"
"github.com/authorizerdev/authorizer/server/refs"
"github.com/authorizerdev/authorizer/server/token"
"github.com/authorizerdev/authorizer/server/utils"
"github.com/authorizerdev/authorizer/server/validators"
)
// MobileLoginResolver is a resolver for mobile login mutation
func MobileLoginResolver(ctx context.Context, params model.MobileLoginInput) (*model.AuthResponse, error) {
var res *model.AuthResponse
gc, err := utils.GinContextFromContext(ctx)
if err != nil {
log.Debug("Failed to get GinContext: ", err)
return res, err
}
isBasiAuthDisabled, err := memorystore.Provider.GetBoolStoreEnvVariable(constants.EnvKeyDisableMobileBasicAuthentication)
if err != nil {
log.Debug("Error getting mobile basic auth disabled: ", err)
isBasiAuthDisabled = true
}
if isBasiAuthDisabled {
log.Debug("Basic authentication is disabled.")
return res, fmt.Errorf(`phone number based basic authentication is disabled for this instance`)
}
log := log.WithFields(log.Fields{
"phone_number": params.PhoneNumber,
})
user, err := db.Provider.GetUserByPhoneNumber(ctx, params.PhoneNumber)
if err != nil {
log.Debug("Failed to get user by phone number: ", err)
return res, fmt.Errorf(`bad user credentials`)
}
if user.RevokedTimestamp != nil {
log.Debug("User access is revoked")
return res, fmt.Errorf(`user access has been revoked`)
}
if !strings.Contains(user.SignupMethods, constants.AuthRecipeMethodMobileBasicAuth) {
log.Debug("User signup method is not mobile basic auth")
return res, fmt.Errorf(`user has not signed up with phone number & password`)
}
if user.PhoneNumberVerifiedAt == nil {
log.Debug("User phone number is not verified")
return res, fmt.Errorf(`phone number is not verified`)
}
err = bcrypt.CompareHashAndPassword([]byte(*user.Password), []byte(params.Password))
if err != nil {
log.Debug("Failed to compare password: ", err)
return res, fmt.Errorf(`bad user credentials`)
}
defaultRolesString, err := memorystore.Provider.GetStringStoreEnvVariable(constants.EnvKeyDefaultRoles)
roles := []string{}
if err != nil {
log.Debug("Error getting default roles: ", err)
defaultRolesString = ""
} else {
roles = strings.Split(defaultRolesString, ",")
}
currentRoles := strings.Split(user.Roles, ",")
if len(params.Roles) > 0 {
if !validators.IsValidRoles(params.Roles, currentRoles) {
log.Debug("Invalid roles: ", params.Roles)
return res, fmt.Errorf(`invalid roles`)
}
roles = params.Roles
}
scope := []string{"openid", "email", "profile"}
if params.Scope != nil && len(scope) > 0 {
scope = params.Scope
}
/*
// TODO use sms authentication for MFA
isEmailServiceEnabled, err := memorystore.Provider.GetBoolStoreEnvVariable(constants.EnvKeyIsEmailServiceEnabled)
if err != nil || !isEmailServiceEnabled {
log.Debug("Email service not enabled: ", err)
}
isMFADisabled, err := memorystore.Provider.GetBoolStoreEnvVariable(constants.EnvKeyDisableMultiFactorAuthentication)
if err != nil || !isEmailServiceEnabled {
log.Debug("MFA service not enabled: ", err)
}
// If email service is not enabled continue the process in any way
if refs.BoolValue(user.IsMultiFactorAuthEnabled) && isEmailServiceEnabled && !isMFADisabled {
otp := utils.GenerateOTP()
otpData, err := db.Provider.UpsertOTP(ctx, &models.OTP{
Email: user.Email,
Otp: otp,
ExpiresAt: time.Now().Add(1 * time.Minute).Unix(),
})
if err != nil {
log.Debug("Failed to add otp: ", err)
return nil, err
}
go func() {
// exec it as go routine so that we can reduce the api latency
go email.SendEmail([]string{params.PhoneNumber}, constants.VerificationTypeOTP, map[string]interface{}{
"user": user.ToMap(),
"organization": utils.GetOrganization(),
"otp": otpData.Otp,
})
if err != nil {
log.Debug("Failed to send otp email: ", err)
}
}()
return &model.AuthResponse{
Message: "Please check the OTP in your inbox",
ShouldShowOtpScreen: refs.NewBoolRef(true),
}, nil
}
*/
code := ""
codeChallenge := ""
nonce := ""
if params.State != nil {
// Get state from store
authorizeState, _ := memorystore.Provider.GetState(refs.StringValue(params.State))
if authorizeState != "" {
authorizeStateSplit := strings.Split(authorizeState, "@@")
if len(authorizeStateSplit) > 1 {
code = authorizeStateSplit[0]
codeChallenge = authorizeStateSplit[1]
} else {
nonce = authorizeState
}
go memorystore.Provider.RemoveState(refs.StringValue(params.State))
}
}
if nonce == "" {
nonce = uuid.New().String()
}
authToken, err := token.CreateAuthToken(gc, *user, roles, scope, constants.AuthRecipeMethodMobileBasicAuth, nonce, code)
if err != nil {
log.Debug("Failed to create auth token", err)
return res, err
}
// TODO add to other login options as well
// Code challenge could be optional if PKCE flow is not used
if code != "" {
if err := memorystore.Provider.SetState(code, codeChallenge+"@@"+authToken.FingerPrintHash); err != nil {
log.Debug("SetState failed: ", err)
return res, err
}
}
expiresIn := authToken.AccessToken.ExpiresAt - time.Now().Unix()
if expiresIn <= 0 {
expiresIn = 1
}
res = &model.AuthResponse{
Message: `Logged in successfully`,
AccessToken: &authToken.AccessToken.Token,
IDToken: &authToken.IDToken.Token,
ExpiresIn: &expiresIn,
User: user.AsAPIUser(),
}
cookie.SetSession(gc, authToken.FingerPrintHash)
sessionStoreKey := constants.AuthRecipeMethodMobileBasicAuth + ":" + 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(sessionStoreKey, constants.TokenTypeRefreshToken+"_"+authToken.FingerPrint, authToken.RefreshToken.Token)
}
go func() {
utils.RegisterEvent(ctx, constants.UserLoginWebhookEvent, constants.AuthRecipeMethodMobileBasicAuth, *user)
db.Provider.AddSession(ctx, models.Session{
UserID: user.ID,
UserAgent: utils.GetUserAgent(gc.Request),
IP: utils.GetIP(gc.Request),
})
}()
return res, nil
}

View File

@@ -0,0 +1,270 @@
package resolvers
import (
"context"
"fmt"
"strings"
"time"
"github.com/google/uuid"
log "github.com/sirupsen/logrus"
"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/graph/model"
"github.com/authorizerdev/authorizer/server/memorystore"
"github.com/authorizerdev/authorizer/server/refs"
"github.com/authorizerdev/authorizer/server/token"
"github.com/authorizerdev/authorizer/server/utils"
"github.com/authorizerdev/authorizer/server/validators"
)
// MobileSignupResolver is a resolver for mobile_basic_auth_signup mutation
func MobileSignupResolver(ctx context.Context, params *model.MobileSignUpInput) (*model.AuthResponse, error) {
var res *model.AuthResponse
gc, err := utils.GinContextFromContext(ctx)
if err != nil {
log.Debug("Failed to get GinContext: ", err)
return res, err
}
isSignupDisabled, err := memorystore.Provider.GetBoolStoreEnvVariable(constants.EnvKeyDisableSignUp)
if err != nil {
log.Debug("Error getting signup disabled: ", err)
isSignupDisabled = true
}
if isSignupDisabled {
log.Debug("Signup is disabled")
return res, fmt.Errorf(`signup is disabled for this instance`)
}
isBasicAuthDisabled, err := memorystore.Provider.GetBoolStoreEnvVariable(constants.EnvKeyDisableMobileBasicAuthentication)
if err != nil {
log.Debug("Error getting basic auth disabled: ", err)
isBasicAuthDisabled = true
}
if isBasicAuthDisabled {
log.Debug("Mobile based Basic authentication is disabled")
return res, fmt.Errorf(`phone number based basic authentication is disabled for this instance`)
}
if params.ConfirmPassword != params.Password {
log.Debug("Passwords do not match")
return res, fmt.Errorf(`password and confirm password does not match`)
}
if err := validators.IsValidPassword(params.Password); err != nil {
log.Debug("Invalid password")
return res, err
}
mobile := strings.TrimSpace(params.PhoneNumber)
if mobile == "" || len(mobile) < 10 {
log.Debug("Invalid phone number")
return res, fmt.Errorf("invalid phone number")
}
emailInput := strings.ToLower(strings.TrimSpace(refs.StringValue(params.Email)))
// if email is null set random dummy email for db constraint
if emailInput != "" && !validators.IsValidEmail(emailInput) {
log.Debug("Invalid email: ", emailInput)
return res, fmt.Errorf(`invalid email address`)
}
if emailInput == "" {
emailInput = mobile + "@authorizer.dev"
}
log := log.WithFields(log.Fields{
"email": emailInput,
"phone_number": mobile,
})
// find user with email
existingUser, err := db.Provider.GetUserByPhoneNumber(ctx, mobile)
if err != nil {
log.Debug("Failed to get user by email: ", err)
}
if existingUser != nil {
if existingUser.PhoneNumberVerifiedAt != nil {
// email is verified
log.Debug("Phone number is already verified and signed up.")
return res, fmt.Errorf(`%s has already signed up`, mobile)
} else if existingUser.ID != "" && existingUser.PhoneNumberVerifiedAt == nil {
log.Debug("Phone number is already signed up. Verification pending...")
return res, fmt.Errorf("%s has already signed up. please complete the phone number verification process or reset the password", mobile)
}
}
inputRoles := []string{}
if len(params.Roles) > 0 {
// check if roles exists
rolesString, err := memorystore.Provider.GetStringStoreEnvVariable(constants.EnvKeyRoles)
roles := []string{}
if err != nil {
log.Debug("Error getting roles: ", err)
return res, err
} else {
roles = strings.Split(rolesString, ",")
}
if !validators.IsValidRoles(params.Roles, roles) {
log.Debug("Invalid roles: ", params.Roles)
return res, fmt.Errorf(`invalid roles`)
} else {
inputRoles = params.Roles
}
} else {
inputRolesString, err := memorystore.Provider.GetStringStoreEnvVariable(constants.EnvKeyDefaultRoles)
if err != nil {
log.Debug("Error getting default roles: ", err)
return res, err
} else {
inputRoles = strings.Split(inputRolesString, ",")
}
}
now := time.Now().Unix()
user := models.User{
Email: emailInput,
PhoneNumber: &mobile,
PhoneNumberVerifiedAt: &now,
}
user.Roles = strings.Join(inputRoles, ",")
password, _ := crypto.EncryptPassword(params.Password)
user.Password = &password
if params.GivenName != nil {
user.GivenName = params.GivenName
}
if params.FamilyName != nil {
user.FamilyName = params.FamilyName
}
if params.MiddleName != nil {
user.MiddleName = params.MiddleName
}
if params.Nickname != nil {
user.Nickname = params.Nickname
}
if params.Gender != nil {
user.Gender = params.Gender
}
if params.Birthdate != nil {
user.Birthdate = params.Birthdate
}
if params.Picture != nil {
user.Picture = params.Picture
}
if params.IsMultiFactorAuthEnabled != nil {
user.IsMultiFactorAuthEnabled = params.IsMultiFactorAuthEnabled
}
isMFAEnforced, err := memorystore.Provider.GetBoolStoreEnvVariable(constants.EnvKeyEnforceMultiFactorAuthentication)
if err != nil {
log.Debug("MFA service not enabled: ", err)
isMFAEnforced = false
}
if isMFAEnforced {
user.IsMultiFactorAuthEnabled = refs.NewBoolRef(true)
}
user.SignupMethods = constants.AuthRecipeMethodMobileBasicAuth
user, err = db.Provider.AddUser(ctx, user)
if err != nil {
log.Debug("Failed to add user: ", err)
return res, err
}
roles := strings.Split(user.Roles, ",")
userToReturn := user.AsAPIUser()
scope := []string{"openid", "email", "profile"}
if params.Scope != nil && len(scope) > 0 {
scope = params.Scope
}
code := ""
codeChallenge := ""
nonce := ""
if params.State != nil {
// Get state from store
authorizeState, _ := memorystore.Provider.GetState(refs.StringValue(params.State))
if authorizeState != "" {
authorizeStateSplit := strings.Split(authorizeState, "@@")
if len(authorizeStateSplit) > 1 {
code = authorizeStateSplit[0]
codeChallenge = authorizeStateSplit[1]
} else {
nonce = authorizeState
}
go memorystore.Provider.RemoveState(refs.StringValue(params.State))
}
}
if nonce == "" {
nonce = uuid.New().String()
}
authToken, err := token.CreateAuthToken(gc, user, roles, scope, constants.AuthRecipeMethodMobileBasicAuth, nonce, code)
if err != nil {
log.Debug("Failed to create auth token: ", err)
return res, err
}
// Code challenge could be optional if PKCE flow is not used
if code != "" {
if err := memorystore.Provider.SetState(code, codeChallenge+"@@"+authToken.FingerPrintHash); err != nil {
log.Debug("SetState failed: ", err)
return res, err
}
}
expiresIn := authToken.AccessToken.ExpiresAt - time.Now().Unix()
if expiresIn <= 0 {
expiresIn = 1
}
res = &model.AuthResponse{
Message: `Signed up successfully.`,
AccessToken: &authToken.AccessToken.Token,
ExpiresIn: &expiresIn,
User: userToReturn,
}
sessionKey := constants.AuthRecipeMethodMobileBasicAuth + ":" + user.ID
cookie.SetSession(gc, authToken.FingerPrintHash)
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(sessionKey, constants.TokenTypeRefreshToken+"_"+authToken.FingerPrint, authToken.RefreshToken.Token)
}
go func() {
utils.RegisterEvent(ctx, constants.UserSignUpWebhookEvent, constants.AuthRecipeMethodMobileBasicAuth, user)
db.Provider.AddSession(ctx, models.Session{
UserID: user.ID,
UserAgent: utils.GetUserAgent(gc.Request),
IP: utils.GetIP(gc.Request),
})
}()
return res, nil
}

View File

@@ -25,6 +25,7 @@ import (
// remove the session tokens for those methods
func clearSessionIfRequired(currentData, updatedData map[string]interface{}) {
isCurrentBasicAuthEnabled := !currentData[constants.EnvKeyDisableBasicAuthentication].(bool)
isCurrentMobileBasicAuthEnabled := !currentData[constants.EnvKeyDisableMobileBasicAuthentication].(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) != ""
@@ -34,6 +35,7 @@ func clearSessionIfRequired(currentData, updatedData map[string]interface{}) {
isCurrentTwitterLoginEnabled := currentData[constants.EnvKeyTwitterClientID] != nil && currentData[constants.EnvKeyTwitterClientSecret] != nil && currentData[constants.EnvKeyTwitterClientID].(string) != "" && currentData[constants.EnvKeyTwitterClientSecret].(string) != ""
isUpdatedBasicAuthEnabled := !updatedData[constants.EnvKeyDisableBasicAuthentication].(bool)
isUpdatedMobileBasicAuthEnabled := !updatedData[constants.EnvKeyDisableMobileBasicAuthentication].(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) != ""
@@ -46,6 +48,10 @@ func clearSessionIfRequired(currentData, updatedData map[string]interface{}) {
memorystore.Provider.DeleteSessionForNamespace(constants.AuthRecipeMethodBasicAuth)
}
if isCurrentMobileBasicAuthEnabled && !isUpdatedMobileBasicAuthEnabled {
memorystore.Provider.DeleteSessionForNamespace(constants.AuthRecipeMethodMobileBasicAuth)
}
if isCurrentMagicLinkLoginEnabled && !isUpdatedMagicLinkLoginEnabled {
memorystore.Provider.DeleteSessionForNamespace(constants.AuthRecipeMethodMagicLinkLogin)
}

View File

@@ -88,6 +88,11 @@ func UpdateProfileResolver(ctx context.Context, params model.UpdateProfileInput)
}
if params.PhoneNumber != nil && refs.StringValue(user.PhoneNumber) != refs.StringValue(params.PhoneNumber) {
// verify if phone number is unique
if _, err := db.Provider.GetUserByPhoneNumber(ctx, strings.TrimSpace(refs.StringValue(params.PhoneNumber))); err == nil {
log.Debug("user with given phone number already exists")
return nil, errors.New("user with given phone number already exists")
}
user.PhoneNumber = params.PhoneNumber
}
@@ -154,8 +159,14 @@ func UpdateProfileResolver(ctx context.Context, params model.UpdateProfileInput)
isBasicAuthDisabled = true
}
isMobileBasicAuthDisabled, err := memorystore.Provider.GetBoolStoreEnvVariable(constants.EnvKeyDisableMobileBasicAuthentication)
if err != nil {
log.Debug("Error getting mobile basic auth disabled: ", err)
isBasicAuthDisabled = true
}
if params.NewPassword != nil && params.ConfirmNewPassword != nil {
if isBasicAuthDisabled {
if isBasicAuthDisabled || isMobileBasicAuthDisabled {
log.Debug("Cannot update password as basic authentication is disabled")
return res, fmt.Errorf(`basic authentication is disabled for this instance`)
}

View File

@@ -83,6 +83,11 @@ func UpdateUserResolver(ctx context.Context, params model.UpdateUserInput) (*mod
}
if params.PhoneNumber != nil && refs.StringValue(user.PhoneNumber) != refs.StringValue(params.PhoneNumber) {
// verify if phone number is unique
if _, err := db.Provider.GetUserByPhoneNumber(ctx, strings.TrimSpace(refs.StringValue(params.PhoneNumber))); err == nil {
log.Debug("user with given phone number already exists")
return nil, errors.New("user with given phone number already exists")
}
user.PhoneNumber = params.PhoneNumber
}

36
server/resolvers/user.go Normal file
View File

@@ -0,0 +1,36 @@
package resolvers
import (
"context"
"fmt"
log "github.com/sirupsen/logrus"
"github.com/authorizerdev/authorizer/server/db"
"github.com/authorizerdev/authorizer/server/graph/model"
"github.com/authorizerdev/authorizer/server/token"
"github.com/authorizerdev/authorizer/server/utils"
)
// UserResolver is a resolver for user query
// This is admin only query
func UserResolver(ctx context.Context, params model.GetUserRequest) (*model.User, error) {
gc, err := utils.GinContextFromContext(ctx)
if err != nil {
log.Debug("Failed to get GinContext: ", err)
return nil, err
}
if !token.IsSuperAdmin(gc) {
log.Debug("Not logged in as super admin.")
return nil, fmt.Errorf("unauthorized")
}
res, err := db.Provider.GetUserByID(ctx, params.ID)
if err != nil {
log.Debug("Failed to get users: ", err)
return nil, err
}
return res.AsAPIUser(), nil
}

View File

@@ -77,7 +77,16 @@ func ValidateJwtTokenResolver(ctx context.Context, params model.ValidateJWTToken
}
}
claimRolesInterface := claims["roles"]
claimKey := "roles"
if tokenType == constants.TokenTypeIdentityToken {
claimKey, err = memorystore.Provider.GetStringStoreEnvVariable(constants.EnvKeyJwtRoleClaim)
if err != nil {
claimKey = "roles"
}
}
claimRolesInterface := claims[claimKey]
roleSlice := utils.ConvertInterfaceToSlice(claimRolesInterface)
for _, v := range roleSlice {
claimRoles = append(claimRoles, v.(string))

View File

@@ -51,8 +51,7 @@ func addEmailTemplateTest(t *testing.T, s TestSetup) {
assert.Nil(t, emailTemplate)
})
var design string
design = ""
design := ""
for _, eventType := range s.TestInfo.TestEmailTemplateEventTypes {
t.Run("should add email template with empty design for "+eventType, func(t *testing.T) {
@@ -70,29 +69,7 @@ func addEmailTemplateTest(t *testing.T, s TestSetup) {
assert.NoError(t, err)
assert.Equal(t, et.EventName, eventType)
assert.Equal(t, "Test email", et.Subject)
assert.Equal(t, "Test design", et.Design)
})
}
design = "Test design"
for _, eventType := range s.TestInfo.TestEmailTemplateEventTypes {
t.Run("should add email template for "+eventType, func(t *testing.T) {
emailTemplate, err := resolvers.AddEmailTemplateResolver(ctx, model.AddEmailTemplateRequest{
EventName: eventType,
Template: "Test email",
Subject: "Test email",
Design: &design,
})
assert.NoError(t, err)
assert.NotNil(t, emailTemplate)
assert.NotEmpty(t, emailTemplate.Message)
et, err := db.Provider.GetEmailTemplateByEventName(ctx, eventType)
assert.NoError(t, err)
assert.Equal(t, et.EventName, eventType)
assert.Equal(t, "Test email", et.Subject)
assert.Equal(t, "Test design", et.Design)
assert.Equal(t, "", et.Design)
})
}
})

View File

@@ -0,0 +1,58 @@
package test
import (
"strings"
"testing"
"github.com/authorizerdev/authorizer/server/constants"
"github.com/authorizerdev/authorizer/server/graph/model"
"github.com/authorizerdev/authorizer/server/refs"
"github.com/authorizerdev/authorizer/server/resolvers"
"github.com/stretchr/testify/assert"
)
func mobileLoginTests(t *testing.T, s TestSetup) {
t.Helper()
t.Run(`should login via mobile`, func(t *testing.T) {
_, ctx := createContext(s)
email := "mobile_login." + s.TestInfo.Email
phoneNumber := "2234567890"
signUpRes, err := resolvers.MobileSignupResolver(ctx, &model.MobileSignUpInput{
Email: refs.NewStringRef(email),
PhoneNumber: phoneNumber,
Password: s.TestInfo.Password,
ConfirmPassword: s.TestInfo.Password,
})
assert.NoError(t, err)
assert.NotNil(t, signUpRes)
assert.Equal(t, email, signUpRes.User.Email)
assert.Equal(t, phoneNumber, refs.StringValue(signUpRes.User.PhoneNumber))
assert.True(t, strings.Contains(signUpRes.User.SignupMethods, constants.AuthRecipeMethodMobileBasicAuth))
assert.Len(t, strings.Split(signUpRes.User.SignupMethods, ","), 1)
res, err := resolvers.MobileLoginResolver(ctx, model.MobileLoginInput{
PhoneNumber: phoneNumber,
Password: "random_test",
})
assert.Error(t, err)
assert.Nil(t, res)
// Should fail for email login
res, err = resolvers.LoginResolver(ctx, model.LoginInput{
Email: email,
Password: s.TestInfo.Password,
})
assert.Error(t, err)
assert.Nil(t, res)
res, err = resolvers.MobileLoginResolver(ctx, model.MobileLoginInput{
PhoneNumber: phoneNumber,
Password: s.TestInfo.Password,
})
assert.NoError(t, err)
assert.NotEmpty(t, res.AccessToken)
assert.NotEmpty(t, res.IDToken)
cleanData(email)
})
}

View File

@@ -0,0 +1,85 @@
package test
import (
"testing"
"github.com/authorizerdev/authorizer/server/constants"
"github.com/authorizerdev/authorizer/server/graph/model"
"github.com/authorizerdev/authorizer/server/memorystore"
"github.com/authorizerdev/authorizer/server/refs"
"github.com/authorizerdev/authorizer/server/resolvers"
"github.com/stretchr/testify/assert"
)
func mobileSingupTest(t *testing.T, s TestSetup) {
t.Helper()
t.Run(`should complete the signup with mobile and check duplicates`, func(t *testing.T) {
_, ctx := createContext(s)
email := "mobile_basic_auth_signup." + s.TestInfo.Email
res, err := resolvers.MobileSignupResolver(ctx, &model.MobileSignUpInput{
Email: refs.NewStringRef(email),
Password: s.TestInfo.Password,
ConfirmPassword: s.TestInfo.Password + "s",
})
assert.NotNil(t, err, "invalid password")
assert.Nil(t, res)
res, err = resolvers.MobileSignupResolver(ctx, &model.MobileSignUpInput{
Email: refs.NewStringRef(email),
Password: "test",
ConfirmPassword: "test",
})
assert.NotNil(t, err, "invalid password")
memorystore.Provider.UpdateEnvVariable(constants.EnvKeyDisableSignUp, true)
res, err = resolvers.MobileSignupResolver(ctx, &model.MobileSignUpInput{
Email: refs.NewStringRef(email),
Password: s.TestInfo.Password,
ConfirmPassword: s.TestInfo.Password,
})
assert.NotNil(t, err, "singup disabled")
memorystore.Provider.UpdateEnvVariable(constants.EnvKeyDisableSignUp, false)
memorystore.Provider.UpdateEnvVariable(constants.EnvKeyDisableMobileBasicAuthentication, true)
res, err = resolvers.MobileSignupResolver(ctx, &model.MobileSignUpInput{
Email: refs.NewStringRef(email),
Password: s.TestInfo.Password,
ConfirmPassword: s.TestInfo.Password,
})
assert.NotNil(t, err, "singup disabled")
memorystore.Provider.UpdateEnvVariable(constants.EnvKeyDisableMobileBasicAuthentication, false)
res, err = resolvers.MobileSignupResolver(ctx, &model.MobileSignUpInput{
PhoneNumber: " ",
Password: s.TestInfo.Password,
ConfirmPassword: s.TestInfo.Password,
})
assert.NotNil(t, err, "invalid mobile")
res, err = resolvers.MobileSignupResolver(ctx, &model.MobileSignUpInput{
PhoneNumber: "test",
Password: s.TestInfo.Password,
ConfirmPassword: s.TestInfo.Password,
})
assert.NotNil(t, err, "invalid mobile")
res, err = resolvers.MobileSignupResolver(ctx, &model.MobileSignUpInput{
PhoneNumber: "1234567890",
Password: s.TestInfo.Password,
ConfirmPassword: s.TestInfo.Password,
})
assert.NoError(t, err)
assert.NotEmpty(t, res.AccessToken)
assert.Equal(t, "1234567890@authorizer.dev", res.User.Email)
res, err = resolvers.MobileSignupResolver(ctx, &model.MobileSignUpInput{
PhoneNumber: "1234567890",
Password: s.TestInfo.Password,
ConfirmPassword: s.TestInfo.Password,
})
assert.Error(t, err, "user exists")
cleanData(email)
cleanData("1234567890@authorizer.dev")
})
}

View File

@@ -93,6 +93,7 @@ func TestResolvers(t *testing.T) {
webhookTest(t, s)
webhooksTest(t, s)
usersTest(t, s)
userTest(t, s)
deleteUserTest(t, s)
updateUserTest(t, s)
adminLoginTests(t, s)
@@ -111,6 +112,8 @@ func TestResolvers(t *testing.T) {
// user resolvers tests
loginTests(t, s)
signupTests(t, s)
mobileSingupTest(t, s)
mobileLoginTests(t, s)
forgotPasswordTest(t, s)
resendVerifyEmailTests(t, s)
resetPasswordTest(t, s)

View File

@@ -31,7 +31,7 @@ func testEndpointTest(t *testing.T, s TestSetup) {
})
assert.NoError(t, err)
assert.NotNil(t, res)
assert.GreaterOrEqual(t, int64(201), *res.HTTPStatus)
assert.GreaterOrEqual(t, *res.HTTPStatus, int64(200))
assert.NotEmpty(t, res.Response)
})
}

49
server/test/user_test.go Normal file
View File

@@ -0,0 +1,49 @@
package test
import (
"fmt"
"testing"
"github.com/authorizerdev/authorizer/server/constants"
"github.com/authorizerdev/authorizer/server/crypto"
"github.com/authorizerdev/authorizer/server/graph/model"
"github.com/authorizerdev/authorizer/server/memorystore"
"github.com/authorizerdev/authorizer/server/resolvers"
"github.com/stretchr/testify/assert"
)
func userTest(t *testing.T, s TestSetup) {
t.Helper()
t.Run(`should get users list with admin secret only`, func(t *testing.T) {
req, ctx := createContext(s)
email := "user." + s.TestInfo.Email
res, err := resolvers.SignupResolver(ctx, model.SignUpInput{
Email: email,
Password: s.TestInfo.Password,
ConfirmPassword: s.TestInfo.Password,
})
assert.NoError(t, err)
assert.NotEmpty(t, res.User)
userRes, err := resolvers.UserResolver(ctx, model.GetUserRequest{
ID: res.User.ID,
})
assert.Nil(t, userRes)
assert.NotNil(t, err, "unauthorized")
adminSecret, err := memorystore.Provider.GetStringStoreEnvVariable(constants.EnvKeyAdminSecret)
assert.Nil(t, err)
h, err := crypto.EncryptPassword(adminSecret)
assert.Nil(t, err)
req.Header.Set("Cookie", fmt.Sprintf("%s=%s", constants.AdminCookieName, h))
userRes, err = resolvers.UserResolver(ctx, model.GetUserRequest{
ID: res.User.ID,
})
assert.Nil(t, err)
assert.Equal(t, res.User.ID, userRes.ID)
assert.Equal(t, email, userRes.Email)
cleanData(email)
})
}