Skip to content

Commit 89b1c36

Browse files
authored
Merge pull request #39 from Dragon-Rage/master
feat: access and refresh token
2 parents e14469c + ea45c60 commit 89b1c36

9 files changed

Lines changed: 148 additions & 21 deletions

File tree

database/queries/users.sql

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -113,4 +113,7 @@ UPDATE users
113113
SET team_id = $1
114114
WHERE email = $2;
115115

116-
116+
-- name: MarkUserAsVerified :exec
117+
UPDATE users
118+
SET is_verified = TRUE
119+
WHERE email = $1;

go.mod

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,5 +43,4 @@ require (
4343
golang.org/x/sync v0.19.0 // indirect
4444
golang.org/x/sys v0.39.0 // indirect
4545
golang.org/x/text v0.32.0 // indirect
46-
golang.org/x/time v0.11.0 // indirect
4746
)

pkg/controllers/auth.go

Lines changed: 24 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -58,27 +58,45 @@ func SendOTP(email string) error {
5858
return nil
5959
}
6060

61-
func VerifyOTP(email, otp string) (string, error) {
61+
func VerifyOTP(email, otp string) (accessToken string, refreshToken string, err error) {
6262
key := "otp:" + email
6363

6464
storedHash, err := redis.Client.Get(redis.Ctx, key).Result()
6565
if err != nil {
6666
if redis.IsKeyMissing(err) {
67-
return "", errors.New("otp expired or invalid")
67+
return "", "",errors.New("otp expired or invalid")
6868
}
69-
return "", err
69+
return "", "",err
7070
}
7171

7272
if utils.HashOTP(otp) != storedHash {
73-
return "", errors.New("invalid otp")
73+
return "", "", errors.New("invalid otp")
7474
}
7575

7676
redis.Client.Del(redis.Ctx, key)
7777

7878
userID, err := db.Queries.GetUserIDByEmail(context.Background(), email)
7979
if err != nil {
80-
return "", err
80+
return "", "",err
8181
}
8282

83-
return utils.CreateJWT(userID.String(), email)
83+
// dB fnc - Marks user as verified in dB
84+
err = db.Queries.MarkUserAsVerified(context.Background(), email)
85+
if err != nil {
86+
return "", "", err
87+
}
88+
89+
// Access Token generation (1 hR)
90+
accessToken, err = utils.CreateJWT(userID.String(), email, "access", 1*time.Hour)
91+
if err != nil {
92+
return "", "", err
93+
}
94+
95+
// Refresh Token generation (2 hR)
96+
refreshToken, err = utils.CreateJWT(userID.String(), email, "refresh", 2*time.Hour)
97+
if err != nil {
98+
return "", "", err
99+
}
100+
101+
return accessToken, refreshToken, nil
84102
}

pkg/db/sqlc/users.sql.go

Lines changed: 11 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pkg/handlers/auth.go

Lines changed: 84 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,15 @@ package handlers
22

33
import (
44
"net/http"
5+
"time"
56

67
"github.com/labstack/echo/v4"
8+
"github.com/lestrrat-go/jwx/v2/jwa"
9+
"github.com/lestrrat-go/jwx/v2/jwt"
710

811
"github.com/CodeChefVIT/devsoc-backend-26/pkg/controllers"
12+
"github.com/CodeChefVIT/devsoc-backend-26/pkg/utils"
13+
"os"
914
)
1015

1116
func RequestOTP(c echo.Context) error {
@@ -42,12 +47,89 @@ func VerifyOTP(c echo.Context) error {
4247
return c.JSON(http.StatusBadRequest, "invalid request")
4348
}
4449

45-
token, err := controllers.VerifyOTP(req.Email, req.OTP)
50+
accessToken, refreshToken, err := controllers.VerifyOTP(req.Email, req.OTP)
4651
if err != nil {
4752
return c.JSON(http.StatusUnauthorized, "invalid or expired otp")
4853
}
4954

55+
// access token in pookie
56+
c.SetCookie(&http.Cookie{
57+
Name: "access_token",
58+
Value: accessToken,
59+
Path: "/",
60+
MaxAge: 3600, // 1 ghanta
61+
HttpOnly: true,
62+
SameSite: http.SameSiteLaxMode,
63+
})
64+
65+
// refresh token in pookie
66+
c.SetCookie(&http.Cookie{
67+
Name: "refresh_token",
68+
Value: refreshToken,
69+
Path: "/",
70+
MaxAge: 7200, // 2 ghante
71+
HttpOnly: true,
72+
SameSite: http.SameSiteLaxMode,
73+
})
74+
5075
return c.JSON(http.StatusOK, map[string]string{
51-
"token": token,
76+
"message": "user verified and logged in",
5277
})
5378
}
79+
80+
func Refresh(c echo.Context) error {
81+
// Reading refresh token from pookie
82+
cookie, err := c.Cookie("refresh_token")
83+
if err != nil || cookie.Value == "" {
84+
return c.JSON(http.StatusUnauthorized, "missing refresh token")
85+
}
86+
87+
jwtKey := []byte(os.Getenv("JWT_SECRET"))
88+
89+
// Parsing and validatng refresh token
90+
tok, err := jwt.Parse(
91+
[]byte(cookie.Value),
92+
jwt.WithKey(jwa.HS256, jwtKey),
93+
)
94+
if err != nil {
95+
return c.JSON(http.StatusUnauthorized, "invalid or expired refresh token")
96+
}
97+
98+
// Verify token_type is "refresh"
99+
tokenType, ok := tok.Get("token_type")
100+
if !ok || tokenType != "refresh" {
101+
return c.JSON(http.StatusUnauthorized, "invalid token type")
102+
}
103+
104+
// Extracting ujer info
105+
userID := tok.Subject()
106+
emailVal, ok := tok.Get("email")
107+
if !ok {
108+
return c.JSON(http.StatusUnauthorized, "invalid token claims")
109+
}
110+
111+
email, ok := emailVal.(string)
112+
if !ok {
113+
return c.JSON(http.StatusUnauthorized, "invalid email claim")
114+
}
115+
116+
// Issuing new access token (1 hour)
117+
newAccessToken, err := utils.CreateJWT(userID, email, "access", 1*time.Hour)
118+
if err != nil {
119+
return c.JSON(http.StatusInternalServerError, "failed to create token")
120+
}
121+
122+
// Setting that new access token pookie
123+
c.SetCookie(&http.Cookie{
124+
Name: "access_token",
125+
Value: newAccessToken,
126+
Path: "/",
127+
MaxAge: 3600, // 1 hour
128+
HttpOnly: true,
129+
SameSite: http.SameSiteLaxMode,
130+
})
131+
132+
return c.JSON(http.StatusOK, map[string]string{
133+
"message": "accesstoken refreshed",
134+
})
135+
}

pkg/middlewares/jwt.go

Lines changed: 19 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -14,15 +14,23 @@ var jwtKey = []byte(os.Getenv("JWT_SECRET"))
1414

1515
func JWTAuth(next echo.HandlerFunc) echo.HandlerFunc {
1616
return func(c echo.Context) error {
17-
auth := c.Request().Header.Get("Authorization")
18-
if !strings.HasPrefix(auth, "Bearer ") {
19-
return echo.ErrUnauthorized
17+
var tokenString string
18+
19+
// Reading from access_token pookie (from handlers)
20+
cookie, err := c.Cookie("access_token")
21+
if err == nil && cookie.Value != "" {
22+
tokenString = cookie.Value
23+
} else {
24+
// if that is not working, we use AuthZ: Bearer header
25+
auth := c.Request().Header.Get("Authorization")
26+
if !strings.HasPrefix(auth, "Bearer ") {
27+
return echo.ErrUnauthorized
28+
}
29+
tokenString = strings.TrimPrefix(auth, "Bearer ")
2030
}
2131
//fmt.Println(auth)
2232

23-
tokenString := strings.TrimPrefix(auth, "Bearer ")
24-
25-
33+
//parse and validate JWT
2634
tok, err := jwt.Parse(
2735
[]byte(tokenString),
2836
jwt.WithKey(jwa.HS256, jwtKey),
@@ -31,6 +39,11 @@ func JWTAuth(next echo.HandlerFunc) echo.HandlerFunc {
3139
if err != nil {
3240
return echo.ErrUnauthorized
3341
}
42+
// verifying token_type is "access"
43+
tokenType, ok := tok.Get("token_type")
44+
if !ok || tokenType != "access" {
45+
return echo.ErrUnauthorized
46+
}
3447
userID := tok.Subject()
3548
if userID == "" {
3649
return echo.ErrUnauthorized

pkg/router/auth.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,5 +9,5 @@ func authRoutes(api *echo.Group) {
99
auth := api.Group("/auth")
1010
auth.POST("/request-otp", handlers.RequestOTP)
1111
auth.POST("/verify-otp", handlers.VerifyOTP)
12-
12+
auth.POST("/refresh", handlers.Refresh) //added refresh funk, fnc, to test tokens
1313
}

pkg/utils/jwt.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,13 +10,14 @@ import (
1010

1111
var jwtKey = []byte(os.Getenv("JWT_SECRET"))
1212

13-
func CreateJWT(userID string, email string) (string, error) {
13+
func CreateJWT(userID string, email string, tokenType string, expiry time.Duration) (string, error) {
1414
t := jwt.New()
1515

1616
t.Set(jwt.SubjectKey, userID)
1717
t.Set("email", email)
18+
t.Set("token_type", tokenType)
1819
t.Set(jwt.IssuedAtKey, time.Now())
19-
t.Set(jwt.ExpirationKey, time.Now().Add(24*time.Hour))
20+
t.Set(jwt.ExpirationKey, time.Now().Add(expiry))
2021

2122
signed, err := jwt.Sign(t, jwt.WithKey(jwa.HS256, jwtKey))
2223
return string(signed), err

pkg/utils/mailer.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
package utils
22

33
import (
4-
//"errors"
54
"fmt"
5+
//"errors"
66
//"net/smtp"
77
)
88

@@ -29,6 +29,6 @@ func SendMail(email, otp string) error {
2929
// []string{email},
3030
// msg,
3131
// )
32-
fmt .Println( otp)
32+
fmt .Println("OTP for", email,":",otp)
3333
return nil
3434
}

0 commit comments

Comments
 (0)