Skip to content

Commit

Permalink
Add test for multiple auth attempts & return error if too many (#62)
Browse files Browse the repository at this point in the history
* proto: add a TooManyAttempts error

* rpc: test auth with multiple and too many attempts

* rpc: return an error if auth attempted more than 3 times
  • Loading branch information
patrislav authored Jul 31, 2024
1 parent f82a71b commit 85276f6
Show file tree
Hide file tree
Showing 6 changed files with 93 additions and 21 deletions.
5 changes: 3 additions & 2 deletions proto/authenticator.gen.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions proto/authenticator.ridl
Original file line number Diff line number Diff line change
Expand Up @@ -201,6 +201,7 @@ error 7001 AccountAlreadyLinked "Could not link account as it is linked to anoth
error 7002 ProofVerificationFailed "The authentication proof could not be verified" HTTP 400
error 7003 AnswerIncorrect "The provided answer is incorrect" HTTP 400
error 7004 ChallengeExpired "The challenge has expired" HTTP 400
error 7005 TooManyAttempts "Too many attempts" HTTP 400


##
Expand Down
5 changes: 3 additions & 2 deletions proto/clients/authenticator.gen.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

19 changes: 17 additions & 2 deletions proto/clients/authenticator.gen.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/* eslint-disable */
// sequence-waas-authenticator v0.1.0 a61eba85f37d76e045a3cf6657005e84edaf5785
// sequence-waas-authenticator v0.1.0 35f86317a98af91896d1114ad52dd22102d9de9f
// --
// Code generated by [email protected] with typescript generator. DO NOT EDIT.
//
Expand All @@ -12,7 +12,7 @@ export const WebRPCVersion = "v1"
export const WebRPCSchemaVersion = "v0.1.0"

// Schema hash generated from your RIDL schema
export const WebRPCSchemaHash = "a61eba85f37d76e045a3cf6657005e84edaf5785"
export const WebRPCSchemaHash = "35f86317a98af91896d1114ad52dd22102d9de9f"

//
// Types
Expand Down Expand Up @@ -768,6 +768,19 @@ export class ChallengeExpiredError extends WebrpcError {
}
}

export class TooManyAttemptsError extends WebrpcError {
constructor(
name: string = 'TooManyAttempts',
code: number = 7005,
message: string = 'Too many attempts',
status: number = 0,
cause?: string
) {
super(name, code, message, status, cause)
Object.setPrototypeOf(this, TooManyAttemptsError.prototype)
}
}


export enum errors {
WebrpcEndpoint = 'WebrpcEndpoint',
Expand All @@ -788,6 +801,7 @@ export enum errors {
ProofVerificationFailed = 'ProofVerificationFailed',
AnswerIncorrect = 'AnswerIncorrect',
ChallengeExpired = 'ChallengeExpired',
TooManyAttempts = 'TooManyAttempts',
}

const webrpcErrorByCode: { [code: number]: any } = {
Expand All @@ -809,6 +823,7 @@ const webrpcErrorByCode: { [code: number]: any } = {
[7002]: ProofVerificationFailedError,
[7003]: AnswerIncorrectError,
[7004]: ChallengeExpiredError,
[7005]: TooManyAttemptsError,
}

export type Fetch = (input: RequestInfo, init?: RequestInit) => Promise<Response>
Expand Down
80 changes: 65 additions & 15 deletions rpc/auth_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,13 @@ import (

func TestEmailAuth(t *testing.T) {
type assertionParams struct {
tenant *data.Tenant
email string
tenant *data.Tenant
email string
attempt int
}

testCases := map[string]struct {
retryAttempts int
emailBuilderFn func(t *testing.T, p assertionParams) string
assertInitiateAuthFn func(t *testing.T, res *proto.IntentResponse, err error) bool
extractAnswerFn func(t *testing.T, p assertionParams, res *proto.IntentResponse) string
Expand Down Expand Up @@ -85,6 +87,50 @@ func TestEmailAuth(t *testing.T) {
require.ErrorContains(t, err, "incorrect answer")
},
},
"MultipleAttempts": {
retryAttempts: 2,
assertInitiateAuthFn: func(t *testing.T, res *proto.IntentResponse, err error) bool {
return true
},
extractAnswerFn: func(t *testing.T, p assertionParams, res *proto.IntentResponse) string {
if p.attempt < 2 {
return "Wrong"
}
_, message, found := getSentEmailMessage(t, fmt.Sprintf("user+%[email protected]", p.tenant.ProjectID))
require.True(t, found)
return strings.TrimPrefix(message, "Your login code: ")
},
assertRegisterSessionFn: func(t *testing.T, p assertionParams, sess *proto.Session, res *proto.IntentResponse, err error) {
if p.attempt < 2 {
require.ErrorContains(t, err, "incorrect answer")
return
}
expectedIdentity := newEmailIdentity(fmt.Sprintf("user+%[email protected]", p.tenant.ProjectID))
require.NoError(t, err)
assert.Equal(t, expectedIdentity, sess.Identity)
},
},
"TooManyAttempts": {
retryAttempts: 10,
assertInitiateAuthFn: func(t *testing.T, res *proto.IntentResponse, err error) bool {
return true
},
extractAnswerFn: func(t *testing.T, p assertionParams, res *proto.IntentResponse) string {
if p.attempt < 3 {
return "Wrong"
}
_, message, found := getSentEmailMessage(t, fmt.Sprintf("user+%[email protected]", p.tenant.ProjectID))
require.True(t, found)
return strings.TrimPrefix(message, "Your login code: ")
},
assertRegisterSessionFn: func(t *testing.T, p assertionParams, sess *proto.Session, res *proto.IntentResponse, err error) {
if p.attempt < 3 {
require.ErrorContains(t, err, "incorrect answer")
} else {
require.ErrorContains(t, err, "Too many attempts")
}
},
},
}

for name, testCase := range testCases {
Expand Down Expand Up @@ -143,21 +189,25 @@ func TestEmailAuth(t *testing.T) {
}
}

code := testCase.extractAnswerFn(t, p, initiateAuthRes)
challenge := initiateAuthRes.Data.(map[string]any)["challenge"].(string)
answer := hexutil.Encode(crypto.Keccak256([]byte(challenge + code)))
for attempt := 0; attempt < testCase.retryAttempts+1; attempt++ {
p.attempt = attempt

openSessionData := intents.IntentDataOpenSession{
SessionID: signingSession.SessionID(),
IdentityType: intents.IdentityType_Email,
Verifier: p.email + ";" + signingSession.SessionID(),
Answer: answer,
}
openSession := generateSignedIntent(t, intents.IntentName_openSession, openSessionData, signingSession)
code := testCase.extractAnswerFn(t, p, initiateAuthRes)
challenge := initiateAuthRes.Data.(map[string]any)["challenge"].(string)
answer := hexutil.Encode(crypto.Keccak256([]byte(challenge + code)))

openSessionData := intents.IntentDataOpenSession{
SessionID: signingSession.SessionID(),
IdentityType: intents.IdentityType_Email,
Verifier: p.email + ";" + signingSession.SessionID(),
Answer: answer,
}
openSession := generateSignedIntent(t, intents.IntentName_openSession, openSessionData, signingSession)

session, openSessionRes, err := c.RegisterSession(ctx, openSession, "friendly name")
if testCase.assertRegisterSessionFn != nil {
testCase.assertRegisterSessionFn(t, p, session, openSessionRes, err)
session, openSessionRes, err := c.RegisterSession(ctx, openSession, "friendly name")
if testCase.assertRegisterSessionFn != nil {
testCase.assertRegisterSessionFn(t, p, session, openSessionRes, err)
}
}
})
}
Expand Down
4 changes: 4 additions & 0 deletions rpc/sessions.go
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,10 @@ func (s *RPC) RegisterSession(
return nil, nil, proto.ErrWebrpcInternalError.WithCausef("decrypting verification context data: %w", err)
}

if verifCtx.Attempts >= 3 {
return nil, nil, proto.ErrTooManyAttempts
}

if time.Now().After(verifCtx.ExpiresAt) {
return nil, nil, proto.ErrChallengeExpired
}
Expand Down

0 comments on commit 85276f6

Please sign in to comment.