Skip to content

Commit

Permalink
⚡️ Added session.CreateToken function
Browse files Browse the repository at this point in the history
  • Loading branch information
lhbelfanti committed Jan 13, 2025
1 parent ee0abfd commit 4c9f2b6
Show file tree
Hide file tree
Showing 8 changed files with 121 additions and 4 deletions.
3 changes: 3 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,8 @@ DB_USER=ahbcc_user
DB_PASS=ahbcc_password
DB_PORT=5432

# Session
SESSION_SECRET_KEY="dAXWWyqlEA1mnnQMVapGWvRwdATwwBdK89XoooAkYD0="

# External APIs URLs
ENQUEUE_CRITERIA_API_URL=http://localhost:5000
20 changes: 18 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -125,9 +125,10 @@ erDiagram
}
```

#### Necessary files to start the database
## Setup

To connect to the database create a `.env` file in the root of the project or rename the provided [.env.example](.env.example).
To run the application, you must define specific environment variables.
You can create a .env file in the root directory of the project or rename the provided example file, .env.example.

This file should contain the following environment variables:
```
Expand All @@ -141,8 +142,23 @@ DB_USER=<Database username>
DB_PASS=<Database password>
DB_PORT=<Database port>
# Session
SESSION_SECRET_KEY=<Secret key used for signing and verifying HMAC-based tokens>
# External APIs URLs
ENQUEUE_CRITERIA_API_URL=<Domain of the application with the endpoint /criteria/enqueue/v1> --> Example: the URL to the GoXCrap API
```

Replace the `< ... >` by the correct value. For example: `DB_NAME=<Database name>` --> `DB_NAME=ahbcc`.

#### Session secret key

The value of the session secret key should be a random and long byte sequence that isn't easily guessable.

An example of how to generate this key is by using a tool like `OpenSSL` or a `password generator`.
```bash
openssl rand -base64 32
```
This generates a 256-bit (32-byte) key encoded in Base64, which is suitable for HMAC-SHA256.


3 changes: 2 additions & 1 deletion cmd/api/user/session/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,6 @@ package session
import "errors"

var (
FailedToInsertUserSession = errors.New("failed to insert user session")
FailedToInsertUserSession = errors.New("failed to insert user session")
FailedToCreatUserSessionToken = errors.New("failed to create user session token")
)
12 changes: 11 additions & 1 deletion cmd/api/user/session/mocks.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,16 @@
package session

import "time"
import (
"context"
"time"
)

// MockInsertUserSession mocks Insert function
func MockInsertUserSession(err error) Insert {
return func(ctx context.Context, session DAO) error {
return err
}
}

// MockUserSessionDAO mocks a session DAO
func MockUserSessionDAO() DAO {
Expand Down
43 changes: 43 additions & 0 deletions cmd/api/user/session/token.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package session

import (
"context"
"crypto/hmac"
"crypto/sha256"
"encoding/base64"
"fmt"
"os"
"time"

"ahbcc/internal/log"
)

// CreateToken creates a new session token for the user login action
type CreateToken func(ctx context.Context, userID int) (string, time.Time, error)

// MakeCreateToken creates a new CreateToken function
func MakeCreateToken(insertUserSession Insert) CreateToken {
return func(ctx context.Context, userID int) (string, time.Time, error) {
expiresAt := time.Now().Add(30 * 24 * time.Hour) // 30 days session expiry
payload := fmt.Sprintf("%d:%d", userID, expiresAt.Unix())

mac := hmac.New(sha256.New, []byte(os.Getenv("SESSION_SECRET_KEY")))
mac.Write([]byte(payload))
signature := base64.URLEncoding.EncodeToString(mac.Sum(nil))
token := base64.URLEncoding.EncodeToString([]byte(payload)) + "." + signature

session := DAO{
UserID: userID,
Token: token,
ExpiresAt: expiresAt,
}

err := insertUserSession(ctx, session)
if err != nil {
log.Error(ctx, err.Error())
return "", time.Time{}, FailedToCreatUserSessionToken
}

return token, expiresAt, nil
}
}
41 changes: 41 additions & 0 deletions cmd/api/user/session/token_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package session_test

import (
"context"
"errors"
"os"
"testing"

"github.com/joho/godotenv"
"github.com/stretchr/testify/assert"

"ahbcc/cmd/api/user/session"
)

func TestMain(m *testing.M) {
_ = godotenv.Load()
os.Exit(m.Run())
}

func TestCreateToken_success(t *testing.T) {
mockInsertUserSession := session.MockInsertUserSession(nil)

createSessionToken := session.MakeCreateToken(mockInsertUserSession)

token, createdAt, got := createSessionToken(context.Background(), 1)

assert.Nil(t, got)
assert.NotNil(t, createdAt)
assert.NotNil(t, token)
}

func TestCreateToken_failsWhenInsertUserSessionThrowsError(t *testing.T) {
mockInsertUserSession := session.MockInsertUserSession(errors.New("failed to insert user session"))

createSessionToken := session.MakeCreateToken(mockInsertUserSession)

want := session.FailedToCreatUserSessionToken
_, _, got := createSessionToken(context.Background(), 1)

assert.Equal(t, want, got)
}
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ require (
github.com/jackc/pgpassfile v1.0.0 // indirect
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect
github.com/jackc/puddle/v2 v2.2.1 // indirect
github.com/joho/godotenv v1.5.1 // indirect
github.com/kr/text v0.1.0 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ github.com/jackc/pgx/v5 v5.6.0 h1:SWJzexBzPL5jb0GEsrPMLIsi/3jOo7RHlzTjcAeDrPY=
github.com/jackc/pgx/v5 v5.6.0/go.mod h1:DNZ/vlrUnhWCoFGxHAG8U2ljioxukquj7utPDgtQdTw=
github.com/jackc/puddle/v2 v2.2.1 h1:RhxXJtFG022u4ibrCSMSiu5aOq1i77R3OHKNJj77OAk=
github.com/jackc/puddle/v2 v2.2.1/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4=
github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
Expand Down

0 comments on commit 4c9f2b6

Please sign in to comment.