Skip to content

Commit 0714b43

Browse files
authored
Merge pull request #206 from authorizerdev/feat/2fa
feat: add mutifactor authentication
2 parents 8f69d57 + ebc1190 commit 0714b43

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

63 files changed

+2300
-322
lines changed

Makefile

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,14 +11,26 @@ clean:
1111
rm -rf build
1212
test:
1313
rm -rf server/test/test.db && rm -rf test.db && cd server && go clean --testcache && TEST_DBS="sqlite" go test -p 1 -v ./test
14+
test-mongodb:
15+
docker run -d --name authorizer_mongodb_db -p 27017:27017 mongo:4.4.15
16+
cd server && go clean --testcache && TEST_DBS="mongodb" go test -p 1 -v ./test
17+
docker rm -vf authorizer_mongodb_db
18+
test-scylladb:
19+
docker run -d --name authorizer_scylla_db -p 9042:9042 scylladb/scylla
20+
cd server && go clean --testcache && TEST_DBS="scylladb" go test -p 1 -v ./test
21+
docker rm -vf authorizer_scylla_db
22+
test-arangodb:
23+
docker run -d --name authorizer_arangodb -p 8529:8529 -e ARANGO_NO_AUTH=1 arangodb/arangodb:3.8.4
24+
cd server && go clean --testcache && TEST_DBS="arangodb" go test -p 1 -v ./test
25+
docker rm -vf authorizer_arangodb
1426
test-all-db:
1527
rm -rf server/test/test.db && rm -rf test.db
1628
docker run -d --name authorizer_scylla_db -p 9042:9042 scylladb/scylla
1729
docker run -d --name authorizer_mongodb_db -p 27017:27017 mongo:4.4.15
1830
docker run -d --name authorizer_arangodb -p 8529:8529 -e ARANGO_NO_AUTH=1 arangodb/arangodb:3.8.4
1931
cd server && go clean --testcache && TEST_DBS="sqlite,mongodb,arangodb,scylladb" go test -p 1 -v ./test
20-
docker rm -vf authorizer_mongodb_db
2132
docker rm -vf authorizer_scylla_db
33+
docker rm -vf authorizer_mongodb_db
2234
docker rm -vf authorizer_arangodb
2335
generate:
2436
cd server && go get github.com/99designs/gqlgen/[email protected] && go run github.com/99designs/gqlgen generate

app/package-lock.json

Lines changed: 15 additions & 15 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

app/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
"author": "Lakhan Samani",
1212
"license": "ISC",
1313
"dependencies": {
14-
"@authorizerdev/authorizer-react": "^0.25.0",
14+
"@authorizerdev/authorizer-react": "^0.26.0-beta.0",
1515
"@types/react": "^17.0.15",
1616
"@types/react-dom": "^17.0.9",
1717
"esbuild": "^0.12.17",

dashboard/src/components/EnvComponents/OAuthConfig.tsx

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,7 @@ const OAuthConfig = ({
108108
fieldVisibility={fieldVisibility}
109109
setFieldVisibility={setFieldVisibility}
110110
inputType={HiddenInputType.GOOGLE_CLIENT_SECRET}
111-
placeholder="Google Secret"
111+
placeholder="Google Client Secret"
112112
/>
113113
</Center>
114114
</Flex>
@@ -146,7 +146,7 @@ const OAuthConfig = ({
146146
fieldVisibility={fieldVisibility}
147147
setFieldVisibility={setFieldVisibility}
148148
inputType={HiddenInputType.GITHUB_CLIENT_SECRET}
149-
placeholder="Github Secret"
149+
placeholder="Github Client Secret"
150150
/>
151151
</Center>
152152
</Flex>
@@ -184,7 +184,7 @@ const OAuthConfig = ({
184184
fieldVisibility={fieldVisibility}
185185
setFieldVisibility={setFieldVisibility}
186186
inputType={HiddenInputType.FACEBOOK_CLIENT_SECRET}
187-
placeholder="Facebook Secret"
187+
placeholder="Facebook Client Secret"
188188
/>
189189
</Center>
190190
</Flex>
@@ -260,7 +260,7 @@ const OAuthConfig = ({
260260
fieldVisibility={fieldVisibility}
261261
setFieldVisibility={setFieldVisibility}
262262
inputType={HiddenInputType.APPLE_CLIENT_SECRET}
263-
placeholder="Apple CLient Secret"
263+
placeholder="Apple Client Secret"
264264
/>
265265
</Center>
266266
</Flex>

dashboard/src/graphql/queries/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,7 @@ export const UserDetailsQuery = `
8989
roles
9090
created_at
9191
revoked_timestamp
92+
is_multi_factor_auth_enabled
9293
}
9394
}
9495
}

dashboard/src/pages/Users.tsx

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@ interface userDataTypes {
6868
roles: [string];
6969
created_at: number;
7070
revoked_timestamp: number;
71+
is_multi_factor_auth_enabled?: boolean;
7172
}
7273

7374
const enum updateAccessActions {
@@ -250,6 +251,34 @@ export default function Users() {
250251
break;
251252
}
252253
};
254+
const multiFactorAuthUpdateHandler = async (user: userDataTypes) => {
255+
const res = await client
256+
.mutation(UpdateUser, {
257+
params: {
258+
id: user.id,
259+
is_multi_factor_auth_enabled: !user.is_multi_factor_auth_enabled,
260+
},
261+
})
262+
.toPromise();
263+
if (res.data?._update_user?.id) {
264+
toast({
265+
title: `Multi factor authentication ${
266+
user.is_multi_factor_auth_enabled ? 'disabled' : 'enabled'
267+
} for user`,
268+
isClosable: true,
269+
status: 'success',
270+
position: 'bottom-right',
271+
});
272+
updateUserList();
273+
return;
274+
}
275+
toast({
276+
title: 'Multi factor authentication update failed for user',
277+
isClosable: true,
278+
status: 'error',
279+
position: 'bottom-right',
280+
});
281+
};
253282

254283
return (
255284
<Box m="5" py="5" px="10" bg="white" rounded="md">
@@ -273,6 +302,7 @@ export default function Users() {
273302
<Th>Roles</Th>
274303
<Th>Verified</Th>
275304
<Th>Access</Th>
305+
<Th>MFA</Th>
276306
<Th>Actions</Th>
277307
</Tr>
278308
</Thead>
@@ -305,6 +335,19 @@ export default function Users() {
305335
{user.revoked_timestamp ? 'Revoked' : 'Enabled'}
306336
</Tag>
307337
</Td>
338+
<Td>
339+
<Tag
340+
size="sm"
341+
variant="outline"
342+
colorScheme={
343+
user.is_multi_factor_auth_enabled ? 'green' : 'red'
344+
}
345+
>
346+
{user.is_multi_factor_auth_enabled
347+
? 'Enabled'
348+
: 'Disabled'}
349+
</Tag>
350+
</Td>
308351
<Td>
309352
<Menu>
310353
<MenuButton as={Button} variant="unstyled" size="sm">
@@ -357,6 +400,19 @@ export default function Users() {
357400
Revoke Access
358401
</MenuItem>
359402
)}
403+
{user.is_multi_factor_auth_enabled ? (
404+
<MenuItem
405+
onClick={() => multiFactorAuthUpdateHandler(user)}
406+
>
407+
Disable MFA
408+
</MenuItem>
409+
) : (
410+
<MenuItem
411+
onClick={() => multiFactorAuthUpdateHandler(user)}
412+
>
413+
Enable MFA
414+
</MenuItem>
415+
)}
360416
</MenuList>
361417
</Menu>
362418
</Td>

server/constants/env.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,8 @@ const (
4747
EnvKeySmtpPassword = "SMTP_PASSWORD"
4848
// EnvKeySenderEmail key for env variable SENDER_EMAIL
4949
EnvKeySenderEmail = "SENDER_EMAIL"
50+
// EnvKeyIsEmailServiceEnabled key for env variable IS_EMAIL_SERVICE_ENABLED
51+
EnvKeyIsEmailServiceEnabled = "IS_EMAIL_SERVICE_ENABLED"
5052
// EnvKeyJwtType key for env variable JWT_TYPE
5153
EnvKeyJwtType = "JWT_TYPE"
5254
// EnvKeyJwtSecret key for env variable JWT_SECRET
@@ -117,6 +119,12 @@ const (
117119
EnvKeyDisableRedisForEnv = "DISABLE_REDIS_FOR_ENV"
118120
// EnvKeyDisableStrongPassword key for env variable DISABLE_STRONG_PASSWORD
119121
EnvKeyDisableStrongPassword = "DISABLE_STRONG_PASSWORD"
122+
// EnvKeyEnforceMultiFactorAuthentication is key for env variable ENFORCE_MULTI_FACTOR_AUTHENTICATION
123+
// If enforced and changed later on, existing user will have MFA but new user will not have MFA
124+
EnvKeyEnforceMultiFactorAuthentication = "ENFORCE_MULTI_FACTOR_AUTHENTICATION"
125+
// EnvKeyDisableMultiFactorAuthentication is key for env variable DISABLE_MULTI_FACTOR_AUTHENTICATION
126+
// this variable is used to completely disable multi factor authentication. It will have no effect on profile preference
127+
EnvKeyDisableMultiFactorAuthentication = "DISABLE_MULTI_FACTOR_AUTHENTICATION"
120128

121129
// Slice variables
122130
// EnvKeyRoles key for env variable ROLES

server/db/models/model.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ type CollectionList struct {
99
Webhook string
1010
WebhookLog string
1111
EmailTemplate string
12+
OTP string
1213
}
1314

1415
var (
@@ -23,5 +24,6 @@ var (
2324
Webhook: Prefix + "webhooks",
2425
WebhookLog: Prefix + "webhook_logs",
2526
EmailTemplate: Prefix + "email_templates",
27+
OTP: Prefix + "otps",
2628
}
2729
)

server/db/models/otp.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
package models
2+
3+
// OTP model for database
4+
type OTP struct {
5+
Key string `json:"_key,omitempty" bson:"_key,omitempty" cql:"_key,omitempty"` // for arangodb
6+
ID string `gorm:"primaryKey;type:char(36)" json:"_id" bson:"_id" cql:"id"`
7+
Email string `gorm:"unique" json:"email" bson:"email" cql:"email"`
8+
Otp string `json:"otp" bson:"otp" cql:"otp"`
9+
ExpiresAt int64 `json:"expires_at" bson:"expires_at" cql:"expires_at"`
10+
CreatedAt int64 `json:"created_at" bson:"created_at" cql:"created_at"`
11+
UpdatedAt int64 `json:"updated_at" bson:"updated_at" cql:"updated_at"`
12+
}

server/db/models/user.go

Lines changed: 41 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -14,51 +14,53 @@ type User struct {
1414
Key string `json:"_key,omitempty" bson:"_key,omitempty" cql:"_key,omitempty"` // for arangodb
1515
ID string `gorm:"primaryKey;type:char(36)" json:"_id" bson:"_id" cql:"id"`
1616

17-
Email string `gorm:"unique" json:"email" bson:"email" cql:"email"`
18-
EmailVerifiedAt *int64 `json:"email_verified_at" bson:"email_verified_at" cql:"email_verified_at"`
19-
Password *string `gorm:"type:text" json:"password" bson:"password" cql:"password"`
20-
SignupMethods string `json:"signup_methods" bson:"signup_methods" cql:"signup_methods"`
21-
GivenName *string `json:"given_name" bson:"given_name" cql:"given_name"`
22-
FamilyName *string `json:"family_name" bson:"family_name" cql:"family_name"`
23-
MiddleName *string `json:"middle_name" bson:"middle_name" cql:"middle_name"`
24-
Nickname *string `json:"nickname" bson:"nickname" cql:"nickname"`
25-
Gender *string `json:"gender" bson:"gender" cql:"gender"`
26-
Birthdate *string `json:"birthdate" bson:"birthdate" cql:"birthdate"`
27-
PhoneNumber *string `gorm:"unique" json:"phone_number" bson:"phone_number" cql:"phone_number"`
28-
PhoneNumberVerifiedAt *int64 `json:"phone_number_verified_at" bson:"phone_number_verified_at" cql:"phone_number_verified_at"`
29-
Picture *string `gorm:"type:text" json:"picture" bson:"picture" cql:"picture"`
30-
Roles string `json:"roles" bson:"roles" cql:"roles"`
31-
RevokedTimestamp *int64 `json:"revoked_timestamp" bson:"revoked_timestamp" cql:"revoked_timestamp"`
32-
UpdatedAt int64 `json:"updated_at" bson:"updated_at" cql:"updated_at"`
33-
CreatedAt int64 `json:"created_at" bson:"created_at" cql:"created_at"`
17+
Email string `gorm:"unique" json:"email" bson:"email" cql:"email"`
18+
EmailVerifiedAt *int64 `json:"email_verified_at" bson:"email_verified_at" cql:"email_verified_at"`
19+
Password *string `gorm:"type:text" json:"password" bson:"password" cql:"password"`
20+
SignupMethods string `json:"signup_methods" bson:"signup_methods" cql:"signup_methods"`
21+
GivenName *string `json:"given_name" bson:"given_name" cql:"given_name"`
22+
FamilyName *string `json:"family_name" bson:"family_name" cql:"family_name"`
23+
MiddleName *string `json:"middle_name" bson:"middle_name" cql:"middle_name"`
24+
Nickname *string `json:"nickname" bson:"nickname" cql:"nickname"`
25+
Gender *string `json:"gender" bson:"gender" cql:"gender"`
26+
Birthdate *string `json:"birthdate" bson:"birthdate" cql:"birthdate"`
27+
PhoneNumber *string `gorm:"unique" json:"phone_number" bson:"phone_number" cql:"phone_number"`
28+
PhoneNumberVerifiedAt *int64 `json:"phone_number_verified_at" bson:"phone_number_verified_at" cql:"phone_number_verified_at"`
29+
Picture *string `gorm:"type:text" json:"picture" bson:"picture" cql:"picture"`
30+
Roles string `json:"roles" bson:"roles" cql:"roles"`
31+
RevokedTimestamp *int64 `json:"revoked_timestamp" bson:"revoked_timestamp" cql:"revoked_timestamp"`
32+
IsMultiFactorAuthEnabled *bool `json:"is_multi_factor_auth_enabled" bson:"is_multi_factor_auth_enabled" cql:"is_multi_factor_auth_enabled"`
33+
UpdatedAt int64 `json:"updated_at" bson:"updated_at" cql:"updated_at"`
34+
CreatedAt int64 `json:"created_at" bson:"created_at" cql:"created_at"`
3435
}
3536

3637
func (user *User) AsAPIUser() *model.User {
3738
isEmailVerified := user.EmailVerifiedAt != nil
3839
isPhoneVerified := user.PhoneNumberVerifiedAt != nil
3940

40-
id := user.ID
41-
if strings.Contains(id, Collections.WebhookLog+"/") {
42-
id = strings.TrimPrefix(id, Collections.WebhookLog+"/")
43-
}
41+
// id := user.ID
42+
// if strings.Contains(id, Collections.User+"/") {
43+
// id = strings.TrimPrefix(id, Collections.User+"/")
44+
// }
4445
return &model.User{
45-
ID: id,
46-
Email: user.Email,
47-
EmailVerified: isEmailVerified,
48-
SignupMethods: user.SignupMethods,
49-
GivenName: user.GivenName,
50-
FamilyName: user.FamilyName,
51-
MiddleName: user.MiddleName,
52-
Nickname: user.Nickname,
53-
PreferredUsername: refs.NewStringRef(user.Email),
54-
Gender: user.Gender,
55-
Birthdate: user.Birthdate,
56-
PhoneNumber: user.PhoneNumber,
57-
PhoneNumberVerified: &isPhoneVerified,
58-
Picture: user.Picture,
59-
Roles: strings.Split(user.Roles, ","),
60-
RevokedTimestamp: user.RevokedTimestamp,
61-
CreatedAt: refs.NewInt64Ref(user.CreatedAt),
62-
UpdatedAt: refs.NewInt64Ref(user.UpdatedAt),
46+
ID: user.ID,
47+
Email: user.Email,
48+
EmailVerified: isEmailVerified,
49+
SignupMethods: user.SignupMethods,
50+
GivenName: user.GivenName,
51+
FamilyName: user.FamilyName,
52+
MiddleName: user.MiddleName,
53+
Nickname: user.Nickname,
54+
PreferredUsername: refs.NewStringRef(user.Email),
55+
Gender: user.Gender,
56+
Birthdate: user.Birthdate,
57+
PhoneNumber: user.PhoneNumber,
58+
PhoneNumberVerified: &isPhoneVerified,
59+
Picture: user.Picture,
60+
Roles: strings.Split(user.Roles, ","),
61+
RevokedTimestamp: user.RevokedTimestamp,
62+
IsMultiFactorAuthEnabled: user.IsMultiFactorAuthEnabled,
63+
CreatedAt: refs.NewInt64Ref(user.CreatedAt),
64+
UpdatedAt: refs.NewInt64Ref(user.UpdatedAt),
6365
}
6466
}

0 commit comments

Comments
 (0)