Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ go 1.25.0

require (
github.com/apache/fory/go/fory v1.1.0
github.com/golang-jwt/jwt/v5 v5.3.1
github.com/jackc/pgx/v5 v5.10.0
golang.org/x/crypto v0.48.0
google.golang.org/grpc v1.81.1
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
github.com/golang-jwt/jwt/v5 v5.3.1 h1:kYf81DTWFe7t+1VvL7eS+jKFVWaUnK9cB1qbwn63YCY=
github.com/golang-jwt/jwt/v5 v5.3.1/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArsqaEUEa5bE=
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
Expand Down
48 changes: 48 additions & 0 deletions internal/handlers/login.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package handlers

import (
"context"

"github.com/ayush00git/goth/grpc/goth"
"golang.org/x/crypto/bcrypt"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
)

func (g *GothServer) Login(ctx context.Context, req *goth.LoginRequest) (*goth.LoginResponse, error) {
// inputs
email := req.Email
password := req.Password

// validate the inputs
if email == "" || password == "" {
return nil, status.Error(codes.InvalidArgument, "email and password are required fields.")
}

// email lookup into db.
var user RequestedUser
err := g.db.QueryRow(
ctx,
`SELECT id, email, password_hash
FROM users
WHERE $1 = email`, email,
).Scan(&user.id, &user.email, &user.password)
if err != nil {
return nil, status.Error(codes.Unauthenticated, "user not found")
}

// match the password
err = bcrypt.CompareHashAndPassword([]byte(user.password), []byte(password))
if err != nil {
return nil, status.Error(codes.Unauthenticated, "credentials do not match.")
}

// just placeholders for now
return &goth.LoginResponse{
AccessToken: "need-to-be-set",
RefreshToken: "need-to-be-set",
MfaRequired: true,
MfaSessionToken: "need-to-be-set",
MfaType: 0,
}, nil
}
39 changes: 0 additions & 39 deletions internal/handlers/goth.go → internal/handlers/signup.go
Original file line number Diff line number Diff line change
Expand Up @@ -73,42 +73,3 @@ func (g *GothServer) Signup(ctx context.Context, req *goth.SignupRequest) (*goth
EmailVerified: false,
}, nil
}


func (g *GothServer) Login(ctx context.Context, req *goth.LoginRequest) (*goth.LoginResponse, error) {
// inputs
email := req.Email
password := req.Password

// validate the inputs
if email == "" || password == "" {
return nil, status.Error(codes.InvalidArgument, "email and password are required fields.")
}

// email lookup into db.
var user RequestedUser
err := g.db.QueryRow(
ctx,
`SELECT id, email, password_hash
FROM users
WHERE $1 = email`, email,
).Scan(&user.id, &user.email, &user.password)
if err != nil {
return nil, status.Error(codes.Unauthenticated, "user not found")
}

// match the password
err = bcrypt.CompareHashAndPassword([]byte(user.password), []byte(password))
if err != nil {
return nil, status.Error(codes.Unauthenticated, "credentials do not match.")
}

// just placeholders for now
return &goth.LoginResponse{
AccessToken: "need-to-be-set",
RefreshToken: "need-to-be-set",
MfaRequired: true,
MfaSessionToken: "need-to-be-set",
MfaType: 0,
}, nil
}
87 changes: 87 additions & 0 deletions internal/services/token.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
package services

import (
"errors"
"time"

"github.com/golang-jwt/jwt/v5"
)

// jwt payload claims
type Claims struct {
UserID string `json:"user_id"`
Email string `json:"email"`
DeviceFingerprint string `json:"device_fingerprint,omitempty"`
jwt.RegisteredClaims
}

// Helper function which generates an access token which helps identifying
// authorized users at every protected handlers/rpc services.
func GenerateAccessToken (userId, email string) (string, error) {
// define the claims/payload including the registered claims.
claims := Claims{
UserID: userId,
Email: email,
RegisteredClaims: jwt.RegisteredClaims{
IssuedAt: jwt.NewNumericDate(time.Now()),
ExpiresAt: jwt.NewNumericDate(time.Now().Add(15 * time.Minute)), // expiration time - 15 min
},
}
// "token" object with "NewWithClaims" defining the signing
// method and claims contained in the object.
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)

// sign the token string.
tokenString, err := token.SignedString([]byte("jwuweufwfweif")) // move it to environment variabls after
if err != nil {
return "", err
}

return tokenString, nil
}

// Helper function which validates the user's authorization via his access token
// which allows user's access to protected rpc services.
func ValidateAccessToken(tokenString string) (*Claims, error) {
secretKey := []byte("jwuweufwfweif") // move to env variables after
// parse the jwt token string.
token, err := jwt.ParseWithClaims(tokenString, &Claims{},
func(token *jwt.Token) (interface{}, error) {
return secretKey, nil
},
)
if err != nil {
return nil, err
}

// validate the token.
if claims, ok := token.Claims.(*Claims); ok && token.Valid {
return claims, nil
}
return nil, errors.New("Invalid Token")
}

// Helper function which generates a refresh token which helps re-generating the
// access tokens, this token is stored in a database and revoked after a specific time.
func GenerateRefreshToken(userId, email, fingerprint string) (string, error) {
// build the claims object.
claims := Claims{
UserID: userId,
Email: email,
DeviceFingerprint: fingerprint,
RegisteredClaims: jwt.RegisteredClaims{
IssuedAt: jwt.NewNumericDate(time.Now()),
ExpiresAt: jwt.NewNumericDate(time.Now().Add(24 * time.Hour)), // expiration time - 24 hours
},
}

// building the token object with "NewWithClaims"
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)

tokenString, err := token.SignedString([]byte("euiy348bcues")) // move it to env vars later.
if err != nil {
return "", err
}

return tokenString, nil
}