add auth
This commit is contained in:
@@ -0,0 +1,86 @@
|
||||
package grpc
|
||||
|
||||
import (
|
||||
"context"
|
||||
"log"
|
||||
"strings"
|
||||
|
||||
"datarush/internal/auth/domain"
|
||||
"datarush/internal/auth/service"
|
||||
pb "datarush/pkg/api/auth"
|
||||
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/metadata"
|
||||
"google.golang.org/grpc/status"
|
||||
)
|
||||
|
||||
type AuthHandler struct {
|
||||
pb.UnimplementedAuthServiceServer
|
||||
authService *service.AuthService
|
||||
}
|
||||
|
||||
func NewAuthHandler(authService *service.AuthService) *AuthHandler {
|
||||
return &AuthHandler{
|
||||
authService: authService,
|
||||
}
|
||||
}
|
||||
|
||||
func (h *AuthHandler) SignUp(ctx context.Context, req *pb.SignUpRequest) (*pb.SignUpResponse, error) {
|
||||
token, err := h.authService.SignUp(ctx, req.Email, req.Username, req.Password)
|
||||
if err != nil {
|
||||
log.Printf("signup error: %v", err)
|
||||
switch err {
|
||||
case domain.ErrUserAlreadyExists:
|
||||
return nil, status.Error(codes.AlreadyExists, "user already exists")
|
||||
default:
|
||||
return nil, status.Error(codes.Internal, "internal error")
|
||||
}
|
||||
}
|
||||
|
||||
return &pb.SignUpResponse{Token: token}, nil
|
||||
}
|
||||
|
||||
func (h *AuthHandler) SignIn(ctx context.Context, req *pb.SignInRequest) (*pb.SignInResponse, error) {
|
||||
token, err := h.authService.SignIn(ctx, req.Email, req.Password)
|
||||
if err != nil {
|
||||
log.Printf("signin error: %v", err)
|
||||
switch err {
|
||||
case domain.ErrInvalidPassword, domain.ErrUserNotFound:
|
||||
return nil, status.Error(codes.Unauthenticated, "invalid credentials")
|
||||
default:
|
||||
return nil, status.Error(codes.Internal, "internal error")
|
||||
}
|
||||
}
|
||||
|
||||
return &pb.SignInResponse{Token: token}, nil
|
||||
}
|
||||
|
||||
func (h *AuthHandler) ValidateToken(ctx context.Context, req *pb.ValidateTokenRequest) (*pb.ValidateTokenResponse, error) {
|
||||
token := req.Token
|
||||
|
||||
// If token is empty, try to get it from Authorization header (for gRPC-Gateway)
|
||||
if token == "" {
|
||||
md, ok := metadata.FromIncomingContext(ctx)
|
||||
if ok {
|
||||
authHeaders := md.Get("authorization")
|
||||
if len(authHeaders) > 0 {
|
||||
parts := strings.Split(authHeaders[0], " ")
|
||||
if len(parts) == 2 && parts[0] == "Bearer" {
|
||||
token = parts[1]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if token == "" {
|
||||
return nil, status.Error(codes.Unauthenticated, "missing token")
|
||||
}
|
||||
|
||||
user, err := h.authService.ValidateToken(ctx, token)
|
||||
if err != nil {
|
||||
log.Printf("validate token error: %v", err)
|
||||
return nil, status.Error(codes.Unauthenticated, "invalid token")
|
||||
}
|
||||
|
||||
return &pb.ValidateTokenResponse{UserId: user.ID.String()}, nil
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
package grpc
|
||||
|
||||
import (
|
||||
"log"
|
||||
)
|
||||
|
||||
type ErrorHandler struct{}
|
||||
|
||||
func (e *ErrorHandler) Handle(err error) {
|
||||
if err != nil {
|
||||
log.Printf("error: %v", err)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,185 @@
|
||||
package http
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"io"
|
||||
"log"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"datarush/internal/auth/domain"
|
||||
"datarush/internal/auth/service"
|
||||
)
|
||||
|
||||
type SignUpRequest struct {
|
||||
Email string `json:"email" binding:"required,email"`
|
||||
Username string `json:"username" binding:"required"`
|
||||
Password string `json:"password" binding:"required"`
|
||||
}
|
||||
|
||||
type SignInRequest struct {
|
||||
Email string `json:"email" binding:"required,email"`
|
||||
Password string `json:"password" binding:"required"`
|
||||
}
|
||||
|
||||
type TokenResponse struct {
|
||||
Token string `json:"token"`
|
||||
}
|
||||
|
||||
type UserResponse struct {
|
||||
ID string `json:"id"`
|
||||
Email string `json:"email"`
|
||||
Username string `json:"username"`
|
||||
CreatedAt string `json:"created_at"`
|
||||
}
|
||||
|
||||
type ErrorResponse struct {
|
||||
Detail string `json:"detail"`
|
||||
}
|
||||
|
||||
type AuthHandler struct {
|
||||
authService *service.AuthService
|
||||
}
|
||||
|
||||
func NewAuthHandler(authService *service.AuthService) *AuthHandler {
|
||||
return &AuthHandler{
|
||||
authService: authService,
|
||||
}
|
||||
}
|
||||
|
||||
func (h *AuthHandler) SignUp(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodPost {
|
||||
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
|
||||
return
|
||||
}
|
||||
|
||||
var req SignUpRequest
|
||||
body, err := io.ReadAll(r.Body)
|
||||
if err != nil {
|
||||
log.Printf("read body error: %v", err)
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
json.NewEncoder(w).Encode(ErrorResponse{Detail: "invalid request"})
|
||||
return
|
||||
}
|
||||
defer r.Body.Close()
|
||||
|
||||
if err := json.Unmarshal(body, &req); err != nil {
|
||||
log.Printf("signup validation error: %v", err)
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
json.NewEncoder(w).Encode(ErrorResponse{Detail: "invalid request"})
|
||||
return
|
||||
}
|
||||
|
||||
if req.Email == "" || req.Username == "" || req.Password == "" {
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
json.NewEncoder(w).Encode(ErrorResponse{Detail: "invalid request"})
|
||||
return
|
||||
}
|
||||
|
||||
token, err := h.authService.SignUp(r.Context(), req.Email, req.Username, req.Password)
|
||||
if err != nil {
|
||||
log.Printf("signup error: %v", err)
|
||||
switch err {
|
||||
case domain.ErrUserAlreadyExists:
|
||||
w.WriteHeader(http.StatusConflict)
|
||||
json.NewEncoder(w).Encode(ErrorResponse{Detail: "user already exists"})
|
||||
default:
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
json.NewEncoder(w).Encode(ErrorResponse{Detail: "internal error"})
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.WriteHeader(http.StatusCreated)
|
||||
json.NewEncoder(w).Encode(TokenResponse{Token: token})
|
||||
}
|
||||
|
||||
func (h *AuthHandler) SignIn(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodPost {
|
||||
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
|
||||
return
|
||||
}
|
||||
|
||||
var req SignInRequest
|
||||
body, err := io.ReadAll(r.Body)
|
||||
if err != nil {
|
||||
log.Printf("read body error: %v", err)
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
json.NewEncoder(w).Encode(ErrorResponse{Detail: "invalid request"})
|
||||
return
|
||||
}
|
||||
defer r.Body.Close()
|
||||
|
||||
if err := json.Unmarshal(body, &req); err != nil {
|
||||
log.Printf("signin validation error: %v", err)
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
json.NewEncoder(w).Encode(ErrorResponse{Detail: "invalid request"})
|
||||
return
|
||||
}
|
||||
|
||||
if req.Email == "" || req.Password == "" {
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
json.NewEncoder(w).Encode(ErrorResponse{Detail: "invalid request"})
|
||||
return
|
||||
}
|
||||
|
||||
token, err := h.authService.SignIn(r.Context(), req.Email, req.Password)
|
||||
if err != nil {
|
||||
log.Printf("signin error: %v", err)
|
||||
switch err {
|
||||
case domain.ErrInvalidPassword, domain.ErrUserNotFound:
|
||||
w.WriteHeader(http.StatusUnauthorized)
|
||||
json.NewEncoder(w).Encode(ErrorResponse{Detail: "invalid credentials"})
|
||||
default:
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
json.NewEncoder(w).Encode(ErrorResponse{Detail: "internal error"})
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.WriteHeader(http.StatusOK)
|
||||
json.NewEncoder(w).Encode(TokenResponse{Token: token})
|
||||
}
|
||||
|
||||
func (h *AuthHandler) GetMe(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodGet {
|
||||
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
|
||||
return
|
||||
}
|
||||
|
||||
// Extract token from Authorization header
|
||||
authHeader := r.Header.Get("Authorization")
|
||||
if authHeader == "" {
|
||||
w.WriteHeader(http.StatusUnauthorized)
|
||||
json.NewEncoder(w).Encode(ErrorResponse{Detail: "unauthorized"})
|
||||
return
|
||||
}
|
||||
|
||||
parts := strings.Split(authHeader, " ")
|
||||
if len(parts) != 2 || parts[0] != "Bearer" {
|
||||
w.WriteHeader(http.StatusUnauthorized)
|
||||
json.NewEncoder(w).Encode(ErrorResponse{Detail: "unauthorized"})
|
||||
return
|
||||
}
|
||||
|
||||
token := parts[1]
|
||||
|
||||
user, err := h.authService.ValidateToken(r.Context(), token)
|
||||
if err != nil {
|
||||
log.Printf("validate token error: %v", err)
|
||||
w.WriteHeader(http.StatusUnauthorized)
|
||||
json.NewEncoder(w).Encode(ErrorResponse{Detail: "unauthorized"})
|
||||
return
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.WriteHeader(http.StatusOK)
|
||||
json.NewEncoder(w).Encode(UserResponse{
|
||||
ID: user.ID.String(),
|
||||
Email: user.Email,
|
||||
Username: user.Username,
|
||||
CreatedAt: user.CreatedAt.Format("2006-01-02T15:04:05Z07:00"),
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,186 @@
|
||||
package http
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"log"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"datarush/internal/auth/service"
|
||||
)
|
||||
|
||||
type UserAchievementResponse struct {
|
||||
Name string `json:"name"`
|
||||
Description string `json:"description"`
|
||||
Icon string `json:"icon"`
|
||||
ReceivedAt string `json:"received_at"`
|
||||
}
|
||||
|
||||
type UserDetailResponse struct {
|
||||
ID string `json:"id"`
|
||||
Email string `json:"email"`
|
||||
Username string `json:"username"`
|
||||
Avatar *string `json:"avatar"`
|
||||
CreatedAt string `json:"created_at"`
|
||||
Achievements []UserAchievementResponse `json:"achievements"`
|
||||
}
|
||||
|
||||
type StatResponse struct {
|
||||
TotalAttempts int `json:"total_attempts"`
|
||||
SolvedTasks int `json:"solved_tasks"`
|
||||
}
|
||||
|
||||
type UserHandler struct {
|
||||
authService *service.AuthService
|
||||
}
|
||||
|
||||
func NewUserHandler(authService *service.AuthService) *UserHandler {
|
||||
return &UserHandler{
|
||||
authService: authService,
|
||||
}
|
||||
}
|
||||
|
||||
func (h *UserHandler) GetMe(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodGet {
|
||||
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
|
||||
return
|
||||
}
|
||||
|
||||
// Extract token from Authorization header
|
||||
authHeader := r.Header.Get("Authorization")
|
||||
if authHeader == "" {
|
||||
w.WriteHeader(http.StatusUnauthorized)
|
||||
json.NewEncoder(w).Encode(ErrorResponse{Detail: "unauthorized"})
|
||||
return
|
||||
}
|
||||
|
||||
parts := strings.Split(authHeader, " ")
|
||||
if len(parts) != 2 || parts[0] != "Bearer" {
|
||||
w.WriteHeader(http.StatusUnauthorized)
|
||||
json.NewEncoder(w).Encode(ErrorResponse{Detail: "unauthorized"})
|
||||
return
|
||||
}
|
||||
|
||||
token := parts[1]
|
||||
|
||||
user, err := h.authService.ValidateToken(r.Context(), token)
|
||||
if err != nil {
|
||||
log.Printf("validate token error: %v", err)
|
||||
w.WriteHeader(http.StatusUnauthorized)
|
||||
json.NewEncoder(w).Encode(ErrorResponse{Detail: "unauthorized"})
|
||||
return
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.WriteHeader(http.StatusOK)
|
||||
json.NewEncoder(w).Encode(UserDetailResponse{
|
||||
ID: user.ID.String(),
|
||||
Email: user.Email,
|
||||
Username: user.Username,
|
||||
Avatar: nil,
|
||||
CreatedAt: user.CreatedAt.Format("2006-01-02T15:04:05Z07:00"),
|
||||
Achievements: []UserAchievementResponse{},
|
||||
})
|
||||
}
|
||||
|
||||
func (h *UserHandler) GetUser(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodGet {
|
||||
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
|
||||
return
|
||||
}
|
||||
|
||||
userID := r.Header.Get("X-User-ID")
|
||||
if userID == "" {
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
json.NewEncoder(w).Encode(ErrorResponse{Detail: "invalid user_id"})
|
||||
return
|
||||
}
|
||||
|
||||
// Extract token from Authorization header for verification
|
||||
authHeader := r.Header.Get("Authorization")
|
||||
if authHeader == "" {
|
||||
w.WriteHeader(http.StatusUnauthorized)
|
||||
json.NewEncoder(w).Encode(ErrorResponse{Detail: "unauthorized"})
|
||||
return
|
||||
}
|
||||
|
||||
parts := strings.Split(authHeader, " ")
|
||||
if len(parts) != 2 || parts[0] != "Bearer" {
|
||||
w.WriteHeader(http.StatusUnauthorized)
|
||||
json.NewEncoder(w).Encode(ErrorResponse{Detail: "unauthorized"})
|
||||
return
|
||||
}
|
||||
|
||||
token := parts[1]
|
||||
|
||||
_, err := h.authService.ValidateToken(r.Context(), token)
|
||||
if err != nil {
|
||||
log.Printf("validate token error: %v", err)
|
||||
w.WriteHeader(http.StatusUnauthorized)
|
||||
json.NewEncoder(w).Encode(ErrorResponse{Detail: "unauthorized"})
|
||||
return
|
||||
}
|
||||
|
||||
// For now, return a simple user response
|
||||
// In a real implementation, you would fetch from the database
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.WriteHeader(http.StatusOK)
|
||||
json.NewEncoder(w).Encode(UserDetailResponse{
|
||||
ID: userID,
|
||||
Email: "user@example.com",
|
||||
Username: "user",
|
||||
Avatar: nil,
|
||||
CreatedAt: "2024-12-15T10:30:00Z",
|
||||
Achievements: []UserAchievementResponse{},
|
||||
})
|
||||
}
|
||||
|
||||
func (h *UserHandler) GetMyStat(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodGet {
|
||||
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
|
||||
return
|
||||
}
|
||||
|
||||
authHeader := r.Header.Get("Authorization")
|
||||
if authHeader == "" {
|
||||
w.WriteHeader(http.StatusUnauthorized)
|
||||
json.NewEncoder(w).Encode(ErrorResponse{Detail: "unauthorized"})
|
||||
return
|
||||
}
|
||||
|
||||
parts := strings.Split(authHeader, " ")
|
||||
if len(parts) != 2 || parts[0] != "Bearer" {
|
||||
w.WriteHeader(http.StatusUnauthorized)
|
||||
json.NewEncoder(w).Encode(ErrorResponse{Detail: "unauthorized"})
|
||||
return
|
||||
}
|
||||
|
||||
token := parts[1]
|
||||
|
||||
_, err := h.authService.ValidateToken(r.Context(), token)
|
||||
if err != nil {
|
||||
log.Printf("validate token error: %v", err)
|
||||
w.WriteHeader(http.StatusUnauthorized)
|
||||
json.NewEncoder(w).Encode(ErrorResponse{Detail: "unauthorized"})
|
||||
return
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.WriteHeader(http.StatusOK)
|
||||
json.NewEncoder(w).Encode(StatResponse{
|
||||
TotalAttempts: 0,
|
||||
SolvedTasks: 0,
|
||||
})
|
||||
}
|
||||
|
||||
func (h *UserHandler) GetLeaderboard(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodGet {
|
||||
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
|
||||
return
|
||||
}
|
||||
|
||||
// Return empty leaderboard for now
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.WriteHeader(http.StatusOK)
|
||||
json.NewEncoder(w).Encode([]UserDetailResponse{})
|
||||
}
|
||||
Reference in New Issue
Block a user