Implement Phase 4: gRPC sync service
- Proto definitions (engpad.v1.EngPadSync) with 6 RPCs - Generated Go gRPC code - Auth interceptor: username/password from metadata - SyncNotebook: upsert with full page/stroke replacement in a tx - DeleteNotebook, ListNotebooks handlers - Share link RPCs: CreateShareLink, RevokeShareLink, ListShareLinks - Share link token management (32-byte random, optional expiry) - gRPC server with TLS 1.3 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
46
internal/grpcserver/interceptors.go
Normal file
46
internal/grpcserver/interceptors.go
Normal file
@@ -0,0 +1,46 @@
|
||||
package grpcserver
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
|
||||
"git.wntrmute.dev/kyle/eng-pad-server/internal/auth"
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/metadata"
|
||||
"google.golang.org/grpc/status"
|
||||
)
|
||||
|
||||
type contextKey string
|
||||
|
||||
const userIDKey contextKey = "user_id"
|
||||
|
||||
// UserIDFromContext extracts the authenticated user ID from the context.
|
||||
func UserIDFromContext(ctx context.Context) (int64, bool) {
|
||||
id, ok := ctx.Value(userIDKey).(int64)
|
||||
return id, ok
|
||||
}
|
||||
|
||||
// AuthInterceptor verifies username/password from gRPC metadata.
|
||||
func AuthInterceptor(database *sql.DB) grpc.UnaryServerInterceptor {
|
||||
return func(ctx context.Context, req any, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (any, error) {
|
||||
md, ok := metadata.FromIncomingContext(ctx)
|
||||
if !ok {
|
||||
return nil, status.Error(codes.Unauthenticated, "missing metadata")
|
||||
}
|
||||
|
||||
usernames := md.Get("username")
|
||||
passwords := md.Get("password")
|
||||
if len(usernames) == 0 || len(passwords) == 0 {
|
||||
return nil, status.Error(codes.Unauthenticated, "missing credentials")
|
||||
}
|
||||
|
||||
userID, err := auth.AuthenticateUser(database, usernames[0], passwords[0])
|
||||
if err != nil {
|
||||
return nil, status.Error(codes.Unauthenticated, "invalid credentials")
|
||||
}
|
||||
|
||||
ctx = context.WithValue(ctx, userIDKey, userID)
|
||||
return handler(ctx, req)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user