mirror of
https://github.com/zitadel/zitadel.git
synced 2025-08-11 19:07:30 +00:00
feat(api/v2): implement TOTP session check (#6362)
* feat(api/v2): implement TOTP session check * add integration test * correct typo in projection test * fix event type typos --------- Co-authored-by: Livio Spring <livio.a@gmail.com>
This commit is contained in:
@@ -7,6 +7,7 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/golang/mock/gomock"
|
||||
"github.com/pquerna/otp/totp"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"golang.org/x/text/language"
|
||||
@@ -695,6 +696,138 @@ func TestCommands_updateSession(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestCheckTOTP(t *testing.T) {
|
||||
ctx := authz.NewMockContext("", "org1", "user1")
|
||||
|
||||
cryptoAlg := crypto.CreateMockEncryptionAlg(gomock.NewController(t))
|
||||
key, secret, err := domain.NewTOTPKey("example.com", "user1", cryptoAlg)
|
||||
require.NoError(t, err)
|
||||
|
||||
sessAgg := &session.NewAggregate("session1", "org1").Aggregate
|
||||
userAgg := &user.NewAggregate("user1", "org1").Aggregate
|
||||
|
||||
code, err := totp.GenerateCode(key.Secret(), testNow)
|
||||
require.NoError(t, err)
|
||||
|
||||
type fields struct {
|
||||
sessionWriteModel *SessionWriteModel
|
||||
eventstore func(*testing.T) *eventstore.Eventstore
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
code string
|
||||
fields fields
|
||||
wantEventCommands []eventstore.Command
|
||||
wantErr error
|
||||
}{
|
||||
{
|
||||
name: "missing userID",
|
||||
code: code,
|
||||
fields: fields{
|
||||
sessionWriteModel: &SessionWriteModel{
|
||||
aggregate: sessAgg,
|
||||
},
|
||||
eventstore: expectEventstore(),
|
||||
},
|
||||
wantErr: caos_errs.ThrowPreconditionFailed(nil, "COMMAND-Neil7", "Errors.User.UserIDMissing"),
|
||||
},
|
||||
{
|
||||
name: "filter error",
|
||||
code: code,
|
||||
fields: fields{
|
||||
sessionWriteModel: &SessionWriteModel{
|
||||
UserID: "user1",
|
||||
UserCheckedAt: testNow,
|
||||
aggregate: sessAgg,
|
||||
},
|
||||
eventstore: expectEventstore(
|
||||
expectFilterError(io.ErrClosedPipe),
|
||||
),
|
||||
},
|
||||
wantErr: io.ErrClosedPipe,
|
||||
},
|
||||
{
|
||||
name: "otp not ready error",
|
||||
code: code,
|
||||
fields: fields{
|
||||
sessionWriteModel: &SessionWriteModel{
|
||||
UserID: "user1",
|
||||
UserCheckedAt: testNow,
|
||||
aggregate: sessAgg,
|
||||
},
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
user.NewHumanOTPAddedEvent(ctx, userAgg, secret),
|
||||
),
|
||||
),
|
||||
),
|
||||
},
|
||||
wantErr: caos_errs.ThrowPreconditionFailed(nil, "COMMAND-eej1U", "Errors.User.MFA.OTP.NotReady"),
|
||||
},
|
||||
{
|
||||
name: "otp verify error",
|
||||
code: "foobar",
|
||||
fields: fields{
|
||||
sessionWriteModel: &SessionWriteModel{
|
||||
UserID: "user1",
|
||||
UserCheckedAt: testNow,
|
||||
aggregate: sessAgg,
|
||||
},
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
user.NewHumanOTPAddedEvent(ctx, userAgg, secret),
|
||||
),
|
||||
eventFromEventPusher(
|
||||
user.NewHumanOTPVerifiedEvent(ctx, userAgg, "agent1"),
|
||||
),
|
||||
),
|
||||
),
|
||||
},
|
||||
wantErr: caos_errs.ThrowInvalidArgument(nil, "EVENT-8isk2", "Errors.User.MFA.OTP.InvalidCode"),
|
||||
},
|
||||
{
|
||||
name: "ok",
|
||||
code: code,
|
||||
fields: fields{
|
||||
sessionWriteModel: &SessionWriteModel{
|
||||
UserID: "user1",
|
||||
UserCheckedAt: testNow,
|
||||
aggregate: sessAgg,
|
||||
},
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
user.NewHumanOTPAddedEvent(ctx, userAgg, secret),
|
||||
),
|
||||
eventFromEventPusher(
|
||||
user.NewHumanOTPVerifiedEvent(ctx, userAgg, "agent1"),
|
||||
),
|
||||
),
|
||||
),
|
||||
},
|
||||
wantEventCommands: []eventstore.Command{
|
||||
session.NewTOTPCheckedEvent(ctx, sessAgg, testNow),
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
cmd := &SessionCommands{
|
||||
sessionWriteModel: tt.fields.sessionWriteModel,
|
||||
eventstore: tt.fields.eventstore(t),
|
||||
totpAlg: cryptoAlg,
|
||||
now: func() time.Time { return testNow },
|
||||
}
|
||||
err := CheckTOTP(tt.code)(ctx, cmd)
|
||||
require.ErrorIs(t, err, tt.wantErr)
|
||||
assert.Equal(t, tt.wantEventCommands, cmd.eventCommands)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestCommands_TerminateSession(t *testing.T) {
|
||||
type fields struct {
|
||||
eventstore *eventstore.Eventstore
|
||||
|
Reference in New Issue
Block a user