update: use jwt

This commit is contained in:
2026-01-04 15:51:25 +07:00
parent 69e250b439
commit 777da84692
7 changed files with 226 additions and 70 deletions

View File

@@ -1,4 +1,10 @@
DROP TABLE IF EXISTS public.verification;
DROP TABLE IF EXISTS public.jwks;
DROP TABLE IF EXISTS public.session;
DROP TABLE IF EXISTS public.account;
DROP TABLE IF EXISTS public."user";
DROP TABLE IF EXISTS public."user";
DROP TABLE IF EXISTS drizzle.__drizzle_migrations;
DROP SCHEMA IF EXISTS drizzle;
DROP EXTENSION IF EXISTS pgcrypto;

View File

@@ -1,52 +1,88 @@
CREATE EXTENSION IF NOT EXISTS pgcrypto;
CREATE TABLE IF NOT EXISTS public."user" (
id text NOT NULL,
ssh_identifier text UNIQUE NOT NULL DEFAULT substr(encode(gen_random_bytes(16), 'hex'), 1, 32),
name text NOT NULL,
email text NOT NULL,
email_verified boolean DEFAULT false NOT NULL,
image text,
created_at timestamp without time zone DEFAULT now() NOT NULL,
updated_at timestamp without time zone DEFAULT now() NOT NULL,
CONSTRAINT user_pkey PRIMARY KEY (id)
CREATE TABLE public."user" (
id TEXT PRIMARY KEY,
ssh_identifier TEXT NOT NULL DEFAULT substr(encode(gen_random_bytes(16), 'hex'), 1, 32),
name TEXT NOT NULL,
email TEXT NOT NULL,
email_verified BOOLEAN NOT NULL DEFAULT false,
image TEXT,
created_at TIMESTAMP WITHOUT TIME ZONE NOT NULL DEFAULT now(),
updated_at TIMESTAMP WITHOUT TIME ZONE NOT NULL DEFAULT now()
);
CREATE TABLE IF NOT EXISTS public.account (
id text NOT NULL,
account_id text NOT NULL,
provider_id text NOT NULL,
user_id text NOT NULL,
access_token text,
refresh_token text,
id_token text,
access_token_expires_at timestamp without time zone,
refresh_token_expires_at timestamp without time zone,
scope text,
password text,
created_at timestamp without time zone DEFAULT now() NOT NULL,
updated_at timestamp without time zone NOT NULL,
CONSTRAINT account_pkey PRIMARY KEY (id)
CREATE UNIQUE INDEX user_email_unique
ON public."user"(email);
CREATE UNIQUE INDEX user_ssh_identifier_unique
ON public."user"(ssh_identifier);
CREATE TABLE public.account (
id TEXT PRIMARY KEY,
account_id TEXT NOT NULL,
provider_id TEXT NOT NULL,
user_id TEXT NOT NULL,
access_token TEXT,
refresh_token TEXT,
id_token TEXT,
access_token_expires_at TIMESTAMP WITHOUT TIME ZONE,
refresh_token_expires_at TIMESTAMP WITHOUT TIME ZONE,
scope TEXT,
password TEXT,
created_at TIMESTAMP WITHOUT TIME ZONE NOT NULL DEFAULT now(),
updated_at TIMESTAMP WITHOUT TIME ZONE NOT NULL
);
CREATE TABLE IF NOT EXISTS public.session (
id text NOT NULL,
expires_at timestamp without time zone NOT NULL,
token text NOT NULL,
created_at timestamp without time zone DEFAULT now() NOT NULL,
updated_at timestamp without time zone NOT NULL,
ip_address text,
user_agent text,
user_id text NOT NULL,
CONSTRAINT session_pkey PRIMARY KEY (id)
CREATE INDEX account_userId_idx
ON public.account(user_id);
ALTER TABLE public.account
ADD CONSTRAINT account_user_id_user_id_fk
FOREIGN KEY (user_id)
REFERENCES public."user"(id)
ON DELETE CASCADE;
CREATE TABLE public.session (
id TEXT PRIMARY KEY,
expires_at TIMESTAMP WITHOUT TIME ZONE NOT NULL,
token TEXT NOT NULL,
created_at TIMESTAMP WITHOUT TIME ZONE NOT NULL DEFAULT now(),
updated_at TIMESTAMP WITHOUT TIME ZONE NOT NULL,
ip_address TEXT,
user_agent TEXT,
user_id TEXT NOT NULL
);
CREATE TABLE IF NOT EXISTS public.verification (
id text NOT NULL,
identifier text NOT NULL,
value text NOT NULL,
expires_at timestamp without time zone NOT NULL,
created_at timestamp without time zone DEFAULT now() NOT NULL,
updated_at timestamp without time zone DEFAULT now() NOT NULL,
CONSTRAINT verification_pkey PRIMARY KEY (id)
);
CREATE UNIQUE INDEX session_token_unique
ON public.session(token);
CREATE INDEX session_userId_idx
ON public.session(user_id);
ALTER TABLE public.session
ADD CONSTRAINT session_user_id_user_id_fk
FOREIGN KEY (user_id)
REFERENCES public."user"(id)
ON DELETE CASCADE;
CREATE TABLE public.jwks (
id TEXT PRIMARY KEY,
public_key TEXT NOT NULL,
private_key TEXT NOT NULL,
created_at TIMESTAMP WITHOUT TIME ZONE NOT NULL,
expires_at TIMESTAMP WITHOUT TIME ZONE
);
CREATE TABLE public.verification (
id TEXT PRIMARY KEY,
identifier TEXT NOT NULL,
value TEXT NOT NULL,
expires_at TIMESTAMP WITHOUT TIME ZONE NOT NULL,
created_at TIMESTAMP WITHOUT TIME ZONE NOT NULL DEFAULT now(),
updated_at TIMESTAMP WITHOUT TIME ZONE NOT NULL DEFAULT now()
);
CREATE INDEX verification_identifier_idx
ON public.verification(identifier);

27
go.mod
View File

@@ -3,20 +3,33 @@ module git.fossy.my.id/bagas/tunnel-please-controller
go 1.25.5
require (
git.fossy.my.id/bagas/tunnel-please-grpc v1.0.0
github.com/google/uuid v1.6.0
git.fossy.my.id/bagas/tunnel-please-grpc v1.2.0
github.com/jackc/pgx/v5 v5.8.0
github.com/joho/godotenv v1.5.1
github.com/lestrrat-go/httprc/v3 v3.0.1
github.com/lestrrat-go/jwx/v3 v3.0.12
google.golang.org/grpc v1.78.0
google.golang.org/protobuf v1.36.11
)
require (
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.0 // indirect
github.com/goccy/go-json v0.10.5 // indirect
github.com/jackc/pgpassfile v1.0.0 // indirect
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect
golang.org/x/net v0.47.0 // indirect
golang.org/x/sys v0.38.0 // indirect
golang.org/x/text v0.31.0 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20251029180050-ab9386a59fda // indirect
github.com/lestrrat-go/blackmagic v1.0.4 // indirect
github.com/lestrrat-go/dsig v1.0.0 // indirect
github.com/lestrrat-go/dsig-secp256k1 v1.0.0 // indirect
github.com/lestrrat-go/httpcc v1.0.1 // indirect
github.com/lestrrat-go/option v1.0.1 // indirect
github.com/lestrrat-go/option/v2 v2.0.0 // indirect
github.com/segmentio/asm v1.2.1 // indirect
github.com/valyala/fastjson v1.6.4 // indirect
golang.org/x/crypto v0.46.0 // indirect
golang.org/x/net v0.48.0 // indirect
golang.org/x/sys v0.39.0 // indirect
golang.org/x/text v0.32.0 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20251222181119-0a764e51fe1b // indirect
google.golang.org/protobuf v1.36.11 // indirect
)
replace git.fossy.my.id/bagas/tunnel-please-grpc => ../tunnel-please-grpc

50
go.sum
View File

@@ -1,12 +1,14 @@
git.fossy.my.id/bagas/tunnel-please-grpc v1.0.0 h1:G9U7YmfMBeTK8ASbqdBCmkrRsCNU3e6RUzGaTmg1NB0=
git.fossy.my.id/bagas/tunnel-please-grpc v1.0.0/go.mod h1:fG+VkArdkceGB0bNA7IFQus9GetLAwdF5Oi4jdMlXtY=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.0 h1:NMZiJj8QnKe1LgsbDayM4UoHwbvwDRwnI3hwNaAHRnc=
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.0/go.mod h1:ZXNYxsqcloTdSy/rNShjYzMhyjf0LaoftYK0p+A3h40=
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/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4=
github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
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=
@@ -23,13 +25,35 @@ github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo
github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4=
github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
github.com/lestrrat-go/blackmagic v1.0.4 h1:IwQibdnf8l2KoO+qC3uT4OaTWsW7tuRQXy9TRN9QanA=
github.com/lestrrat-go/blackmagic v1.0.4/go.mod h1:6AWFyKNNj0zEXQYfTMPfZrAXUWUfTIZ5ECEUEJaijtw=
github.com/lestrrat-go/dsig v1.0.0 h1:OE09s2r9Z81kxzJYRn07TFM9XA4akrUdoMwr0L8xj38=
github.com/lestrrat-go/dsig v1.0.0/go.mod h1:dEgoOYYEJvW6XGbLasr8TFcAxoWrKlbQvmJgCR0qkDo=
github.com/lestrrat-go/dsig-secp256k1 v1.0.0 h1:JpDe4Aybfl0soBvoVwjqDbp+9S1Y2OM7gcrVVMFPOzY=
github.com/lestrrat-go/dsig-secp256k1 v1.0.0/go.mod h1:CxUgAhssb8FToqbL8NjSPoGQlnO4w3LG1P0qPWQm/NU=
github.com/lestrrat-go/httpcc v1.0.1 h1:ydWCStUeJLkpYyjLDHihupbn2tYmZ7m22BGkcvZZrIE=
github.com/lestrrat-go/httpcc v1.0.1/go.mod h1:qiltp3Mt56+55GPVCbTdM9MlqhvzyuL6W/NMDA8vA5E=
github.com/lestrrat-go/httprc/v3 v3.0.1 h1:3n7Es68YYGZb2Jf+k//llA4FTZMl3yCwIjFIk4ubevI=
github.com/lestrrat-go/httprc/v3 v3.0.1/go.mod h1:2uAvmbXE4Xq8kAUjVrZOq1tZVYYYs5iP62Cmtru00xk=
github.com/lestrrat-go/jwx/v3 v3.0.12 h1:p25r68Y4KrbBdYjIsQweYxq794CtGCzcrc5dGzJIRjg=
github.com/lestrrat-go/jwx/v3 v3.0.12/go.mod h1:HiUSaNmMLXgZ08OmGBaPVvoZQgJVOQphSrGr5zMamS8=
github.com/lestrrat-go/option v1.0.1 h1:oAzP2fvZGQKWkvHa1/SAcFolBEca1oN+mQ7eooNBEYU=
github.com/lestrrat-go/option v1.0.1/go.mod h1:5ZHFbivi4xwXxhxY9XHDe2FHo6/Z7WWmtT7T5nBBp3I=
github.com/lestrrat-go/option/v2 v2.0.0 h1:XxrcaJESE1fokHy3FpaQ/cXW8ZsIdWcdFzzLOcID3Ss=
github.com/lestrrat-go/option/v2 v2.0.0/go.mod h1:oSySsmzMoR0iRzCDCaUfsCzxQHUEuhOViQObyy7S6Vg=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/segmentio/asm v1.2.1 h1:DTNbBqs57ioxAD4PrArqftgypG4/qNpXoJx8TVXxPR0=
github.com/segmentio/asm v1.2.1/go.mod h1:BqMnlJP91P8d+4ibuonYZw9mfnzI9HfxselHZr5aAcs=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
github.com/valyala/fastjson v1.6.4 h1:uAUNq9Z6ymTgGhcm0UynUAB6tlbakBrz6CQFax3BXVQ=
github.com/valyala/fastjson v1.6.4/go.mod h1:CLCAqky6SMuOcxStkYQvblddUtoRxhYMGLrsQns1aXY=
go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=
go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=
go.opentelemetry.io/otel v1.38.0 h1:RkfdswUDRimDg0m2Az18RKOsnI8UDzppJAtj01/Ymk8=
@@ -42,18 +66,20 @@ go.opentelemetry.io/otel/sdk/metric v1.38.0 h1:aSH66iL0aZqo//xXzQLYozmWrXxyFkBJ6
go.opentelemetry.io/otel/sdk/metric v1.38.0/go.mod h1:dg9PBnW9XdQ1Hd6ZnRz689CbtrUp0wMMs9iPcgT9EZA=
go.opentelemetry.io/otel/trace v1.38.0 h1:Fxk5bKrDZJUH+AMyyIXGcFAPah0oRcT+LuNtJrmcNLE=
go.opentelemetry.io/otel/trace v1.38.0/go.mod h1:j1P9ivuFsTceSWe1oY+EeW3sc+Pp42sO++GHkg4wwhs=
golang.org/x/net v0.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY=
golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU=
golang.org/x/sync v0.18.0 h1:kr88TuHDroi+UVf+0hZnirlk8o8T+4MrK6mr60WkH/I=
golang.org/x/sync v0.18.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc=
golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM=
golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM=
golang.org/x/crypto v0.46.0 h1:cKRW/pmt1pKAfetfu+RCEvjvZkA9RimPbh7bhFjGVBU=
golang.org/x/crypto v0.46.0/go.mod h1:Evb/oLKmMraqjZ2iQTwDwvCtJkczlDuTmdJXoZVzqU0=
golang.org/x/net v0.48.0 h1:zyQRTTrjc33Lhh0fBgT/H3oZq9WuvRR5gPC70xpDiQU=
golang.org/x/net v0.48.0/go.mod h1:+ndRgGjkh8FGtu1w1FGbEC31if4VrNVMuKTgcAAnQRY=
golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4=
golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
golang.org/x/sys v0.39.0 h1:CvCKL8MeisomCi6qNZ+wbb0DN9E5AATixKsvNtMoMFk=
golang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/text v0.32.0 h1:ZD01bjUt1FQ9WJ0ClOL5vxgxOI/sVCNgX1YtKwcY0mU=
golang.org/x/text v0.32.0/go.mod h1:o/rUWzghvpD5TXrTIBuJU77MTaN0ljMWE47kxGJQ7jY=
gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk=
gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E=
google.golang.org/genproto/googleapis/rpc v0.0.0-20251029180050-ab9386a59fda h1:i/Q+bfisr7gq6feoJnS/DlpdwEL4ihp41fvRiM3Ork0=
google.golang.org/genproto/googleapis/rpc v0.0.0-20251029180050-ab9386a59fda/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk=
google.golang.org/genproto/googleapis/rpc v0.0.0-20251222181119-0a764e51fe1b h1:Mv8VFug0MP9e5vUxfBcE3vUkV6CImK3cMNMIDFjmzxU=
google.golang.org/genproto/googleapis/rpc v0.0.0-20251222181119-0a764e51fe1b/go.mod h1:j9x/tPzZkyxcgEFkiKEEGxfvyumM01BEtsW8xzOahRQ=
google.golang.org/grpc v1.78.0 h1:K1XZG/yGDJnzMdd/uZHAkVqJE+xIDOcmdSFZkBUicNc=
google.golang.org/grpc v1.78.0/go.mod h1:I47qjTo4OKbMkjA/aOOwxDIiPSBofUtQUI5EfpWvW7U=
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=

12
internal/config/config.go Normal file
View File

@@ -0,0 +1,12 @@
package config
import "os"
func Getenv(key, defaultValue string) string {
val := os.Getenv(key)
if val == "" {
val = defaultValue
}
return val
}

View File

@@ -14,6 +14,8 @@ import (
"git.fossy.my.id/bagas/tunnel-please-controller/server"
"github.com/jackc/pgx/v5"
"github.com/joho/godotenv"
"github.com/lestrrat-go/httprc/v3"
"github.com/lestrrat-go/jwx/v3/jwk"
)
func main() {
@@ -55,7 +57,12 @@ func main() {
}(connect, ctx)
repo := repository.New(connect)
s := server.New(repo, authToken)
client := httprc.NewClient()
jwkCache, err := jwk.NewCache(ctx, client)
if err != nil {
log.Printf("failed to initialize jwk cache : %s", err)
}
s := server.New(repo, authToken, jwkCache)
log.Printf("Listening controller on %s", controllerAddr)
log.Printf("Listening api on %s", apiAddr)

View File

@@ -13,7 +13,10 @@ import (
"time"
"git.fossy.my.id/bagas/tunnel-please-controller/db/sqlc/repository"
"git.fossy.my.id/bagas/tunnel-please-controller/internal/config"
proto "git.fossy.my.id/bagas/tunnel-please-grpc/gen"
"github.com/lestrrat-go/jwx/v3/jwk"
"github.com/lestrrat-go/jwx/v3/jwt"
"google.golang.org/grpc"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/health"
@@ -39,17 +42,19 @@ type Server struct {
Subscribers map[string]*Subscriber
mu *sync.RWMutex
authToken string
jwkCache *jwk.Cache
proto.UnimplementedEventServiceServer
proto.UnimplementedSlugChangeServer
proto.UnimplementedUserServiceServer
}
func New(database *repository.Queries, authToken string) *Server {
func New(database *repository.Queries, authToken string, jwkCache *jwk.Cache) *Server {
return &Server{
Database: database,
Subscribers: make(map[string]*Subscriber),
mu: new(sync.RWMutex),
authToken: authToken,
jwkCache: jwkCache,
}
}
@@ -348,14 +353,63 @@ func (s *Server) StartAPI(ctx context.Context, Addr string) error {
IdleTimeout: 60 * time.Second,
}
jwkURL := config.Getenv("JWKS_URL", "")
if jwkURL != "" {
if err := s.jwkCache.Register(ctx, jwkURL); err != nil {
return fmt.Errorf("failed to register jwk cache: %w", err)
}
}
handler.HandleFunc("/api/sessions", func(writer http.ResponseWriter, request *http.Request) {
identity := request.URL.Query().Get("identity")
writeError := func(status int, msg string) {
writer.Header().Set("Content-Type", "application/json")
writer.WriteHeader(status)
_ = json.NewEncoder(writer).Encode(map[string]string{"error": msg})
}
var token jwt.Token
var err error
if jwkURL != "" {
keyset, err := s.jwkCache.Lookup(request.Context(), jwkURL)
if err != nil {
log.Printf("jwks lookup failed: %v", err)
writeError(http.StatusBadGateway, "unable to fetch jwks")
return
}
token, err = jwt.ParseRequest(request, jwt.WithKeySet(keyset))
if err != nil {
log.Printf("jwt parse failed: %v", err)
writeError(http.StatusUnauthorized, "invalid or expired token")
return
}
} else {
token, err = jwt.ParseRequest(request, jwt.WithVerify(false))
if err != nil {
log.Printf("jwt parse failed (no verification): %v", err)
writeError(http.StatusBadRequest, "invalid token")
return
}
}
var email string
err = token.Get("email", &email)
if err != nil {
log.Printf("email claim not found: %v", err)
writeError(http.StatusBadRequest, "missing email claim in token")
return
}
if email == "" {
writeError(http.StatusBadRequest, "empty email claim in token")
return
}
results := s.broadcastAndCollect(request.Context(), func(ctx context.Context, subscriber *Subscriber) (interface{}, bool) {
receive, err := s.sendAndReceive(ctx, subscriber, &proto.Events{
Type: proto.EventType_GET_SESSIONS,
Payload: &proto.Events_GetSessionsEvent{
GetSessionsEvent: &proto.GetSessionsEvent{
Identity: identity,
Identity: email,
},
},
}, defaultSubscriberResponseWait)
@@ -382,18 +436,20 @@ func (s *Server) StartAPI(ctx context.Context, Addr string) error {
if len(flatten) == 0 {
_, err := writer.Write([]byte("[]"))
if err != nil {
return
log.Printf("write empty sessions response failed: %v", err)
}
return
}
marshal, err := json.Marshal(flatten)
if err != nil {
http.Error(writer, "failed to marshal sessions", http.StatusInternalServerError)
log.Printf("marshal sessions failed: %v", err)
writeError(http.StatusInternalServerError, "failed to marshal sessions")
return
}
_, err = writer.Write(marshal)
if err != nil {
log.Printf("write sessions response failed: %v", err)
return
}
})