Skip to content

Commit

Permalink
wip: setup csrf token generation
Browse files Browse the repository at this point in the history
  • Loading branch information
katallaxie authored Oct 28, 2024
1 parent 33b11b1 commit 784b987
Show file tree
Hide file tree
Showing 9 changed files with 344 additions and 113 deletions.
6 changes: 2 additions & 4 deletions .devcontainer/devcontainer.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,7 @@
"postCreateCommand": "bash scripts/postCreateCommand.sh",
"features": {
"ghcr.io/devcontainers/features/docker-in-docker:2": {},
"ghcr.io/devcontainers/features/go:1": {
"version": "1.22"
}
"ghcr.io/devcontainers/features/go:1": {}
},
"forwardPorts": [
3000
Expand All @@ -38,4 +36,4 @@
"onAutoForward": "openPreview"
}
}
}
}
64 changes: 30 additions & 34 deletions .golangci.yml
Original file line number Diff line number Diff line change
@@ -1,15 +1,10 @@
run:
deadline: 6m

skip-files:
- "zz_generated\\..+\\.go$"

skip-dirs:
- vendor$
timeout: 6m

output:
# colored-line-number|line-number|json|tab|checkstyle|code-climate, default is "colored-line-number"
format: colored-line-number
formats:
- format: colored-line-number

linters-settings:
errcheck:
Expand All @@ -21,19 +16,15 @@ linters-settings:
# default is false: such cases aren't reported by default.
check-blank: false

# [deprecated] comma-separated list of pairs of the form pkg:regex
# the regex is used to ignore names within pkg. (default "fmt:.*").
# see https://github.com/kisielk/errcheck#the-deprecated-method for details
ignore: fmt:.*,io/ioutil:^Read.*
# report about not checking of errors in assignments: `num, err := strconv.Atoi(numStr)`;
exclude-functions:
- fmt:.*
- io/ioutil:^Read.*

govet:
# report about shadowed variables
check-shadowing: false

golint:
# minimal confidence for issues, default is 0.8
min-confidence: 0.8

gofmt:
# simplify code: gofmt with `-s` option, true by default
simplify: true
Expand All @@ -47,10 +38,6 @@ linters-settings:
# minimal code complexity to report, 30 by default (but we recommend 10-20)
min-complexity: 10

maligned:
# print struct with more effective memory layout or not, false by default
suggest-new: true

dupl:
# tokens count to trigger issue, 150 by default
threshold: 100
Expand Down Expand Up @@ -109,35 +96,39 @@ linters-settings:
severity: warning
confidence: 0.8


linters:
enable:
- megacheck
- govet
- gocyclo
- gocritic
- gosimple
- staticcheck
- unused
- goconst
- goimports
- gofmt # We enable this as well as goimports for its simplify mode.
- gofmt # We enable this as well as goimports for its simplify mode.
- prealloc
- revive
- unconvert
- misspell
- nakedret
- exportloopref
- copyloopvar
- gosec

disable:
- scopelint
- errcheck

presets:
- bugs
- unused
fast: false


issues:
exclude-files:
- "zz_generated\\..+\\.go$"
exclude-dirs:
- vendor$

exclude:
- "G103: Use of unsafe calls should be audited"

Expand All @@ -164,31 +155,36 @@ issues:
# rather than using a pointer.
- text: "(hugeParam|rangeValCopy):"
linters:
- gocritic
- gocritic

# This "TestMain should call os.Exit to set exit code" warning is not clever
# enough to notice that we call a helper method that calls os.Exit.
- text: "SA3000:"
linters:
- staticcheck
- staticcheck

- text: "k8s.io/api/core/v1"
linters:
- goimports
- goimports

# This is a "potential hardcoded credentials" warning. It's triggered by
# any variable with 'secret' in the same, and thus hits a lot of false
# positives in Kubernetes land where a Secret is an object type.
- text: "G101:"
linters:
- gosec
- gas
- gosec
- gas

# This is an 'errors unhandled' warning that duplicates errcheck.
- text: "G104:"
linters:
- gosec
- gas
- gosec
- gas

- text: "G115:"
linters:
- gosec
- gas

# Independently from option `exclude` we use default exclude patterns,
# it can be disabled by this option. To list all
Expand All @@ -208,4 +204,4 @@ issues:
max-per-linter: 0

# Maximum count of issues with the same text. Set to 0 to disable. Default is 3.
max-same-issues: 0
max-same-issues: 0
77 changes: 21 additions & 56 deletions adapters/adapter.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ func init() {
gob.Register(&GothAccount{})
gob.Register(&GothUser{})
gob.Register(&GothSession{})
gob.Register(&GothTeam{})
gob.Register(&GothVerificationToken{})
gob.Register(&GothCsrfToken{})
}
Expand Down Expand Up @@ -103,8 +102,6 @@ type GothUser struct {
Accounts []GothAccount `json:"accounts" gorm:"foreignKey:UserID;constraint:OnDelete:CASCADE"`
// Sessions are the sessions of the user.
Sessions []GothSession `json:"sessions" gorm:"foreignKey:UserID;constraint:OnDelete:CASCADE"`
// Teams are the teams the user is a member of.
Teams *[]GothTeam `json:"teams" gorm:"many2many:goth_team_users"`
// CreatedAt is the creation time of the user.
CreatedAt time.Time `json:"created_at"`
// UpdatedAt is the update time of the user.
Expand All @@ -113,17 +110,6 @@ type GothUser struct {
DeletedAt gorm.DeletedAt `json:"deleted_at"`
}

// TeamBySlug is returning the team with the given ID.
func (u GothUser) TeamBySlug(slug string) GothTeam {
for _, team := range *u.Teams {
if team.Slug == slug {
return team
}
}

return GothTeam{}
}

// GothSession is a session for a user.
type GothSession struct {
// ID is the unique identifier of the session.
Expand Down Expand Up @@ -190,48 +176,6 @@ type GothVerificationToken struct {
DeletedAt gorm.DeletedAt `json:"deleted_at"`
}

// GothTeam is a team in the application.
type GothTeam struct {
// ID is the unique identifier of the team.
ID uuid.UUID `json:"id" gorm:"primaryKey;unique;type:uuid;column:id;default:gen_random_uuid()"`
// Name is the name of the team.
Name string `json:"name" validate:"required,max=255"`
// Slug is the slug of the team.
Slug string `json:"slug" validate:"required,min=3,max=255"`
// Description is the description of the team.
Description string `json:"description" validate:"max=255"`
// Users are the users in the team.
Users []GothUser `json:"users" gorm:"many2many:goth_team_users"`
// Roles are the roles in the team.
Roles []GothRole `json:"roles" gorm:"foreignKey:TeamID;constraint:OnDelete:CASCADE"`
// CreatedAt is the creation time of the team.
CreatedAt time.Time `json:"created_at"`
// UpdatedAt is the update time of the team.
UpdatedAt time.Time `json:"updated_at"`
// DeletedAt is the deletion time of the team.
DeletedAt gorm.DeletedAt `json:"deleted_at"`
}

// GothRole is a role in the application.
type GothRole struct {
// ID is the unique identifier of the role.
ID uuid.UUID `json:"id" gorm:"primaryKey;unique;type:uuid;column:id;default:gen_random_uuid()"`
// Name is the name of the role.
Name string `json:"name" validate:"required,min=3,max=255"`
// Description is the description of the role.
Description string `json:"description" validate:"max=255"`
// TeamID is the team ID of the role.
TeamID uuid.UUID `json:"team_id"`
// Team is the team of the role.
Team GothTeam `json:"team"`
// CreatedAt is the creation time of the role.
CreatedAt time.Time `json:"created_at"`
// UpdatedAt is the update time of the role.
UpdatedAt time.Time `json:"updated_at"`
// DeletedAt is the deletion time of the role.
DeletedAt gorm.DeletedAt `json:"deleted_at"`
}

// Adapter is an interface that defines the methods for interacting with the underlying data storage.
type Adapter interface {
// CreateUser creates a new user.
Expand Down Expand Up @@ -262,6 +206,12 @@ type Adapter interface {
CreateVerificationToken(ctx context.Context, verficationToken GothVerificationToken) (GothVerificationToken, error)
// UseVerficationToken uses a verification token.
UseVerficationToken(ctx context.Context, identifier string, token string) (GothVerificationToken, error)
// CreateCsrfToken creates a new CSRF token.
CreateCsrfToken(ctx context.Context, csrfToken GothCsrfToken) (GothCsrfToken, error)
// GetCsrfToken retrieves a CSRF token by token.
GetCsrfToken(ctx context.Context, token string) (GothCsrfToken, error)
// DeleteCsrfToken deletes a CSRF token by token.
DeleteCsrfToken(ctx context.Context, token string) error
}

var _ Adapter = (*UnimplementedAdapter)(nil)
Expand Down Expand Up @@ -343,3 +293,18 @@ func (a *UnimplementedAdapter) CreateVerificationToken(_ context.Context, erfica
func (a *UnimplementedAdapter) UseVerficationToken(_ context.Context, identifier string, token string) (GothVerificationToken, error) {
return GothVerificationToken{}, ErrUnimplemented
}

// CreateCsrfToken creates a new CSRF token.
func (a *UnimplementedAdapter) CreateCsrfToken(_ context.Context, csrfToken GothCsrfToken) (GothCsrfToken, error) {
return GothCsrfToken{}, ErrUnimplemented
}

// GetCsrfToken retrieves a CSRF token by token.
func (a *UnimplementedAdapter) GetCsrfToken(_ context.Context, token string) (GothCsrfToken, error) {
return GothCsrfToken{}, ErrUnimplemented
}

// DeleteCsrfToken deletes a CSRF token by token.
func (a *UnimplementedAdapter) DeleteCsrfToken(_ context.Context, token string) error {
return ErrUnimplemented
}
20 changes: 7 additions & 13 deletions adapters/gorm/gorm.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,22 +19,19 @@ func RunMigrations(db *gorm.DB) error {
&adapters.GothUser{},
&adapters.GothSession{},
&adapters.GothVerificationToken{},
&adapters.GothTeam{},
&adapters.GothRole{},
)
}

var _ adapters.Adapter = (*gormAdapter)(nil)

type gormAdapter struct {
db *gorm.DB

adapters.UnimplementedAdapter
}

// New ...
// New is a helper function to create a new adapter.
func New(db *gorm.DB) *gormAdapter {
return &gormAdapter{db, adapters.UnimplementedAdapter{}}
return &gormAdapter{db: db}
}

// CreateUser is a helper function to create a new user.
Expand All @@ -50,11 +47,7 @@ func (a *gormAdapter) CreateUser(ctx context.Context, user adapters.GothUser) (a
// GetSession is a helper function to retrieve a session by session token.
func (a *gormAdapter) GetSession(ctx context.Context, sessionToken string) (adapters.GothSession, error) {
var session adapters.GothSession
err := a.db.WithContext(ctx).
Preload(clause.Associations).
Preload("User.Teams").
Preload("User.Teams.Roles").
Where("session_token = ?", sessionToken).First(&session).Error
err := a.db.WithContext(ctx).Preload(clause.Associations).Where("session_token = ?", sessionToken).First(&session).Error
if err != nil {
return adapters.GothSession{}, goth.ErrMissingSession
}
Expand All @@ -65,7 +58,7 @@ func (a *gormAdapter) GetSession(ctx context.Context, sessionToken string) (adap
// GetUser is a helper function to retrieve a user by ID.
func (a *gormAdapter) GetUser(ctx context.Context, id uuid.UUID) (adapters.GothUser, error) {
var user adapters.GothUser
err := a.db.WithContext(ctx).Preload("Accounts").Where("id = ?", id).First(&user).Error
err := a.db.WithContext(ctx).Preload(clause.Associations).Where("id = ?", id).First(&user).Error
if err != nil {
return adapters.GothUser{}, goth.ErrMissingUser
}
Expand All @@ -80,10 +73,11 @@ func (a *gormAdapter) CreateSession(ctx context.Context, userID uuid.UUID, expir
SessionToken: uuid.NewString(),
ExpiresAt: expires,
CsrfToken: adapters.GothCsrfToken{
Token: uuid.NewString(), // creates a token that is used to prevent CSRF attacks
ExpiresAt: time.Now().Add(24 * time.Hour),
Token: uuid.NewString(), // creates a token that is used to prevent CSRF attacks
ExpiresAt: time.Now().Add(24 * time.Hour), // expires in 24 hours
},
}

err := a.db.Session(&gorm.Session{FullSaveAssociations: true}).WithContext(ctx).Create(&session).Error
if err != nil {
return adapters.GothSession{}, goth.ErrBadSession
Expand Down
Loading

0 comments on commit 784b987

Please sign in to comment.