Skip to content

Commit 8d1d662

Browse files
authored
env templates (#297)
1 parent aae127d commit 8d1d662

19 files changed

Lines changed: 235 additions & 149 deletions

File tree

backend/api/openapi.yaml

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,7 @@ paths:
9191
required:
9292
- access_token
9393
- user
94+
- needs_mfa
9495
properties:
9596
access_token:
9697
type: string
@@ -104,6 +105,10 @@ paths:
104105
format: uuid
105106
description: The unique identifier of the newly created therapist
106107
example: "f20e5948-01ba-4113-b453-db05d8bde3bc"
108+
needs_mfa:
109+
type: boolean
110+
description: Indicates if the user needs multi-factor authentication
111+
example: false
107112
"400":
108113
description: Bad Request (e.g., validation errors)
109114
content:
@@ -165,6 +170,7 @@ paths:
165170
- expires_in
166171
- refresh_token
167172
- user
173+
- needs_mfa
168174
properties:
169175
access_token:
170176
type: string
@@ -190,6 +196,10 @@ paths:
190196
format: uuid
191197
description: The unique identifier of the newly created therapist
192198
example: "f20e5948-01ba-4113-b453-db05d8bde3bc"
199+
needs_mfa:
200+
type: boolean
201+
description: Indicates if the user needs multi-factor authentication
202+
example: false
193203
error:
194204
content:
195205
application/json:

backend/env.sample

Lines changed: 0 additions & 11 deletions
This file was deleted.

backend/env.template

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
DB_USER=
2+
DB_PASSWORD=
3+
DB_HOST=
4+
DB_PORT=
5+
DB_NAME=
6+
7+
PORT=
8+
9+
SUPABASE_URL=
10+
SUPABASE_ANON_KEY=
11+
SUPABASE_SERVICE_ROLE_KEY=
12+
13+
AWS_ACCESS_KEY_ID=
14+
AWS_SECRET_ACCESS_KEY=
15+
AWS_REGION=
16+
AWS_S3_BUCKET=
17+
18+
RESEND_API_KEY=
19+
EMAIL_VERIFICATION_ENABLED=true
20+
RESEND_FROM_EMAIL=
21+
22+
DB_MAX_OPEN_CONNS=2
23+
DB_MAX_IDLE_CONNS=0
24+
DB_CONN_MAX_LIFETIME=300

backend/internal/auth/login.go

Lines changed: 32 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -10,38 +10,26 @@ import (
1010
"specialstandard/internal/errs"
1111

1212
"github.com/goccy/go-json"
13-
"github.com/google/uuid"
14-
)
15-
16-
type userResponse struct {
17-
ID uuid.UUID `json:"id"`
18-
}
1913

20-
type SignInResponse struct {
21-
AccessToken string `json:"access_token"`
22-
TokenType string `json:"token_type"`
23-
ExpiresIn int `json:"expires_in"`
24-
RefreshToken string `json:"refresh_token"`
25-
User userResponse `json:"user"`
26-
Error interface{} `json:"error"`
27-
}
14+
"specialstandard/internal/models"
15+
)
2816

29-
func SupabaseLogin(cfg *config.Supabase, email string, password string) (SignInResponse, error) {
17+
func SupabaseLogin(cfg *config.Supabase, email string, password string, needsEmailVerification bool) (models.SignInResponse, error) {
3018
supabaseURL := cfg.URL
3119
serviceroleKey := cfg.ServiceRoleKey
3220

33-
payload := Payload{
21+
payload := models.Payload{
3422
Email: email,
3523
Password: password,
3624
}
3725
payloadBytes, err := json.Marshal(payload)
3826
if err != nil {
39-
return SignInResponse{}, err
27+
return models.SignInResponse{}, err
4028
}
4129

4230
req, err := http.NewRequest("POST", fmt.Sprintf("%s/auth/v1/token?grant_type=password", supabaseURL), bytes.NewBuffer(payloadBytes))
4331
if err != nil {
44-
return SignInResponse{}, err
32+
return models.SignInResponse{}, err
4533
}
4634

4735
req.Header.Set("Content-Type", "application/json")
@@ -51,7 +39,7 @@ func SupabaseLogin(cfg *config.Supabase, email string, password string) (SignInR
5139
res, err := Client.Do(req)
5240
if err != nil {
5341
slog.Error("Failed to execute Request", "err", err)
54-
return SignInResponse{}, errs.BadRequest("Failed to execute Request")
42+
return models.SignInResponse{}, errs.BadRequest("Failed to execute Request")
5543
}
5644
defer func() {
5745
_ = res.Body.Close()
@@ -60,40 +48,42 @@ func SupabaseLogin(cfg *config.Supabase, email string, password string) (SignInR
6048
body, err := io.ReadAll(res.Body)
6149
if err != nil {
6250
slog.Error("Failed to read response body", "err", err)
63-
return SignInResponse{}, errs.BadRequest("Failed to read response body")
51+
return models.SignInResponse{}, errs.BadRequest("Failed to read response body")
6452
}
6553

6654
if res.StatusCode != http.StatusOK {
67-
// Try to parse the error response
68-
var errorResp struct {
69-
Message string `json:"msg"`
70-
Error string `json:"error"`
71-
}
72-
73-
if err := json.Unmarshal(body, &errorResp); err == nil {
74-
// Use the parsed message if available
75-
if errorResp.Message != "" {
76-
return SignInResponse{}, errs.BadRequest(errorResp.Message)
77-
}
78-
if errorResp.Error != "" {
79-
return SignInResponse{}, errs.BadRequest(errorResp.Error)
80-
}
81-
}
82-
83-
// Fallback to generic message if parsing fails
84-
return SignInResponse{}, errs.BadRequest("Invalid credentials")
85-
}
55+
// Try to parse the error response
56+
var errorResp struct {
57+
Message string `json:"msg"`
58+
Error string `json:"error"`
59+
}
60+
61+
if err := json.Unmarshal(body, &errorResp); err == nil {
62+
// Use the parsed message if available
63+
if errorResp.Message != "" {
64+
return models.SignInResponse{}, errs.BadRequest(errorResp.Message)
65+
}
66+
if errorResp.Error != "" {
67+
return models.SignInResponse{}, errs.BadRequest(errorResp.Error)
68+
}
69+
}
8670

87-
var signInResponse SignInResponse
71+
// Fallback to generic message if parsing fails
72+
return models.SignInResponse{}, errs.BadRequest("Invalid credentials")
73+
}
74+
75+
var signInResponse models.SignInResponse
8876
err = json.Unmarshal(body, &signInResponse)
8977
if err != nil {
9078
slog.Error("Failed to parse response body", "body", err)
91-
return SignInResponse{}, errs.BadRequest("Failed to parse response body")
79+
return models.SignInResponse{}, errs.BadRequest("Failed to parse response body")
9280
}
9381

9482
if signInResponse.Error != nil {
95-
return SignInResponse{}, errs.BadRequest(fmt.Sprintf("Sign In Response Error %v", signInResponse.Error))
83+
return models.SignInResponse{}, errs.BadRequest(fmt.Sprintf("Sign In Response Error %v", signInResponse.Error))
9684
}
9785

86+
signInResponse.RequiresMFA = needsEmailVerification
87+
9888
return signInResponse, nil
9989
}

backend/internal/auth/signup.go

Lines changed: 14 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -12,22 +12,9 @@ import (
1212
"specialstandard/internal/errs"
1313

1414
"github.com/goccy/go-json"
15-
"github.com/google/uuid"
16-
)
17-
18-
type Payload struct {
19-
Email string `json:"email"`
20-
Password string `json:"password"`
21-
}
22-
23-
type UserSignupResponse struct {
24-
ID uuid.UUID `json:"id"`
25-
}
2615

27-
type SignupResponse struct {
28-
AccessToken string `json:"access_token"`
29-
User UserSignupResponse `json:"user"`
30-
}
16+
"specialstandard/internal/models"
17+
)
3118

3219
func validatePasswordStrength(password string) error {
3320
if len(password) < 8 {
@@ -45,27 +32,27 @@ func validatePasswordStrength(password string) error {
4532
return nil
4633
}
4734

48-
func SupabaseSignup(cfg *config.Supabase, email, password string) (SignupResponse, error) {
35+
func SupabaseSignup(cfg *config.Supabase, email, password string, needsEmailVerification bool) (models.SignupResponse, error) {
4936
if err := validatePasswordStrength(password); err != nil {
50-
return SignupResponse{}, errs.BadRequest(fmt.Sprintf("Weak Password: %v", err))
37+
return models.SignupResponse{}, errs.BadRequest(fmt.Sprintf("Weak Password: %v", err))
5138
}
5239

5340
supabaseURL := cfg.URL
5441
apiKey := cfg.ServiceRoleKey
5542

56-
payload := Payload{
43+
payload := models.Payload{
5744
Email: email,
5845
Password: password,
5946
}
6047
payloadBytes, err := json.Marshal(payload)
6148
if err != nil {
62-
return SignupResponse{}, err
49+
return models.SignupResponse{}, err
6350
}
6451

6552
req, err := http.NewRequest("POST", fmt.Sprintf("%s/auth/v1/signup", supabaseURL), bytes.NewBuffer(payloadBytes))
6653
if err != nil {
6754
slog.Error("Error in Request Creation: ", "err", err)
68-
return SignupResponse{}, errs.BadRequest(fmt.Sprintf("Failed to create request: %v", err))
55+
return models.SignupResponse{}, errs.BadRequest(fmt.Sprintf("Failed to create request: %v", err))
6956
}
7057

7158
req.Header.Set("Content-Type", "application/json")
@@ -75,7 +62,7 @@ func SupabaseSignup(cfg *config.Supabase, email, password string) (SignupRespons
7562
res, err := Client.Do(req)
7663
if err != nil {
7764
slog.Error("Error executing request: ", "err", err)
78-
return SignupResponse{}, errs.BadRequest(fmt.Sprintf("Failed to execute request: %v, %s", err, supabaseURL))
65+
return models.SignupResponse{}, errs.BadRequest(fmt.Sprintf("Failed to execute request: %v, %s", err, supabaseURL))
7966
}
8067
defer func() {
8168
_ = res.Body.Close()
@@ -84,19 +71,21 @@ func SupabaseSignup(cfg *config.Supabase, email, password string) (SignupRespons
8471
body, err := io.ReadAll(res.Body)
8572
if err != nil {
8673
slog.Error("Error reading response body: ", "body", body)
87-
return SignupResponse{}, errs.BadRequest("Failed to read response body", string(body))
74+
return models.SignupResponse{}, errs.BadRequest("Failed to read response body", string(body))
8875
}
8976

9077
if res.StatusCode != http.StatusOK {
9178
slog.Error("Error Response: ", "res.StatusCode", res.StatusCode, "body", string(body))
92-
return SignupResponse{}, errs.BadRequest(fmt.Sprintf("Failed to login %d, %s", res.StatusCode, body))
79+
return models.SignupResponse{}, errs.BadRequest(fmt.Sprintf("Failed to login %d, %s", res.StatusCode, body))
9380
}
9481

95-
var response SignupResponse
82+
var response models.SignupResponse
9683
if err := json.Unmarshal(body, &response); err != nil {
9784
slog.Error("Error parsing response: ", "err", err)
98-
return SignupResponse{}, errs.BadRequest("Failed to parse request")
85+
return models.SignupResponse{}, errs.BadRequest("Failed to parse request")
9986
}
10087

88+
response.RequiresMFA = needsEmailVerification
89+
10190
return response, nil
10291
}

backend/internal/models/auth.go

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
package models
2+
3+
import (
4+
"github.com/google/uuid"
5+
)
6+
7+
type userResponse struct {
8+
ID uuid.UUID `json:"id"`
9+
}
10+
11+
type SignInResponse struct {
12+
AccessToken string `json:"access_token"`
13+
TokenType string `json:"token_type"`
14+
ExpiresIn int `json:"expires_in"`
15+
RefreshToken string `json:"refresh_token"`
16+
User userResponse `json:"user"`
17+
Error interface{} `json:"error"`
18+
RequiresMFA bool `json:"needs_mfa"`
19+
}
20+
21+
type Payload struct {
22+
Email string `json:"email"`
23+
Password string `json:"password"`
24+
}
25+
26+
type UserSignupResponse struct {
27+
ID uuid.UUID `json:"id"`
28+
}
29+
30+
type SignupResponse struct {
31+
AccessToken string `json:"access_token"`
32+
User UserSignupResponse `json:"user"`
33+
RequiresMFA bool `json:"needs_mfa"`
34+
}

backend/internal/service/handler/auth/handler.go

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,9 @@ import (
66
)
77

88
type Handler struct {
9-
config config.Supabase
10-
therapistRepository storage.TherapistRepository
9+
config config.Supabase
10+
therapistRepository storage.TherapistRepository
11+
emailVerificationEnabled bool
1112
}
1213

1314
type Credentials struct {
@@ -18,9 +19,10 @@ type Credentials struct {
1819
RememberMe bool `json:"remember_me"`
1920
}
2021

21-
func NewHandler(config config.Supabase, therapistRepository storage.TherapistRepository) *Handler {
22+
func NewHandler(config config.Supabase, therapistRepository storage.TherapistRepository, emailVerificationEnabled bool) *Handler {
2223
return &Handler{
2324
config,
2425
therapistRepository,
26+
emailVerificationEnabled,
2527
}
2628
}

backend/internal/service/handler/auth/handler_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ func TestHandler_SignUp(t *testing.T) {
8585
ServiceRoleKey: "SRK",
8686
}
8787

88-
handler := NewHandler(mockConfig, mockRepo)
88+
handler := NewHandler(mockConfig, mockRepo, true)
8989
app.Post("/signup", handler.SignUp)
9090

9191
req := httptest.NewRequest("POST", "/signup", strings.NewReader(tt.payload))
@@ -152,7 +152,7 @@ func TestHandler_Login(t *testing.T) {
152152
ServiceRoleKey: "SRK",
153153
}
154154

155-
handler := NewHandler(mockConfig, mockRepo)
155+
handler := NewHandler(mockConfig, mockRepo, true)
156156
app.Post("/login", handler.Login)
157157

158158
req := httptest.NewRequest("POST", "/login", strings.NewReader(tt.payload))

backend/internal/service/handler/auth/login.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ func (h *Handler) Login(c *fiber.Ctx) error {
1717
return errs.BadRequest(fmt.Sprintf("Invalid Request Body: %v", cred))
1818
}
1919

20-
signInResponse, err := auth.SupabaseLogin(&h.config, cred.Email, cred.Password)
20+
signInResponse, err := auth.SupabaseLogin(&h.config, cred.Email, cred.Password, h.emailVerificationEnabled)
2121
if err != nil {
2222
slog.Error("Supabase Login Error: ", "err", err.Error())
2323

backend/internal/service/handler/auth/signup.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ func (h *Handler) SignUp(c *fiber.Ctx) error {
2222
return errs.BadRequest(fmt.Sprintf("Invalid Request Body: %v", err))
2323
}
2424

25-
res, err := auth.SupabaseSignup(&h.config, cred.Email, cred.Password)
25+
res, err := auth.SupabaseSignup(&h.config, cred.Email, cred.Password, h.emailVerificationEnabled)
2626
if err != nil {
2727
slog.Error(fmt.Sprintf("Signup Request Failed: %v", err))
2828
return errs.InternalServerError(fmt.Sprintf("Signup Request Failed: %v", err))

0 commit comments

Comments
 (0)