Skip to content

Commit cac67b7

Browse files
authored
feat: add totp UI & recovery code (#429)
1 parent d7da81d commit cac67b7

File tree

11 files changed

+87
-62
lines changed

11 files changed

+87
-62
lines changed

app/package-lock.json

Lines changed: 11 additions & 11 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
@@ -12,7 +12,7 @@
1212
"author": "Lakhan Samani",
1313
"license": "ISC",
1414
"dependencies": {
15-
"@authorizerdev/authorizer-react": "^1.1.13",
15+
"@authorizerdev/authorizer-react": "^1.1.15",
1616
"@types/react": "^17.0.15",
1717
"@types/react-dom": "^17.0.9",
1818
"esbuild": "^0.12.17",

app/src/pages/login.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,8 +37,8 @@ export default function Login({ urlProps }: { urlProps: Record<string, any> }) {
3737
{view === VIEW_TYPES.LOGIN && (
3838
<Fragment>
3939
<h1 style={{ textAlign: 'center' }}>Login</h1>
40-
<br />
4140
<AuthorizerSocialLogin urlProps={urlProps} />
41+
<br />
4242
{config.is_basic_authentication_enabled &&
4343
!config.is_magic_link_login_enabled && (
4444
<AuthorizerBasicAuthLogin urlProps={urlProps} />

app/yarn.lock

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2,19 +2,19 @@
22
# yarn lockfile v1
33

44

5-
"@authorizerdev/authorizer-js@^1.2.6":
6-
version "1.2.6"
7-
resolved "https://registry.npmjs.org/@authorizerdev/authorizer-js/-/authorizer-js-1.2.6.tgz"
8-
integrity sha512-9+9phHUMF+AeDM0y+XQvIRDoerOXnQ1vfTfYN6KxWN1apdrkAd9nzS1zUsA2uJSnX3fFZOErn83GjbYYCYF1BA==
5+
"@authorizerdev/authorizer-js@^1.2.17":
6+
version "1.2.17"
7+
resolved "https://registry.npmjs.org/@authorizerdev/authorizer-js/-/authorizer-js-1.2.17.tgz"
8+
integrity sha512-aF/lu9wZR7TBRaRMAes/hy1q8cZzz5Zo60QLU9Iu09sqnhliHJCp5wSkjsVH+V4ER9i7bmJ2HNABTmOdluxj3A==
99
dependencies:
1010
cross-fetch "^3.1.5"
1111

12-
"@authorizerdev/authorizer-react@^1.1.13":
13-
version "1.1.13"
14-
resolved "https://registry.npmjs.org/@authorizerdev/authorizer-react/-/authorizer-react-1.1.13.tgz"
15-
integrity sha512-LmpzyfR0+nEn+bjUrb/QU9b3kiVoYzMBIvcQ1nV4TNvrvVSqbLPKk+GmoIPkiBEtfy/QSM6XFLkiGNGD9BRP+g==
12+
"@authorizerdev/authorizer-react@^1.1.15":
13+
version "1.1.15"
14+
resolved "https://registry.npmjs.org/@authorizerdev/authorizer-react/-/authorizer-react-1.1.15.tgz"
15+
integrity sha512-Y71qC4GUAHL0QCNj5mVv0Jwv1cIg4Y0yXRiOeYV21C1NMleyLRXgw4qzJ/Vk8rmXsxqSHmr8SGrwOLcSKA2oMA==
1616
dependencies:
17-
"@authorizerdev/authorizer-js" "^1.2.6"
17+
"@authorizerdev/authorizer-js" "^1.2.17"
1818

1919
"@babel/code-frame@^7.22.13":
2020
version "7.22.13"
@@ -420,9 +420,9 @@ [email protected]:
420420
integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==
421421

422422
node-fetch@^2.6.12:
423-
version "2.6.12"
424-
resolved "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.12.tgz"
425-
integrity sha512-C/fGU2E8ToujUivIO0H+tpQ6HWo4eEmchoPIoXtxCrVghxdKq+QOHqEZW7tuP3KlV3bC8FRMO5nMCC7Zm1VP6g==
423+
version "2.7.0"
424+
resolved "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz"
425+
integrity sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==
426426
dependencies:
427427
whatwg-url "^5.0.0"
428428

server/authenticators/providers/providers.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ type Provider interface {
1919
// Generate totp: to generate totp, store secret into db and returns base64 of QR code image
2020
Generate(ctx context.Context, id string) (*AuthenticatorConfig, error)
2121
// Validate totp: user passcode with secret stored in our db
22-
Validate(ctx context.Context, passcode string, id string) (bool, error)
23-
// RecoveryCode totp: gives a recovery code for first time user
24-
RecoveryCode(ctx context.Context, id string) (*string, error)
22+
Validate(ctx context.Context, passcode string, userID string) (bool, error)
23+
// ValidateRecoveryCode totp: allows user to validate using recovery code incase if they lost their device
24+
ValidateRecoveryCode(ctx context.Context, recoveryCode, userID string) (bool, error)
2525
}

server/authenticators/providers/totp/totp.go

Lines changed: 34 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"bytes"
55
"context"
66
"encoding/json"
7+
"fmt"
78
"image/png"
89
"time"
910

@@ -113,24 +114,38 @@ func (p *provider) Validate(ctx context.Context, passcode string, userID string)
113114
return status, nil
114115
}
115116

116-
// RecoveryCode generates a recovery code for a user's TOTP authentication, if not already verified.
117-
func (p *provider) RecoveryCode(ctx context.Context, id string) (*string, error) {
117+
// ValidateRecoveryCode validates a Time-Based One-Time Password (TOTP) recovery code against the stored TOTP recovery code for a user.
118+
func (p *provider) ValidateRecoveryCode(ctx context.Context, recoveryCode, userID string) (bool, error) {
118119
// get totp details
119-
// totpModel, err := db.Provider.GetAuthenticatorDetailsByUserId(ctx, id, constants.EnvKeyTOTPAuthenticator)
120-
// if err != nil {
121-
// return nil, fmt.Errorf("error while getting totp details from authenticators")
122-
// }
123-
// //TODO *totpModel.RecoveryCode == "null" used to just verify couchbase recoveryCode value to be nil
124-
// // have to find another way round
125-
// if totpModel.RecoveryCode == nil || *totpModel.RecoveryCode == "null" {
126-
// recoveryCode := utils.GenerateTOTPRecoveryCode()
127-
// totpModel.RecoveryCode = &recoveryCode
128-
129-
// _, err = db.Provider.UpdateAuthenticator(ctx, totpModel)
130-
// if err != nil {
131-
// return nil, fmt.Errorf("error while updaing authenticator table for totp")
132-
// }
133-
// return &recoveryCode, nil
134-
// }
135-
return nil, nil
120+
totpModel, err := db.Provider.GetAuthenticatorDetailsByUserId(ctx, userID, constants.EnvKeyTOTPAuthenticator)
121+
if err != nil {
122+
return false, err
123+
}
124+
// convert recoveryCodes to map
125+
recoveryCodesMap := map[string]bool{}
126+
err = json.Unmarshal([]byte(refs.StringValue(totpModel.RecoveryCodes)), &recoveryCodesMap)
127+
if err != nil {
128+
return false, err
129+
}
130+
// check if recovery code is valid
131+
if val, ok := recoveryCodesMap[recoveryCode]; !ok {
132+
return false, fmt.Errorf("invalid recovery code")
133+
} else if val {
134+
return false, fmt.Errorf("recovery code already used")
135+
}
136+
// update recovery code map
137+
recoveryCodesMap[recoveryCode] = true
138+
// convert recoveryCodesMap to string
139+
jsonData, err := json.Marshal(recoveryCodesMap)
140+
if err != nil {
141+
return false, err
142+
}
143+
recoveryCodesString := string(jsonData)
144+
totpModel.RecoveryCodes = refs.NewStringRef(recoveryCodesString)
145+
// update recovery code map in db
146+
_, err = db.Provider.UpdateAuthenticator(ctx, totpModel)
147+
if err != nil {
148+
return false, err
149+
}
150+
return true, nil
136151
}

server/graph/generated/generated.go

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

server/graph/model/models_gen.go

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

server/graph/schema.graphqls

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -573,7 +573,7 @@ input VerifyOTPRequest {
573573
email: String
574574
phone_number: String
575575
otp: String!
576-
totp: Boolean
576+
is_totp: Boolean
577577
# state is used for authorization code grant flow
578578
# it is used to get code for an on-going auth process during login
579579
# and use that code for setting `c_hash` in id_token

server/resolvers/verify_otp.go

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -56,15 +56,25 @@ func VerifyOtpResolver(ctx context.Context, params model.VerifyOTPRequest) (*mod
5656
return res, err
5757
}
5858
// Verify OTP based on TOPT or OTP
59-
if refs.BoolValue(params.Totp) {
59+
if refs.BoolValue(params.IsTotp) {
6060
status, err := authenticators.Provider.Validate(ctx, params.Otp, user.ID)
6161
if err != nil {
6262
log.Debug("Failed to validate totp: ", err)
6363
return nil, fmt.Errorf("error while validating passcode")
6464
}
6565
if !status {
6666
log.Debug("Failed to verify otp request: Incorrect value")
67-
return res, fmt.Errorf(`invalid otp`)
67+
log.Info("Checking if otp is recovery code")
68+
// Check if otp is recovery code
69+
isValidRecoveryCode, err := authenticators.Provider.ValidateRecoveryCode(ctx, params.Otp, user.ID)
70+
if err != nil {
71+
log.Debug("Failed to validate recovery code: ", err)
72+
return nil, fmt.Errorf("error while validating recovery code")
73+
}
74+
if !isValidRecoveryCode {
75+
log.Debug("Failed to verify otp request: Incorrect value")
76+
return res, fmt.Errorf(`invalid otp`)
77+
}
6878
}
6979
} else {
7080
var otp *models.OTP

0 commit comments

Comments
 (0)