Skip to content

Commit

Permalink
feat: wip login with code and cleanup
Browse files Browse the repository at this point in the history
  • Loading branch information
Benehiko committed Jul 17, 2023
1 parent 8d6e952 commit ffb2035
Show file tree
Hide file tree
Showing 32 changed files with 423 additions and 79 deletions.
4 changes: 4 additions & 0 deletions driver/registry_default.go
Original file line number Diff line number Diff line change
Expand Up @@ -755,6 +755,10 @@ func (m *RegistryDefault) RegistrationCodePersister() code.RegistrationCodePersi
return m.Persister()
}

func (m *RegistryDefault) LoginCodePersister() code.LoginCodePersister {
return m.Persister()
}

func (m *RegistryDefault) Persister() persistence.Persister {
return m.persister
}
Expand Down
1 change: 1 addition & 0 deletions persistence/reference.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ type Persister interface {
code.RecoveryCodePersister
code.VerificationCodePersister
code.RegistrationCodePersister
code.LoginCodePersister

CleanupDatabase(context.Context, time.Duration, time.Duration, int) error
Close(context.Context) error
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"id": "f1f66a69-ce02-4a12-9591-9e02dda30a0d",
"expires_at": "2022-08-18T08:28:18Z",
"issued_at": "2022-08-18T07:28:18Z"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
{
"id": "69c80296-36cd-4afc-921a-15369cac5bf0",
"type": "browser",
"expires_at": "2013-10-07T08:23:19Z",
"issued_at": "2013-10-07T08:23:19Z",
"request_url": "http://kratos:4433/self-service/browser/flows/registration?login_challenge/self-service/browser/flows/registration?login_challenge/self-service/browser/flows/registration?login_challenge/self-service/browser/flows/registration?login_challenge/self-service/browser/flows/registration?login_challenge/self-service/browser/flows/registration?login_challenge/self-service/browser/flows/registration?login_challenge/self-service/browser/flows/registration?login_challenge/self-service/browser/flows/registration?login_challenge/self-service/browser/flows/registration?login_challenge/self-service/browser/flows/registration?login_challenge/self-service/browser/flows/registration?login_challenge/self-service/browser/flows/registration?login_challenge/self-service/browser/flows/registration?login_challenge/self-service/browser/flows/registration?login_challenge/self-service/browser/flows/registration?login_challenge/self-service/browser/flows/registration?login_challenge/self-service/browser/flows/registration?login_challenge/self-service/browser/flows/registration?login_challenge/self-service/browser/flows/registration?login_challenge/self-service/browser/flows/registration?login_challenge/self-service/browser/flows/registration?login_challenge/self-service/browser/flows/registration?login_challenge/self-service/browser/flows/registration?login_challenge/self-service/browser/flows/registration?login_challenge/self-service/browser/flows/registration?login_challenge/self-service/browser/flows/registration?login_challenge=",
"active": "password",
"ui": {
"action": "",
"method": "",
"nodes": null
},
"state": ""
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,5 +9,6 @@
"action": "",
"method": "",
"nodes": null
}
},
"state": ""
}
38 changes: 36 additions & 2 deletions persistence/sql/migratest/migration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,8 @@ func CompareWithFixture(t *testing.T, actual interface{}, prefix string, id stri
func TestMigrations_SQLite(t *testing.T) {
t.Parallel()
sqlite, err := pop.NewConnection(&pop.ConnectionDetails{
URL: "sqlite3://" + filepath.Join(os.TempDir(), x.NewUUID().String()) + ".sql?_fk=true"})
URL: "sqlite3://" + filepath.Join(os.TempDir(), x.NewUUID().String()) + ".sql?_fk=true",
})
require.NoError(t, err)
require.NoError(t, sqlite.Open())

Expand Down Expand Up @@ -105,7 +106,6 @@ func TestMigrations_Cockroach(t *testing.T) {
}

func testDatabase(t *testing.T, db string, c *pop.Connection) {

ctx := context.Background()
l := logrusx.New("", "", logrusx.ForceLevel(logrus.ErrorLevel))

Expand Down Expand Up @@ -372,6 +372,40 @@ func testDatabase(t *testing.T, db string, c *pop.Connection) {
migratest.ContainsExpectedIds(t, filepath.Join("fixtures", "recovery_code"), found)
})

t.Run("case=registration_code", func(t *testing.T) {
wg.Add(1)
defer wg.Done()
t.Parallel()

var ids []code.RegistrationCode
require.NoError(t, c.All(&ids))
require.NotEmpty(t, ids)

var found []string
for _, id := range ids {
found = append(found, id.ID.String())
CompareWithFixture(t, id, "registration_code", id.ID.String())
}
migratest.ContainsExpectedIds(t, filepath.Join("fixtures", "registration_code"), found)
})

t.Run("case=login_code", func(t *testing.T) {
wg.Add(1)
defer wg.Done()
t.Parallel()

var ids []code.LoginCode
require.NoError(t, c.All(&ids))
require.NotEmpty(t, ids)

var found []string
for _, id := range ids {
found = append(found, id.ID.String())
CompareWithFixture(t, id, "login_code", id.ID.String())
}
migratest.ContainsExpectedIds(t, filepath.Join("fixtures", "login_code"), found)
})

t.Run("suite=constraints", func(t *testing.T) {
// This is not really a parallel test, but we have to mark it parallel so the other tests run first.
t.Parallel()
Expand Down
23 changes: 23 additions & 0 deletions persistence/sql/migratest/testdata/20230707133700_testdata.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
INSERT INTO selfservice_login_flows (id, nid, request_url, issued_at, expires_at, active_method, csrf_token, created_at,
updated_at, forced, type, ui, internal_context, oauth2_login_challenge_data)
VALUES ('00b1517f-2467-4aaf-b0a5-82b4a27dcaf5',
'0c175792-3aad-4795-ad03-972e8a88f94c',
'http://kratos:4433/self-service/browser/flows/login/self-service/browser/flows/login/self-service/browser/flows/login/self-service/browser/flows/login/self-service/browser/flows/login/self-service/browser/flows/login/self-service/browser/flows/login/self-service/browser/flows/login/self-service/browser/flows/login/self-service/browser/flows/login/self-service/browser/flows/login/self-service/browser/flows/login/self-service/browser/flows/login/self-service/browser/flows/login/self-service/browser/flows/login/self-service/browser/flows/login/self-service/browser/flows/login/self-service/browser/flows/login/self-service/browser/flows/login/self-service/browser/flows/login/self-service/browser/flows/login/self-service/browser/flows/login/self-service/browser/flows/login/self-service/browser/flows/login/self-service/browser/flows/login/self-service/browser/flows/login/self-service/browser/flows/login/self-service/browser/flows/login/self-service/browser/flows/login/self-service/browser/flows/login/self-service/browser/flows/login/self-service/browser/flows/login/self-service/browser/flows/login/self-service/browser/flows/login/self-service/browser/flows/login/self-service/browser/flows/login/self-service/browser/flows/login/self-service/browser/flows/login/self-service/browser/flows/login/self-service/browser/flows/login',
'2013-10-07 08:23:19', '2013-10-07 08:23:19', '',
'fpeVSZ9ZH7YvUkhXsOVEIssxbfauh5lcoQSYxTcN0XkMneg1L42h+HtvisjlNjBF4ElcD2jApCHoJYq2u9sVWg==',
'2013-10-07 08:23:19', '2013-10-07 08:23:19', false, 'api', '{}', '{"foo":"bar"}', 'challenge data');


INSERT INTO identity_login_codes (id, code, used_at, expires_at, issued_at, selfservice_login_flow_id,
identity_verifiable_address_id, created_at, updated_at, nid)
VALUES ('bd292366-af32-4ba6-bdf0-11d6d1a217f3',
'7eb71370d8497734ec78dfe613bf0f08967e206d2b5c2fc1243be823cfcd57a7',
null,
'2022-08-18 08:28:18',
'2022-08-18 07:28:18',
'00b1517f-2467-4aaf-b0a5-82b4a27dcaf5',
'd4718a67-aec2-418d-8173-6ebc7bde3b86',
'2022-08-18 07:28:18',
'2022-08-18 07:28:18',
'0c175792-3aad-4795-ad03-972e8a88f94c'
)
22 changes: 22 additions & 0 deletions persistence/sql/migratest/testdata/20230707133701_testdata.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
INSERT INTO selfservice_registration_flows (id, nid, request_url, issued_at, expires_at, active_method, csrf_token,
created_at, updated_at, type, ui, internal_context, oauth2_login_challenge)
VALUES ('69c80296-36cd-4afc-921a-15369cac5bf0', '884f556e-eb3a-4b9f-bee3-11345642c6c0',
'http://kratos:4433/self-service/browser/flows/registration?login_challenge/self-service/browser/flows/registration?login_challenge/self-service/browser/flows/registration?login_challenge/self-service/browser/flows/registration?login_challenge/self-service/browser/flows/registration?login_challenge/self-service/browser/flows/registration?login_challenge/self-service/browser/flows/registration?login_challenge/self-service/browser/flows/registration?login_challenge/self-service/browser/flows/registration?login_challenge/self-service/browser/flows/registration?login_challenge/self-service/browser/flows/registration?login_challenge/self-service/browser/flows/registration?login_challenge/self-service/browser/flows/registration?login_challenge/self-service/browser/flows/registration?login_challenge/self-service/browser/flows/registration?login_challenge/self-service/browser/flows/registration?login_challenge/self-service/browser/flows/registration?login_challenge/self-service/browser/flows/registration?login_challenge/self-service/browser/flows/registration?login_challenge/self-service/browser/flows/registration?login_challenge/self-service/browser/flows/registration?login_challenge/self-service/browser/flows/registration?login_challenge/self-service/browser/flows/registration?login_challenge/self-service/browser/flows/registration?login_challenge/self-service/browser/flows/registration?login_challenge/self-service/browser/flows/registration?login_challenge/self-service/browser/flows/registration?login_challenge=',
'2013-10-07 08:23:19', '2013-10-07 08:23:19',
'password', 'vYYuhWXBfXKzBC+BlnbDmXfBKsUWY6SU/v04gHF9GYzPjFP51RXDPOc57R7Dpbf+XLkbPNAkmem33Crz/avdrw==',
'2013-10-07 08:23:19', '2013-10-07 08:23:19', 'browser', '{}', '{"foo":"bar"}',
'3caddfd5-9903-4bce-83ff-cae36f42dff7');

INSERT INTO identity_registration_codes (id, address, code, used_at, expires_at, issued_at, selfservice_registration_flow_id,
created_at, updated_at, nid)
VALUES ('f1f66a69-ce02-4a12-9591-9e02dda30a0d',
'[email protected]',
'7eb71370d8497734ec78dfe613bf0f08967e206d2b5c2fc1243be823cfcd57a7',
null,
'2022-08-18 08:28:18',
'2022-08-18 07:28:18',
'69c80296-36cd-4afc-921a-15369cac5bf0',
'2022-08-18 07:28:18',
'2022-08-18 07:28:18',
'884f556e-eb3a-4b9f-bee3-11345642c6c0'
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
DROP TABLE identity_login_codes;

ALTER TABLE selfservice_login_flows DROP submit_count;
ALTER TABLE selfservice_login_flows DROP skip_csrf_check;
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
DROP TABLE identity_login_codes;

ALTER TABLE selfservice_login_flows DROP submit_count;
ALTER TABLE selfservice_login_flows DROP skip_csrf_check;
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
CREATE TABLE identity_login_codes
(
id CHAR(36) NOT NULL PRIMARY KEY,
code VARCHAR (64) NOT NULL, -- HMACed value of the actual code
used_at timestamp NULL DEFAULT NULL,
expires_at timestamp NOT NULL DEFAULT '2000-01-01 00:00:00',
issued_at timestamp NOT NULL DEFAULT '2000-01-01 00:00:00',
selfservice_login_flow_id CHAR(36),
identity_verifiable_address_id CHAR(36),
created_at timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
nid CHAR(36) NOT NULL,
CONSTRAINT identity_login_codes_selfservice_login_flows_id_fk
FOREIGN KEY (selfservice_login_flow_id)
REFERENCES selfservice_login_flows (id)
ON DELETE cascade,
CONSTRAINT identity_login_codes_networks_id_fk
FOREIGN KEY (nid)
REFERENCES networks (id)
ON UPDATE RESTRICT ON DELETE CASCADE,
CONSTRAINT identity_login_codes_identity_verifiable_addresses_id_fk
FOREIGN KEY (identity_verifiable_address_id)
REFERENCES identity_verifiable_addresses (id)
ON DELETE cascade
);

CREATE INDEX identity_login_codes_nid_flow_id_idx ON identity_login_codes (nid, selfservice_login_flow_id);
CREATE INDEX identity_login_codes_identity_verifiable_address_id_idx ON identity_login_codes (identity_verifiable_address_id);
CREATE INDEX identity_login_codes_id_nid_idx ON identity_login_codes (id, nid);


ALTER TABLE selfservice_login_flows ADD submit_count int NOT NULL DEFAULT 0;
ALTER TABLE selfservice_login_flows ADD skip_csrf_check boolean NOT NULL DEFAULT FALSE;
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
CREATE TABLE identity_login_codes
(
id UUID NOT NULL PRIMARY KEY,
code VARCHAR (64) NOT NULL, -- HMACed value of the actual code
used_at timestamp NULL DEFAULT NULL,
expires_at timestamp NOT NULL DEFAULT '2000-01-01 00:00:00',
issued_at timestamp NOT NULL DEFAULT '2000-01-01 00:00:00',
selfservice_login_flow_id UUID NOT NULL,
identity_verifiable_address_id UUID NOT NULL,
created_at timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
nid UUID NOT NULL,
CONSTRAINT identity_login_codes_selfservice_login_flows_id_fk
FOREIGN KEY (selfservice_login_flow_id)
REFERENCES selfservice_login_flows (id)
ON DELETE cascade,
CONSTRAINT identity_login_codes_networks_id_fk
FOREIGN KEY (nid)
REFERENCES networks (id)
ON UPDATE RESTRICT ON DELETE CASCADE,
CONSTRAINT identity_login_codes_identity_verifiable_addresses_id_fk
FOREIGN KEY (identity_verifiable_address_id)
REFERENCES identity_verifiable_addresses (id)
ON DELETE cascade
);

CREATE INDEX identity_login_codes_nid_flow_id_idx ON identity_login_codes (nid, selfservice_login_flow_id);
CREATE INDEX identity_login_codes_identity_verifiable_address_id_idx ON identity_login_codes (identity_verifiable_address_id);
CREATE INDEX identity_login_codes_id_nid_idx ON identity_login_codes (id, nid);

ALTER TABLE selfservice_login_flows ADD submit_count int NOT NULL DEFAULT 0;
ALTER TABLE selfservice_login_flows ADD skip_csrf_check boolean NOT NULL DEFAULT FALSE;
124 changes: 124 additions & 0 deletions persistence/sql/persister_login.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,22 @@ package sql

import (
"context"
"crypto/subtle"
"fmt"
"time"

"github.com/gobuffalo/pop/v6"
"github.com/pkg/errors"

"github.com/gofrs/uuid"

"github.com/ory/x/sqlcon"

"github.com/ory/kratos/identity"
"github.com/ory/kratos/persistence/sql/update"
"github.com/ory/kratos/selfservice/flow"
"github.com/ory/kratos/selfservice/flow/login"
"github.com/ory/kratos/selfservice/strategy/code"
)

var _ login.FlowPersister = new(Persister)
Expand Down Expand Up @@ -84,3 +89,122 @@ func (p *Persister) DeleteExpiredLoginFlows(ctx context.Context, expiresAt time.
}
return nil
}

func (p *Persister) CreateLoginCode(ctx context.Context, codeParams *code.CreateLoginCodeParams) (*code.LoginCode, error) {
ctx, span := p.r.Tracer(ctx).Tracer().Start(ctx, "persistence.sql.CreateLoginCode")
defer span.End()

now := time.Now()

loginCode := &code.LoginCode{
VerifiableAddressID: uuid.NullUUID{UUID: codeParams.VerifiableAddress.ID, Valid: true},
CodeHMAC: p.hmacValue(ctx, codeParams.RawCode),
IssuedAt: now,
ExpiresAt: now.UTC().Add(p.r.Config().SelfServiceCodeMethodLifespan(ctx)),
FlowID: codeParams.FlowID,
NID: p.NetworkID(ctx),
ID: uuid.Nil,
}

if err := p.GetConnection(ctx).Create(loginCode); err != nil {
return nil, sqlcon.HandleError(err)
}
return loginCode, nil
}

func (p *Persister) UseLoginCode(ctx context.Context, flowID uuid.UUID, codeVal string) (*code.LoginCode, error) {
ctx, span := p.r.Tracer(ctx).Tracer().Start(ctx, "persistence.sql.UseLoginCode")
defer span.End()

var loginCode *code.LoginCode

nid := p.NetworkID(ctx)
flowTableName := new(login.Flow).TableName(ctx)

if err := sqlcon.HandleError(p.Transaction(ctx, func(ctx context.Context, tx *pop.Connection) (err error) {
//#nosec G201 -- TableName is static
if err := sqlcon.HandleError(tx.RawQuery(fmt.Sprintf("UPDATE %s SET submit_count = submit_count + 1 WHERE id = ? AND nid = ?", flowTableName), flowID, nid).Exec()); err != nil {
return err
}

var submitCount int
// Because MySQL does not support "RETURNING" clauses, but we need the updated `submit_count` later on.
//#nosec G201 -- TableName is static
if err := sqlcon.HandleError(tx.RawQuery(fmt.Sprintf("SELECT submit_count FROM %s WHERE id = ? AND nid = ?", flowTableName), flowID, nid).First(&submitCount)); err != nil {
if errors.Is(err, sqlcon.ErrNoRows) {
// Return no error, as that would roll back the transaction
return nil
}
return err
}

if submitCount > 5 {
return errors.WithStack(code.ErrCodeSubmittedTooOften)
}

var loginCodes []code.LoginCode
if err = sqlcon.HandleError(tx.Where("nid = ? AND selfservice_login_flow_id = ?", nid, flowID).All(&loginCodes)); err != nil {
if errors.Is(err, sqlcon.ErrNoRows) {
return err
}
return nil
}

secrets:

for _, secret := range p.r.Config().SecretsSession(ctx) {
suppliedCode := []byte(p.hmacValueWithSecret(ctx, codeVal, secret))
for i := range loginCodes {
code := loginCodes[i]
if subtle.ConstantTimeCompare([]byte(code.CodeHMAC), suppliedCode) == 0 {
// Not the supplied code
continue
}
loginCode = &code
break secrets
}
}

if loginCode == nil || !loginCode.IsValid() {
// Return no error, as that would roll back the transaction
return nil
}

var verifiableAddress identity.VerifiableAddress
if err := tx.Where("nid = ? AND id = ?", nid, loginCode.VerifiableAddressID).First(&verifiableAddress); err != nil {
if err = sqlcon.HandleError(err); !errors.Is(err, sqlcon.ErrNoRows) {
return err
}
return err
}

loginCode.VerifiableAddress = &verifiableAddress

//#nosec G201 -- TableName is static
return sqlcon.HandleError(tx.RawQuery(fmt.Sprintf("UPDATE %s SET used_at = ? WHERE id = ? AND nid = ?", loginCode.TableName(ctx)), time.Now().UTC(), loginCode.ID, nid).Exec())
})); err != nil {
return nil, err
}

if loginCode == nil {
return nil, code.ErrCodeNotFound
}

if loginCode.IsExpired() {
return nil, flow.NewFlowExpiredError(loginCode.ExpiresAt)
}

if loginCode.WasUsed() {
return nil, code.ErrCodeAlreadyUsed
}

return nil, nil
}

func (p *Persister) DeleteLoginCodesOfFlow(ctx context.Context, flowID uuid.UUID) error {
ctx, span := p.r.Tracer(ctx).Tracer().Start(ctx, "persistence.sql.DeleteLoginCodesOfFlow")
defer span.End()

//#nosec G201 -- TableName is static
return p.GetConnection(ctx).RawQuery(fmt.Sprintf("DELETE FROM %s WHERE selfservice_login_flow_id = ? AND nid = ?", new(code.LoginCode).TableName(ctx)), flowID, p.NetworkID(ctx)).Exec()
}
1 change: 1 addition & 0 deletions selfservice/flow/login/sort.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ func sortNodes(ctx context.Context, n node.Nodes) error {
node.OpenIDConnectGroup,
node.DefaultGroup,
node.WebAuthnGroup,
node.CodeGroup,
node.PasswordGroup,
node.TOTPGroup,
node.LookupGroup,
Expand Down
Loading

0 comments on commit ffb2035

Please sign in to comment.