Skip to content

Commit

Permalink
Merge branch 'main' into dependabot/go_modules/backend/backend-b42804…
Browse files Browse the repository at this point in the history
…ba1b
  • Loading branch information
garrettladley authored May 25, 2024
2 parents 081cc83 + 6d72ecd commit 10bff0d
Show file tree
Hide file tree
Showing 38 changed files with 1,020 additions and 158 deletions.
16 changes: 0 additions & 16 deletions backend/auth/jwt.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,22 +18,6 @@ type CustomClaims struct {
Role string `json:"role"`
}

// From extracts the CustomClaims from the fiber context
// Returns nil if the claims are not present
func From(c *fiber.Ctx) (*CustomClaims, error) {
rawClaims := c.Locals("claims")
if rawClaims == nil {
return nil, utilities.Forbidden()
}

claims, ok := rawClaims.(*CustomClaims)
if !ok {
return nil, fmt.Errorf("claims are not of type CustomClaims. got: %T", rawClaims)
}

return claims, nil
}

type JWTType string

const (
Expand Down
52 changes: 52 additions & 0 deletions backend/auth/locals.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
package auth

import (
"fmt"

"github.com/GenerateNU/sac/backend/utilities"
"github.com/gofiber/fiber/v2"
"github.com/google/uuid"
)

type localsKey byte

const (
claimsKey localsKey = 0
userIDKey localsKey = 1
)

func CustomClaimsFrom(c *fiber.Ctx) (*CustomClaims, error) {
rawClaims := c.Locals(claimsKey)
if rawClaims == nil {
return nil, utilities.Forbidden()
}

claims, ok := rawClaims.(*CustomClaims)
if !ok {
return nil, fmt.Errorf("claims are not of type CustomClaims. got: %T", rawClaims)
}

return claims, nil
}

func SetClaims(c *fiber.Ctx, claims *CustomClaims) {
c.Locals(claimsKey, claims)
}

func UserIDFrom(c *fiber.Ctx) (*uuid.UUID, error) {
userID := c.Locals(userIDKey)
if userID == nil {
return nil, utilities.Forbidden()
}

id, ok := userID.(*uuid.UUID)
if !ok {
return nil, fmt.Errorf("userID is not of type uuid.UUID. got: %T", userID)
}

return id, nil
}

func SetUserID(c *fiber.Ctx, id *uuid.UUID) {
c.Locals(userIDKey, id)
}
4 changes: 4 additions & 0 deletions backend/auth/password.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,10 @@ func ValidatePassword(password string) error {
errs = append(errs, "must be at least 8 characters long")
}

if len(password) > 128 { // see https://github.com/OWASP/ASVS/issues/756
errs = append(errs, "must be at most 128 characters long")
}

if !hasDigit(password) {
errs = append(errs, "must contain at least one digit")
}
Expand Down
20 changes: 11 additions & 9 deletions backend/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,17 @@ import (
)

type Settings struct {
Application ApplicationSettings
Database DatabaseSettings
SuperUser SuperUserSettings
Auth AuthSettings
AWS AWSSettings
Pinecone PineconeSettings
OpenAI OpenAISettings
Resend ResendSettings
Calendar CalendarSettings
Application ApplicationSettings
Database DatabaseSettings
SuperUser SuperUserSettings
Auth AuthSettings
AWS AWSSettings
Pinecone PineconeSettings
OpenAI OpenAISettings
Resend ResendSettings
Calendar CalendarSettings
GoogleSettings OAuthSettings
OutlookSettings OAuthSettings
}

type intermediateSettings struct {
Expand Down
14 changes: 14 additions & 0 deletions backend/config/local.go
Original file line number Diff line number Diff line change
Expand Up @@ -65,5 +65,19 @@ func readLocal(v *viper.Viper, path string, useDevDotEnv bool) (*Settings, error

settings.Resend = *resendSettings

googleSettings, err := readGoogleOAuthSettings()
if err != nil {
return nil, fmt.Errorf("failed to read Google OAuth settings: %w", err)
}

settings.GoogleSettings = *googleSettings

outlookSettings, err := readOutlookOAuthSettings()
if err != nil {
return nil, fmt.Errorf("failed to read Outlook OAuth settings: %w", err)
}

settings.OutlookSettings = *outlookSettings

return settings, nil
}
102 changes: 102 additions & 0 deletions backend/config/oauth.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
package config

import (
"errors"
"os"

m "github.com/garrettladley/mattress"
)

type OAuthSettings struct {
BaseURL string
TokenURL string
ClientID *m.Secret[string]
ClientSecret *m.Secret[string]
Scopes string
RedirectURI string
ResponseType string
ResponseMode string
AccessType string
IncludeGrantedScopes string
Prompt string
}

type OAuthResources struct {
GoogleOAuthSettings *OAuthSettings
OutlookOAuthSettings *OAuthSettings
}

/**
* GOOGLE
**/
func readGoogleOAuthSettings() (*OAuthSettings, error) {
clientID := os.Getenv("GOOGLE_OAUTH_CLIENT_ID")
if clientID == "" {
return nil, errors.New("GOOGLE_OAUTH_CLIENT_ID is not set")
}

secretClientID, err := m.NewSecret(clientID)
if err != nil {
return nil, errors.New("failed to create secret from client ID")
}

clientSecret := os.Getenv("GOOGLE_OAUTH_CLIENT_SECRET")
if clientSecret == "" {
return nil, errors.New("GOOGLE_OAUTH_CLIENT_SECRET is not set")
}

secretClientSecret, err := m.NewSecret(clientSecret)
if err != nil {
return nil, errors.New("failed to create secret from client secret")
}

return &OAuthSettings{
BaseURL: "https://accounts.google.com/o/oauth2/v2",
TokenURL: "https://oauth2.googleapis.com",
ClientID: secretClientID,
ClientSecret: secretClientSecret,
Scopes: "https://www.googleapis.com/auth/calendar.events https://www.googleapis.com/auth/calendar.readonly",
ResponseType: "code",
RedirectURI: "http://localhost:3000",
IncludeGrantedScopes: "true",
AccessType: "offline",
Prompt: "consent",
}, nil
}

/**
* OUTLOOK
**/
func readOutlookOAuthSettings() (*OAuthSettings, error) {
clientID := os.Getenv("OUTLOOK_OAUTH_CLIENT_ID")
if clientID == "" {
return nil, errors.New("OUTLOOK_OAUTH_CLIENT_ID is not set")
}

secretClientID, err := m.NewSecret(clientID)
if err != nil {
return nil, errors.New("failed to create secret from client ID")
}

clientSecret := os.Getenv("OUTLOOK_OAUTH_CLIENT_SECRET")
if clientSecret == "" {
return nil, errors.New("OUTLOOK_OAUTH_CLIENT_SECRET is not set")
}

secretClientSecret, err := m.NewSecret(clientSecret)
if err != nil {
return nil, errors.New("failed to create secret from client secret")
}

return &OAuthSettings{
BaseURL: "https://login.microsoftonline.com/common/oauth2/v2.0",
TokenURL: "https://login.microsoftonline.com/common/oauth2/v2.0",
ClientID: secretClientID,
ClientSecret: secretClientSecret,
Scopes: "offline_access user.read calendars.readwrite",
ResponseType: "code",
RedirectURI: "http://localhost:3000",
ResponseMode: "query",
Prompt: "consent",
}, nil
}
22 changes: 17 additions & 5 deletions backend/config/production.go
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,16 @@ func readProd(v *viper.Viper) (*Settings, error) {
return nil, fmt.Errorf("failed to read Resend settings: %w", err)
}

googleSettings, err := readGoogleOAuthSettings()
if err != nil {
return nil, fmt.Errorf("failed to read Google OAuth settings: %w", err)
}

outlookSettings, err := readOutlookOAuthSettings()
if err != nil {
return nil, fmt.Errorf("failed to read Outlook OAuth settings: %w", err)
}

return &Settings{
Application: ApplicationSettings{
Port: uint16(portInt),
Expand All @@ -119,10 +129,12 @@ func readProd(v *viper.Viper) (*Settings, error) {
AccessKey: authAccessKey,
RefreshKey: authRefreshKey,
},
Pinecone: *pineconeSettings,
OpenAI: *openAISettings,
AWS: *awsSettings,
Resend: *resendSettings,
Calendar: prodSettings.Calendar,
Pinecone: *pineconeSettings,
OpenAI: *openAISettings,
AWS: *awsSettings,
Resend: *resendSettings,
Calendar: prodSettings.Calendar,
GoogleSettings: *googleSettings,
OutlookSettings: *outlookSettings,
}, nil
}
1 change: 1 addition & 0 deletions backend/constants/auth.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import "time"
const (
ACCESS_TOKEN_EXPIRY time.Duration = time.Minute * 24 * 30 // temporary TODO: change to 60 minutes
REFRESH_TOKEN_EXPIRY time.Duration = time.Minute * 24 * 30
CSRF_TOKEN_LENGTH int = 32
)

var SPECIAL_CHARACTERS = []rune{' ', '!', '"', '#', '$', '%', '&', '\'', '(', ')', '*', '+', ',', '-', '.', '/', ':', ';', '<', '=', '>', '?', '@', '[', '\\', ']', '^', '_', '`', '{', '|', '}', '~'} // see https://owasp.org/www-community/password-special-characters
4 changes: 2 additions & 2 deletions backend/entities/auth/base/models.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@ type VerifyEmailRequestBody struct {

type VerifyPasswordResetTokenRequestBody struct {
Token string `json:"token" validate:"required"`
NewPassword string `json:"new_password" validate:"required,min=8,password"`
VerifyNewPassword string `json:"verify_new_password" validate:"required,min=8,password,eqfield=NewPassword"`
NewPassword string `json:"new_password" validate:"required"` // MARK: must be validated manually
VerifyNewPassword string `json:"verify_new_password" validate:"required,eqfield=NewPassword"` // MARK: must be validated manually
}

type EmailRequestBody struct {
Expand Down
6 changes: 3 additions & 3 deletions backend/entities/auth/models.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,12 @@ package auth

type LoginResponseBody struct {
Email string `json:"email" validate:"required,email"`
Password string `json:"password" validate:"required,max=255"` // MARK: must be validated manually
Password string `json:"password" validate:"required"` // MARK: must be validated manually
}

type UpdatePasswordRequestBody struct {
OldPassword string `json:"old_password" validate:"required,max=255"` // MARK: must be validated manually
NewPassword string `json:"new_password" validate:"required,not_equal_if_not_empty=OldPassword,max=255"` // MARK: must be validated manually
OldPassword string `json:"old_password" validate:"required"` // MARK: must be validated manually
NewPassword string `json:"new_password" validate:"required,not_equal_if_not_empty=OldPassword"` // MARK: must be validated manually
}

type RefreshTokenRequestBody struct {
Expand Down
35 changes: 35 additions & 0 deletions backend/entities/models/oauth.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package models

import (
"time"

"github.com/google/uuid"
)

type OAuthResource string

const (
Google OAuthResource = "google"
Outlook OAuthResource = "outlook"
)

type UserOAuthTokens struct {
UserID uuid.UUID `json:"user_id" validate:"required,uuid4"`
RefreshToken string `json:"refresh_token" validate:"max=255"`
AccessToken string `json:"access_token" validate:"max=255"`
CSRFToken string `json:"csrf_token" validate:"max=255"`
ResourceType OAuthResource `json:"resource_type" validate:"required"`
ExpiresAt time.Time `json:"expires_at" validate:"required"`
}

type OAuthToken struct {
AccessToken string `json:"access_token" validate:"required"`
ExpiresIn int `json:"expires_in" validate:"required"`
RefreshToken string `json:"refresh_token" validate:"required"`
Scope string `json:"scope" validate:"required"`
TokenType string `json:"token_type" validate:"required"`
}

func (UserOAuthTokens) TableName() string {
return "user_oauth_tokens"
}
Loading

0 comments on commit 10bff0d

Please sign in to comment.