feat: next

This commit is contained in:
trev 2025-05-12 11:30:33 -04:00
parent cdeaa13d92
commit 07cec78aa5
36 changed files with 1553 additions and 327 deletions

View File

@ -38,6 +38,4 @@ if [ "${updated}" = true ]; then
nix-update --flake --version=skip --subpackage client default
git add flake.nix
git commit -m "build(nix): updated nix hashes"
else
echo "nothing to update"
fi

View File

@ -21,15 +21,15 @@ apps:
dir: server
exts:
- go
onstart: go build -o ./tmp/app -tags dev && ./tmp/app
onchange: go build -o ./tmp/app -tags dev && ./tmp/app
onstart: go build -o ./tmp/app -tags dev cmd/trevstack/main.go && ./tmp/app
onchange: go build -o ./tmp/app -tags dev cmd/trevstack/main.go && ./tmp/app
nix:
color: "#74c7ec"
exts:
- nix
onstart: nix fmt
onchange: nix fmt
onstart: nix fmt .
onchange: nix fmt .
prettier:
color: "#fab387"

View File

@ -5,6 +5,9 @@ managed:
override:
- file_option: go_package_prefix
value: github.com/spotdemo4/trevstack/server/internal/connect
disable:
- file_option: go_package
module: buf.build/bufbuild/protovalidate
plugins:
- local: protoc-gen-go
@ -18,6 +21,7 @@ plugins:
- local: protoc-gen-es
out: client/src/lib/connect
opt: target=ts
include_imports: true
- local: protoc-gen-connect-openapi
out: client/static/openapi

6
buf.lock Normal file
View File

@ -0,0 +1,6 @@
# Generated by buf. DO NOT EDIT.
version: v2
deps:
- name: buf.build/bufbuild/protovalidate
commit: 8976f5be98c146529b1cc15cd2012b60
digest: b5:5d513af91a439d9e78cacac0c9455c7cb885a8737d30405d0b91974fe05276d19c07a876a51a107213a3d01b83ecc912996cdad4cddf7231f91379079cf7488d

View File

@ -2,3 +2,5 @@
version: v2
modules:
- path: proto
deps:
- buf.build/bufbuild/protovalidate

12
flake.lock generated
View File

@ -2,11 +2,11 @@
"nodes": {
"nixpkgs": {
"locked": {
"lastModified": 1744463964,
"narHash": "sha256-LWqduOgLHCFxiTNYi3Uj5Lgz0SR+Xhw3kr/3Xd0GPTM=",
"lastModified": 1746663147,
"narHash": "sha256-Ua0drDHawlzNqJnclTJGf87dBmaO/tn7iZ+TCkTRpRc=",
"owner": "nixos",
"repo": "nixpkgs",
"rev": "2631b0b7abcea6e640ce31cd78ea58910d31e650",
"rev": "dda3dcd3fe03e991015e9a74b22d35950f264a54",
"type": "github"
},
"original": {
@ -43,11 +43,11 @@
"nixpkgs": "nixpkgs_2"
},
"locked": {
"lastModified": 1744776944,
"narHash": "sha256-wdmVDDyz6aZpFai2P0PBb2sMRBTgnaURylJw8wis6ks=",
"lastModified": 1744951889,
"narHash": "sha256-SXVCC/3rmTRJ4qhi63F4A9hgyGya0CTwA+EhPcow9rU=",
"owner": "spotdemo4",
"repo": "treli",
"rev": "eefe10c1ba62a577592ff25e6e5b274215c1d8db",
"rev": "6f1413a2e7324f44eb5f1dc4a2fc506c7dae4fb4",
"type": "github"
},
"original": {

View File

@ -2,11 +2,14 @@ syntax = "proto3";
package item.v1;
import "buf/validate/validate.proto";
import "google/protobuf/timestamp.proto";
message Item {
int64 id = 1;
string name = 2;
string name = 2 [
(buf.validate.field).string.min_len = 3
];
google.protobuf.Timestamp added = 3;
string description = 4;
float price = 5;

View File

@ -2,17 +2,21 @@ syntax = "proto3";
package user.v1;
import "buf/validate/validate.proto";
service AuthService {
rpc Login (LoginRequest) returns (LoginResponse) {}
rpc SignUp (SignUpRequest) returns (SignUpResponse) {}
rpc Logout (LogoutRequest) returns (LogoutResponse) {}
// rpc GetPasskeyIDs (GetPasskeyIDsRequest) returns (GetPasskeyIDsResponse) {}
// rpc BeginPasskeyLogin (BeginPasskeyLoginRequest) returns (BeginPasskeyLoginResponse) {}
// rpc FinishPasskeyLogin (FinishPasskeyLoginRequest) returns (FinishPasskeyLoginResponse) {}
rpc BeginPasskeyLogin (BeginPasskeyLoginRequest) returns (BeginPasskeyLoginResponse) {}
rpc FinishPasskeyLogin (FinishPasskeyLoginRequest) returns (FinishPasskeyLoginResponse) {}
}
message LoginRequest {
string username = 1;
string username = 1 [
(buf.validate.field).string.min_len = 3
];
string password = 2;
}
message LoginResponse {
@ -20,7 +24,9 @@ message LoginResponse {
}
message SignUpRequest {
string username = 1;
string username = 1 [
(buf.validate.field).string.min_len = 3
];
string password = 2;
string confirm_password = 3;
}
@ -29,15 +35,17 @@ message SignUpResponse {}
message LogoutRequest {}
message LogoutResponse {}
// message GetPasskeyIDsRequest {
// string username = 1;
// }
// message GetPasskeyIDsResponse {
// repeated string passkey_ids = 1;
// }
message BeginPasskeyLoginRequest {
string username = 1;
}
message BeginPasskeyLoginResponse {
string options_json = 1;
}
// message BeginPasskeyLoginRequest {}
// message BeginPasskeyLoginResponse {}
// message FinishPasskeyLoginRequest {}
// message FinishPasskeyLoginResponse {}
message FinishPasskeyLoginRequest {
string username = 1;
string attestation = 2;
}
message FinishPasskeyLoginResponse {
string token = 1;
}

View File

@ -13,8 +13,9 @@ service UserService {
rpc UpdatePassword (UpdatePasswordRequest) returns (UpdatePasswordResponse) {}
rpc GetAPIKey (GetAPIKeyRequest) returns (GetAPIKeyResponse) {}
rpc UpdateProfilePicture (UpdateProfilePictureRequest) returns (UpdateProfilePictureResponse) {}
// rpc BeginPasskeyRegistration (BeginPasskeyRegistrationRequest) returns (BeginPasskeyRegistrationResponse) {}
// rpc FinishPasskeyRegistration (FinishPasskeyRegistrationRequest) returns (FinishPasskeyRegistrationResponse) {}
rpc BeginPasskeyRegistration (BeginPasskeyRegistrationRequest) returns (BeginPasskeyRegistrationResponse) {}
rpc FinishPasskeyRegistration (FinishPasskeyRegistrationRequest) returns (FinishPasskeyRegistrationResponse) {}
}
message GetUserRequest {}
@ -47,6 +48,16 @@ message UpdateProfilePictureResponse {
User user = 1;
}
message BeginPasskeyRegistrationRequest {}
message BeginPasskeyRegistrationResponse {
string options_json = 1;
}
message FinishPasskeyRegistrationRequest {
string attestation = 1;
}
message FinishPasskeyRegistrationResponse {}
// message BeginPasskeyRegistrationRequest {}
// message BeginPasskeyRegistrationResponse {}

View File

@ -8,11 +8,14 @@ import (
"log"
"log/slog"
"net/http"
"net/url"
"os"
"os/signal"
"syscall"
"time"
"connectrpc.com/validate"
"github.com/go-webauthn/webauthn/webauthn"
"github.com/joho/godotenv"
"golang.org/x/net/http2"
"golang.org/x/net/http2/h2c"
@ -48,11 +51,27 @@ func main() {
log.Fatalf("failed to connect to database: %s", err.Error())
}
// Create webauthn
webAuthn, err := webauthn.New(&webauthn.Config{
RPDisplayName: env.Name,
RPID: env.URL.Hostname(),
RPOrigins: []string{env.URL.String()},
})
if err != nil {
log.Fatalf("failed to create webauthn: %s", err.Error())
}
// Create validate interceptor
vi, err := validate.NewInterceptor()
if err != nil {
log.Fatalf("failed to create validator: %s", err.Error())
}
// Serve GRPC Handlers
api := http.NewServeMux()
api.Handle(interceptors.WithCORS(user.NewAuthHandler(sqlc, env.Key)))
api.Handle(interceptors.WithCORS(user.NewHandler(sqlc, env.Key)))
api.Handle(interceptors.WithCORS(item.NewHandler(sqlc, env.Key)))
api.Handle(interceptors.WithCORS(user.NewAuthHandler(vi, sqlc, webAuthn, env.Name, env.Key)))
api.Handle(interceptors.WithCORS(user.NewHandler(vi, sqlc, webAuthn, env.Key)))
api.Handle(interceptors.WithCORS(item.NewHandler(vi, sqlc, env.Key)))
// Serve web interface
mux := http.NewServeMux()
@ -93,6 +112,8 @@ func main() {
type env struct {
Port string
Key string
Name string
URL *url.URL
DatabaseURL string
}
@ -106,19 +127,36 @@ func getEnv() (*env, error) {
env := env{
Port: os.Getenv("PORT"),
Key: os.Getenv("KEY"),
Name: os.Getenv("NAME"),
DatabaseURL: os.Getenv("DATABASE_URL"),
}
// Validate
if env.Port == "" {
env.Port = "8080"
log.Printf("env 'PORT' not found, defaulting to %s\n", env.Port)
}
if env.Key == "" {
return nil, errors.New("env 'key' not found")
return nil, errors.New("env 'KEY' not found")
}
if env.Name == "" {
env.Name = "trevstack"
log.Printf("env 'NAME' not found, defaulting to %s\n", env.Name)
}
if env.DatabaseURL == "" {
return nil, errors.New("env 'DATABASE_URL' not found")
}
// Parse URL
if os.Getenv("URL") == "" {
env.URL, _ = url.Parse("http://localhost:" + env.Port)
log.Printf("env 'URL' not found, defaulting to %s\n", env.URL.String())
} else {
env.URL, err = url.Parse(os.Getenv("URL"))
if err != nil {
return nil, err
}
}
return &env, nil
}

View File

@ -0,0 +1,22 @@
-- migrate:up
CREATE TABLE credential (
cred_id TEXT PRIMARY KEY NOT NULL,
cred_public_key BLOB NOT NULL,
sign_count INTEGER NOT NULL,
transports TEXT,
user_verified BOOLEAN,
backup_eligible BOOLEAN,
backup_state BOOLEAN,
attestation_object BLOB,
attestation_client_data BLOB,
created_at DATETIME NOT NULL,
last_used DATETIME NOT NULL,
user_id INTEGER NOT NULL,
FOREIGN KEY (user_id) REFERENCES user (id)
);
ALTER TABLE user ADD webauthn_id TEXT NOT NULL;
-- migrate:down
DROP TABLE credential;
ALTER TABLE user DROP COLUMN webauthn_id;

View File

@ -0,0 +1,83 @@
-- name: GetCredential :one
SELECT
cred_id,
cred_public_key,
sign_count,
transports,
user_verified,
backup_eligible,
backup_state,
attestation_object,
attestation_client_data,
created_at,
last_used,
user_id
FROM credential
WHERE
cred_id = @id
AND
user_id = @user_id
LIMIT 1;
-- name: GetCredentials :many
SELECT
cred_id,
cred_public_key,
sign_count,
transports,
user_verified,
backup_eligible,
backup_state,
attestation_object,
attestation_client_data,
created_at,
last_used,
user_id
FROM credential
WHERE user_id = @user_id;
-- name: InsertCredential :exec
INSERT INTO credential (
cred_id,
cred_public_key,
sign_count,
transports,
user_verified,
backup_eligible,
backup_state,
attestation_object,
attestation_client_data,
created_at,
last_used,
user_id
) VALUES (
@cred_id,
@cred_public_key,
@sign_count,
@transports,
@user_verified,
@backup_eligible,
@backup_state,
@attestation_object,
@attestation_client_data,
@created_at,
@last_used,
@user_id
);
-- name: UpdateCredential :exec
UPDATE credential
SET
last_used = COALESCE(sqlc.narg('last_used'), last_used),
sign_count = COALESCE(sqlc.narg('sign_count'), sign_count)
WHERE
cred_id = @id
AND
user_id = @user_id;
-- name: DeleteCredential :exec
DELETE FROM credential
WHERE
cred_id = @id
AND
user_id = @user_id;

View File

@ -34,6 +34,7 @@ WHERE
AND
(added <= sqlc.narg('end') OR sqlc.narg('end') IS NULL)
)
ORDER BY added DESC
LIMIT
@limit
OFFSET

View File

@ -3,7 +3,8 @@ SELECT
id,
username,
password,
profile_picture_id
profile_picture_id,
webauthn_id
FROM user
WHERE
id = @id
@ -14,7 +15,8 @@ SELECT
id,
username,
password,
profile_picture_id
profile_picture_id,
webauthn_id
FROM user
WHERE
username = @username
@ -23,10 +25,12 @@ LIMIT 1;
-- name: InsertUser :one
INSERT INTO user (
username,
password
password,
webauthn_id
) VALUES (
@username,
@password
@password,
@webauthn_id
)
RETURNING id;

View File

@ -3,7 +3,7 @@ CREATE TABLE user (
id INTEGER PRIMARY KEY NOT NULL,
username TEXT NOT NULL,
password TEXT NOT NULL,
profile_picture_id INTEGER,
profile_picture_id INTEGER, webauthn_id TEXT NOT NULL,
FOREIGN KEY (profile_picture_id) REFERENCES file (id)
);
@ -26,6 +26,23 @@ CREATE TABLE item (
FOREIGN KEY (user_id) REFERENCES user (id)
);
CREATE TABLE credential (
cred_id TEXT PRIMARY KEY NOT NULL,
cred_public_key BLOB NOT NULL,
sign_count INTEGER NOT NULL,
transports TEXT,
user_verified BOOLEAN,
backup_eligible BOOLEAN,
backup_state BOOLEAN,
attestation_object BLOB,
attestation_client_data BLOB,
created_at DATETIME NOT NULL,
last_used DATETIME NOT NULL,
user_id INTEGER NOT NULL,
FOREIGN KEY (user_id) REFERENCES user (id)
);
-- Dbmate schema migrations
INSERT INTO "schema_migrations" (version) VALUES
('20250410195416');
('20250410195416'),
('20250418055807');

View File

@ -3,31 +3,47 @@ module github.com/spotdemo4/trevstack/server
go 1.24.1
require (
buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.6-20250425153114-8976f5be98c1.1
connectrpc.com/connect v1.18.1
connectrpc.com/cors v0.1.0
github.com/amacneil/dbmate/v2 v2.26.0
connectrpc.com/validate v0.3.0
github.com/amacneil/dbmate/v2 v2.27.0
github.com/go-webauthn/webauthn v0.12.3
github.com/golang-jwt/jwt/v5 v5.2.2
github.com/google/uuid v1.6.0
github.com/joho/godotenv v1.5.1
github.com/rs/cors v1.11.1
github.com/spotdemo4/dbmate-sqlite-modernc v0.0.2
golang.org/x/crypto v0.37.0
golang.org/x/net v0.39.0
golang.org/x/crypto v0.38.0
golang.org/x/net v0.40.0
golang.org/x/time v0.11.0
google.golang.org/protobuf v1.36.6
modernc.org/sqlite v1.37.0
)
require (
buf.build/go/protovalidate v0.12.0 // indirect
cel.dev/expr v0.24.0 // indirect
github.com/antlr4-go/antlr/v4 v4.13.1 // indirect
github.com/dustin/go-humanize v1.0.1 // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/fxamacker/cbor/v2 v2.8.0 // indirect
github.com/go-webauthn/x v0.1.21 // indirect
github.com/google/cel-go v0.25.0 // indirect
github.com/google/go-cmp v0.7.0 // indirect
github.com/google/go-tpm v0.9.5 // indirect
github.com/lib/pq v1.10.9 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/mitchellh/mapstructure v1.5.0 // indirect
github.com/ncruces/go-strftime v0.1.9 // indirect
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
golang.org/x/exp v0.0.0-20250408133849-7e4ce0ab07d0 // indirect
golang.org/x/sys v0.32.0 // indirect
golang.org/x/text v0.24.0 // indirect
modernc.org/libc v1.62.1 // indirect
github.com/stoewer/go-strcase v1.3.0 // indirect
github.com/x448/float16 v0.8.4 // indirect
golang.org/x/exp v0.0.0-20250506013437-ce4c2cf36ca6 // indirect
golang.org/x/sys v0.33.0 // indirect
golang.org/x/text v0.25.0 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20250505200425-f936aa4a68b2 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20250505200425-f936aa4a68b2 // indirect
modernc.org/libc v1.65.3 // indirect
modernc.org/mathutil v1.7.1 // indirect
modernc.org/memory v1.10.0 // indirect
)

View File

@ -1,21 +1,42 @@
buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.6-20250425153114-8976f5be98c1.1 h1:YhMSc48s25kr7kv31Z8vf7sPUIq5YJva9z1mn/hAt0M=
buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.6-20250425153114-8976f5be98c1.1/go.mod h1:avRlCjnFzl98VPaeCtJ24RrV/wwHFzB8sWXhj26+n/U=
buf.build/go/protovalidate v0.12.0 h1:4GKJotbspQjRCcqZMGVSuC8SjwZ/FmgtSuKDpKUTZew=
buf.build/go/protovalidate v0.12.0/go.mod h1:q3PFfbzI05LeqxSwq+begW2syjy2Z6hLxZSkP1OH/D0=
cel.dev/expr v0.24.0 h1:56OvJKSH3hDGL0ml5uSxZmz3/3Pq4tJ+fb1unVLAFcY=
cel.dev/expr v0.24.0/go.mod h1:hLPLo1W4QUmuYdA72RBX06QTs6MXw941piREPl3Yfiw=
connectrpc.com/connect v1.18.1 h1:PAg7CjSAGvscaf6YZKUefjoih5Z/qYkyaTrBW8xvYPw=
connectrpc.com/connect v1.18.1/go.mod h1:0292hj1rnx8oFrStN7cB4jjVBeqs+Yx5yDIC2prWDO8=
connectrpc.com/cors v0.1.0 h1:f3gTXJyDZPrDIZCQ567jxfD9PAIpopHiRDnJRt3QuOQ=
connectrpc.com/cors v0.1.0/go.mod h1:v8SJZCPfHtGH1zsm+Ttajpozd4cYIUryl4dFB6QEpfg=
connectrpc.com/validate v0.3.0 h1:eMPASBQM+ztVzuLSXddB61zwJKzvWWZ6RLdIwTgh9Wo=
connectrpc.com/validate v0.3.0/go.mod h1:QLGN/m+oDeI4zaDAANK1L1G5K4i8gg6CUUwyl3HAG4A=
filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=
filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
github.com/amacneil/dbmate/v2 v2.26.0 h1:74ykEWh0V41BU3wesgmTowMn/2x9rUfIxIG+Q+vuUm0=
github.com/amacneil/dbmate/v2 v2.26.0/go.mod h1:cnjZKm5x/gKMLPfExXbEDUUQi1Sv5UqY3AIgbnxql84=
github.com/amacneil/dbmate/v2 v2.27.0 h1:A9JCrHD2z7bbPashxSdS17Xhfzzpu/2oB67P6j/xTVY=
github.com/amacneil/dbmate/v2 v2.27.0/go.mod h1:3OcOFCWRyY5VhRPTGaFq6Siijgzecoe5+0A3oZbaHIc=
github.com/antlr4-go/antlr/v4 v4.13.1 h1:SqQKkuVZ+zWkMMNkjy5FZe5mr5WURWnlpmOuzYWrPrQ=
github.com/antlr4-go/antlr/v4 v4.13.1/go.mod h1:GKmUxMtwp6ZgGwZSva4eWPC5mS6vUAmOABFgjdkM7Nw=
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/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
github.com/go-sql-driver/mysql v1.9.0 h1:Y0zIbQXhQKmQgTp44Y1dp3wTXcn804QoTptLZT1vtvo=
github.com/go-sql-driver/mysql v1.9.0/go.mod h1:pDetrLJeA3oMujJuvXc8RJoasr589B6A9fwzD3QMrqw=
github.com/fxamacker/cbor/v2 v2.8.0 h1:fFtUGXUzXPHTIUdne5+zzMPTfffl3RD5qYnkY40vtxU=
github.com/fxamacker/cbor/v2 v2.8.0/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ=
github.com/go-sql-driver/mysql v1.9.2 h1:4cNKDYQ1I84SXslGddlsrMhc8k4LeDVj6Ad6WRjiHuU=
github.com/go-sql-driver/mysql v1.9.2/go.mod h1:qn46aNg1333BRMNU69Lq93t8du/dwxI64Gl8i5p1WMU=
github.com/go-webauthn/webauthn v0.12.3 h1:hHQl1xkUuabUU9uS+ISNCMLs9z50p9mDUZI/FmkayNE=
github.com/go-webauthn/webauthn v0.12.3/go.mod h1:4JRe8Z3W7HIw8NGEWn2fnUwecoDzkkeach/NnvhkqGY=
github.com/go-webauthn/x v0.1.21 h1:nFbckQxudvHEJn2uy1VEi713MeSpApoAv9eRqsb9AdQ=
github.com/go-webauthn/x v0.1.21/go.mod h1:sEYohtg1zL4An1TXIUIQ5csdmoO+WO0R4R2pGKaHYKA=
github.com/golang-jwt/jwt/v5 v5.2.2 h1:Rl4B7itRWVtYIHFrSNd7vhTiz9UpLdi6gZhZ3wEeDy8=
github.com/golang-jwt/jwt/v5 v5.2.2/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/cel-go v0.25.0 h1:jsFw9Fhn+3y2kBbltZR4VEz5xKkcIFRPDnuEzAGv5GY=
github.com/google/cel-go v0.25.0/go.mod h1:hjEb6r5SuOSlhCHmFoLzu8HGCERvIsDAbxDAyNU/MmI=
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
github.com/google/go-tpm v0.9.5 h1:ocUmnDebX54dnW+MQWGQRbdaAcJELsa6PqZhJ48KwVU=
github.com/google/go-tpm v0.9.5/go.mod h1:h9jEsEECg7gtLis0upRBQU+GhYVH6jMjrFxI8u6bVUY=
github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e h1:ijClszYn+mADRFY17kjQEVQ1XRhq2/JR1M3sGqeJoxs=
github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e/go.mod h1:boTsfXsheKC2y+lKOCMpSfarhxDeIzfZG1jqGcPl3cA=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
@ -26,8 +47,10 @@ github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw=
github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mattn/go-sqlite3 v1.14.24 h1:tpSp2G2KyMnnQu99ngJ47EIkWVmliIizyZBfPrBWDRM=
github.com/mattn/go-sqlite3 v1.14.24/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
github.com/mattn/go-sqlite3 v1.14.28 h1:ThEiQrnbtumT+QMknw63Befp/ce/nUPgBPMlRFEum7A=
github.com/mattn/go-sqlite3 v1.14.28/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
github.com/ncruces/go-strftime v0.1.9 h1:bY0MQC28UADQmHmaF5dgpLmImcShSi2kHU9XLdhx/f4=
github.com/ncruces/go-strftime v0.1.9/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
@ -38,43 +61,59 @@ github.com/rs/cors v1.11.1 h1:eU3gRzXLRK57F5rKMGMZURNdIG4EoAmX8k94r9wXWHA=
github.com/rs/cors v1.11.1/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU=
github.com/spotdemo4/dbmate-sqlite-modernc v0.0.2 h1:537TA0HvjoWJxPzAhP+t2Gt82NpxGhCdmDsJpta7sdc=
github.com/spotdemo4/dbmate-sqlite-modernc v0.0.2/go.mod h1:JwuvDDgb1VLGyHgSsQN3I+dx6QkEGk86o6Yc8vxzG/4=
github.com/stoewer/go-strcase v1.3.0 h1:g0eASXYtp+yvN9fK8sH94oCIk0fau9uV1/ZdJ0AVEzs=
github.com/stoewer/go-strcase v1.3.0/go.mod h1:fAH5hQ5pehh+j3nZfvwdk2RgEgQjAoM8wodgtPmh1xo=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM=
github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg=
github.com/zenizh/go-capturer v0.0.0-20211219060012-52ea6c8fed04 h1:qXafrlZL1WsJW5OokjraLLRURHiw0OzKHD/RNdspp4w=
github.com/zenizh/go-capturer v0.0.0-20211219060012-52ea6c8fed04/go.mod h1:FiwNQxz6hGoNFBC4nIx+CxZhI3nne5RmIOlT/MXcSD4=
golang.org/x/crypto v0.37.0 h1:kJNSjF/Xp7kU0iB2Z+9viTPMW4EqqsrywMXLJOOsXSE=
golang.org/x/crypto v0.37.0/go.mod h1:vg+k43peMZ0pUMhYmVAWysMK35e6ioLh3wB8ZCAfbVc=
golang.org/x/exp v0.0.0-20250408133849-7e4ce0ab07d0 h1:R84qjqJb5nVJMxqWYb3np9L5ZsaDtB+a39EqjV0JSUM=
golang.org/x/exp v0.0.0-20250408133849-7e4ce0ab07d0/go.mod h1:S9Xr4PYopiDyqSyp5NjCrhFrqg6A5zA2E/iPHPhqnS8=
golang.org/x/crypto v0.38.0 h1:jt+WWG8IZlBnVbomuhg2Mdq0+BBQaHbtqHEFEigjUV8=
golang.org/x/crypto v0.38.0/go.mod h1:MvrbAqul58NNYPKnOra203SB9vpuZW0e+RRZV+Ggqjw=
golang.org/x/exp v0.0.0-20250506013437-ce4c2cf36ca6 h1:y5zboxd6LQAqYIhHnB48p0ByQ/GnQx2BE33L8BOHQkI=
golang.org/x/exp v0.0.0-20250506013437-ce4c2cf36ca6/go.mod h1:U6Lno4MTRCDY+Ba7aCcauB9T60gsv5s4ralQzP72ZoQ=
golang.org/x/mod v0.24.0 h1:ZfthKaKaT4NrhGVZHO1/WDTwGES4De8KtWO0SIbNJMU=
golang.org/x/mod v0.24.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww=
golang.org/x/net v0.39.0 h1:ZCu7HMWDxpXpaiKdhzIfaltL9Lp31x/3fCP11bc6/fY=
golang.org/x/net v0.39.0/go.mod h1:X7NRbYVEA+ewNkCNyJ513WmMdQ3BineSwVtN2zD/d+E=
golang.org/x/sync v0.13.0 h1:AauUjRAJ9OSnvULf/ARrrVywoJDy0YS2AwQ98I37610=
golang.org/x/sync v0.13.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
golang.org/x/net v0.40.0 h1:79Xs7wF06Gbdcg4kdCCIQArK11Z1hr5POQ6+fIYHNuY=
golang.org/x/net v0.40.0/go.mod h1:y0hY0exeL2Pku80/zKK7tpntoX23cqL3Oa6njdgRtds=
golang.org/x/sync v0.14.0 h1:woo0S4Yywslg6hp4eUFjTVOyKt0RookbpAHG4c1HmhQ=
golang.org/x/sync v0.14.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.32.0 h1:s77OFDvIQeibCmezSnk/q6iAfkdiQaJi4VzroCFrN20=
golang.org/x/sys v0.32.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
golang.org/x/text v0.24.0 h1:dd5Bzh4yt5KYA8f9CJHCP4FB4D51c2c6JvN37xJJkJ0=
golang.org/x/text v0.24.0/go.mod h1:L8rBsPeo2pSS+xqN0d5u2ikmjtmoJbDBT1b7nHvFCdU=
golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw=
golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
golang.org/x/text v0.25.0 h1:qVyWApTSYLk/drJRO5mDlNYskwQznZmkpV2c8q9zls4=
golang.org/x/text v0.25.0/go.mod h1:WEdwpYrmk1qmdHvhkSTNPm3app7v4rsT8F2UD6+VHIA=
golang.org/x/time v0.11.0 h1:/bpjEDfN9tkoN/ryeYHnv5hcMlc8ncjMcM4XBk5NWV0=
golang.org/x/time v0.11.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg=
golang.org/x/tools v0.32.0 h1:Q7N1vhpkQv7ybVzLFtTjvQya2ewbwNDZzUgfXGqtMWU=
golang.org/x/tools v0.32.0/go.mod h1:ZxrU41P/wAbZD8EDa6dDCa6XfpkhJ7HFMjHJXfBDu8s=
golang.org/x/tools v0.33.0 h1:4qz2S3zmRxbGIhDIAgjxvFutSvH5EfnsYrRBj0UI0bc=
golang.org/x/tools v0.33.0/go.mod h1:CIJMaWEY88juyUfo7UbgPqbC8rU2OqfAV1h2Qp0oMYI=
google.golang.org/genproto/googleapis/api v0.0.0-20250505200425-f936aa4a68b2 h1:vPV0tzlsK6EzEDHNNH5sa7Hs9bd7iXR7B1tSiPepkV0=
google.golang.org/genproto/googleapis/api v0.0.0-20250505200425-f936aa4a68b2/go.mod h1:pKLAc5OolXC3ViWGI62vvC0n10CpwAtRcTNCFwTKBEw=
google.golang.org/genproto/googleapis/rpc v0.0.0-20250505200425-f936aa4a68b2 h1:IqsN8hx+lWLqlN+Sc3DoMy/watjofWiU8sRFgQ8fhKM=
google.golang.org/genproto/googleapis/rpc v0.0.0-20250505200425-f936aa4a68b2/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A=
google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY=
google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
modernc.org/cc/v4 v4.25.2 h1:T2oH7sZdGvTaie0BRNFbIYsabzCxUQg8nLqCdQ2i0ic=
modernc.org/cc/v4 v4.25.2/go.mod h1:uVtb5OGqUKpoLWhqwNQo/8LwvoiEBLvZXIQ/SmO6mL0=
modernc.org/ccgo/v4 v4.25.1 h1:TFSzPrAGmDsdnhT9X2UrcPMI3N/mJ9/X9ykKXwLhDsU=
modernc.org/ccgo/v4 v4.25.1/go.mod h1:njjuAYiPflywOOrm3B7kCB444ONP5pAVr8PIEoE0uDw=
modernc.org/fileutil v1.3.0 h1:gQ5SIzK3H9kdfai/5x41oQiKValumqNTDXMvKo62HvE=
modernc.org/fileutil v1.3.0/go.mod h1:XatxS8fZi3pS8/hKG2GH/ArUogfxjpEKs3Ku3aK4JyQ=
modernc.org/cc/v4 v4.26.1 h1:+X5NtzVBn0KgsBCBe+xkDC7twLb/jNVj9FPgiwSQO3s=
modernc.org/cc/v4 v4.26.1/go.mod h1:uVtb5OGqUKpoLWhqwNQo/8LwvoiEBLvZXIQ/SmO6mL0=
modernc.org/ccgo/v4 v4.27.1 h1:emhLB4uoOmkZUnTDFcMI3AbkmU/Evjuerit9Taqe6Ss=
modernc.org/ccgo/v4 v4.27.1/go.mod h1:543Q0qQhJWekKVS5P6yL5fO6liNhla9Lbm2/B3rEKDE=
modernc.org/fileutil v1.3.1 h1:8vq5fe7jdtEvoCf3Zf9Nm0Q05sH6kGx0Op2CPx1wTC8=
modernc.org/fileutil v1.3.1/go.mod h1:HxmghZSZVAz/LXcMNwZPA/DRrQZEVP9VX0V4LQGQFOc=
modernc.org/gc/v2 v2.6.5 h1:nyqdV8q46KvTpZlsw66kWqwXRHdjIlJOhG6kxiV/9xI=
modernc.org/gc/v2 v2.6.5/go.mod h1:YgIahr1ypgfe7chRuJi2gD7DBQiKSLMPgBQe9oIiito=
modernc.org/libc v1.62.1 h1:s0+fv5E3FymN8eJVmnk0llBe6rOxCu/DEU+XygRbS8s=
modernc.org/libc v1.62.1/go.mod h1:iXhATfJQLjG3NWy56a6WVU73lWOcdYVxsvwCgoPljuo=
modernc.org/libc v1.65.3 h1:umuvKKemW1RATk14y2f0IMPYa/Bi8NB+iL+kOQYNWAw=
modernc.org/libc v1.65.3/go.mod h1:VI3V2S5mNka4deJErQ0jsMXe7jgxojE2fOB/mWoHlbc=
modernc.org/mathutil v1.7.1 h1:GCZVGXdaN8gTqB1Mf/usp1Y/hSqgI2vAGGP4jZMCxOU=
modernc.org/mathutil v1.7.1/go.mod h1:4p5IwJITfppl0G4sUEDtCr4DthTaT47/N3aT6MhfgJg=
modernc.org/memory v1.10.0 h1:fzumd51yQ1DxcOxSO+S6X7+QTuVU+n8/Aj7swYjFfC4=

View File

@ -0,0 +1,49 @@
package auth
import (
"strings"
"github.com/go-webauthn/webauthn/protocol"
"github.com/go-webauthn/webauthn/webauthn"
"github.com/spotdemo4/trevstack/server/internal/sqlc"
)
func NewCreds(creds []sqlc.Credential) []webauthn.Credential {
webauthnCreds := []webauthn.Credential{}
for _, c := range creds {
transports := []protocol.AuthenticatorTransport{}
if c.Transports != nil {
for t := range strings.SplitSeq(*c.Transports, " ") {
transports = append(transports, protocol.AuthenticatorTransport(t))
}
}
flags := webauthn.CredentialFlags{}
if c.UserVerified != nil {
flags.UserVerified = *c.UserVerified
}
if c.BackupEligible != nil {
flags.BackupEligible = *c.BackupEligible
}
if c.BackupState != nil {
flags.BackupState = *c.BackupState
}
webauthnCreds = append(webauthnCreds, webauthn.Credential{
ID: []byte(c.CredID),
PublicKey: c.CredPublicKey,
Authenticator: webauthn.Authenticator{
SignCount: uint32(c.SignCount),
},
Transport: transports,
Flags: flags,
Attestation: webauthn.CredentialAttestation{
Object: c.AttestationObject,
ClientDataJSON: c.AttestationClientData,
},
})
}
return webauthnCreds
}

View File

@ -0,0 +1,33 @@
package auth
import "github.com/go-webauthn/webauthn/webauthn"
type User struct {
id string
username string
credentials []webauthn.Credential
}
func NewUser(id string, username string, credentials []webauthn.Credential) User {
return User{
id: id,
username: username,
credentials: credentials,
}
}
func (u User) WebAuthnID() []byte {
return []byte(u.id)
}
func (u User) WebAuthnName() string {
return u.username
}
func (u User) WebAuthnDisplayName() string {
return u.username
}
func (u User) WebAuthnCredentials() []webauthn.Credential {
return u.credentials
}

View File

@ -7,6 +7,7 @@
package itemv1
import (
_ "buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go/buf/validate"
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
timestamppb "google.golang.org/protobuf/types/known/timestamppb"
@ -638,10 +639,10 @@ var File_item_v1_item_proto protoreflect.FileDescriptor
const file_item_v1_item_proto_rawDesc = "" +
"\n" +
"\x12item/v1/item.proto\x12\aitem.v1\x1a\x1fgoogle/protobuf/timestamp.proto\"\xb0\x01\n" +
"\x12item/v1/item.proto\x12\aitem.v1\x1a\x1bbuf/validate/validate.proto\x1a\x1fgoogle/protobuf/timestamp.proto\"\xb9\x01\n" +
"\x04Item\x12\x0e\n" +
"\x02id\x18\x01 \x01(\x03R\x02id\x12\x12\n" +
"\x04name\x18\x02 \x01(\tR\x04name\x120\n" +
"\x02id\x18\x01 \x01(\x03R\x02id\x12\x1b\n" +
"\x04name\x18\x02 \x01(\tB\a\xbaH\x04r\x02\x10\x03R\x04name\x120\n" +
"\x05added\x18\x03 \x01(\v2\x1a.google.protobuf.TimestampR\x05added\x12 \n" +
"\vdescription\x18\x04 \x01(\tR\vdescription\x12\x14\n" +
"\x05price\x18\x05 \x01(\x02R\x05price\x12\x1a\n" +

View File

@ -7,6 +7,7 @@
package userv1
import (
_ "buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go/buf/validate"
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
reflect "reflect"
@ -285,27 +286,222 @@ func (*LogoutResponse) Descriptor() ([]byte, []int) {
return file_user_v1_auth_proto_rawDescGZIP(), []int{5}
}
type BeginPasskeyLoginRequest struct {
state protoimpl.MessageState `protogen:"open.v1"`
Username string `protobuf:"bytes,1,opt,name=username,proto3" json:"username,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *BeginPasskeyLoginRequest) Reset() {
*x = BeginPasskeyLoginRequest{}
mi := &file_user_v1_auth_proto_msgTypes[6]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *BeginPasskeyLoginRequest) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*BeginPasskeyLoginRequest) ProtoMessage() {}
func (x *BeginPasskeyLoginRequest) ProtoReflect() protoreflect.Message {
mi := &file_user_v1_auth_proto_msgTypes[6]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use BeginPasskeyLoginRequest.ProtoReflect.Descriptor instead.
func (*BeginPasskeyLoginRequest) Descriptor() ([]byte, []int) {
return file_user_v1_auth_proto_rawDescGZIP(), []int{6}
}
func (x *BeginPasskeyLoginRequest) GetUsername() string {
if x != nil {
return x.Username
}
return ""
}
type BeginPasskeyLoginResponse struct {
state protoimpl.MessageState `protogen:"open.v1"`
OptionsJson string `protobuf:"bytes,1,opt,name=options_json,json=optionsJson,proto3" json:"options_json,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *BeginPasskeyLoginResponse) Reset() {
*x = BeginPasskeyLoginResponse{}
mi := &file_user_v1_auth_proto_msgTypes[7]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *BeginPasskeyLoginResponse) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*BeginPasskeyLoginResponse) ProtoMessage() {}
func (x *BeginPasskeyLoginResponse) ProtoReflect() protoreflect.Message {
mi := &file_user_v1_auth_proto_msgTypes[7]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use BeginPasskeyLoginResponse.ProtoReflect.Descriptor instead.
func (*BeginPasskeyLoginResponse) Descriptor() ([]byte, []int) {
return file_user_v1_auth_proto_rawDescGZIP(), []int{7}
}
func (x *BeginPasskeyLoginResponse) GetOptionsJson() string {
if x != nil {
return x.OptionsJson
}
return ""
}
type FinishPasskeyLoginRequest struct {
state protoimpl.MessageState `protogen:"open.v1"`
Username string `protobuf:"bytes,1,opt,name=username,proto3" json:"username,omitempty"`
Attestation string `protobuf:"bytes,2,opt,name=attestation,proto3" json:"attestation,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *FinishPasskeyLoginRequest) Reset() {
*x = FinishPasskeyLoginRequest{}
mi := &file_user_v1_auth_proto_msgTypes[8]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *FinishPasskeyLoginRequest) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*FinishPasskeyLoginRequest) ProtoMessage() {}
func (x *FinishPasskeyLoginRequest) ProtoReflect() protoreflect.Message {
mi := &file_user_v1_auth_proto_msgTypes[8]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use FinishPasskeyLoginRequest.ProtoReflect.Descriptor instead.
func (*FinishPasskeyLoginRequest) Descriptor() ([]byte, []int) {
return file_user_v1_auth_proto_rawDescGZIP(), []int{8}
}
func (x *FinishPasskeyLoginRequest) GetUsername() string {
if x != nil {
return x.Username
}
return ""
}
func (x *FinishPasskeyLoginRequest) GetAttestation() string {
if x != nil {
return x.Attestation
}
return ""
}
type FinishPasskeyLoginResponse struct {
state protoimpl.MessageState `protogen:"open.v1"`
Token string `protobuf:"bytes,1,opt,name=token,proto3" json:"token,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *FinishPasskeyLoginResponse) Reset() {
*x = FinishPasskeyLoginResponse{}
mi := &file_user_v1_auth_proto_msgTypes[9]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *FinishPasskeyLoginResponse) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*FinishPasskeyLoginResponse) ProtoMessage() {}
func (x *FinishPasskeyLoginResponse) ProtoReflect() protoreflect.Message {
mi := &file_user_v1_auth_proto_msgTypes[9]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use FinishPasskeyLoginResponse.ProtoReflect.Descriptor instead.
func (*FinishPasskeyLoginResponse) Descriptor() ([]byte, []int) {
return file_user_v1_auth_proto_rawDescGZIP(), []int{9}
}
func (x *FinishPasskeyLoginResponse) GetToken() string {
if x != nil {
return x.Token
}
return ""
}
var File_user_v1_auth_proto protoreflect.FileDescriptor
const file_user_v1_auth_proto_rawDesc = "" +
"\n" +
"\x12user/v1/auth.proto\x12\auser.v1\"F\n" +
"\fLoginRequest\x12\x1a\n" +
"\busername\x18\x01 \x01(\tR\busername\x12\x1a\n" +
"\x12user/v1/auth.proto\x12\auser.v1\x1a\x1bbuf/validate/validate.proto\"O\n" +
"\fLoginRequest\x12#\n" +
"\busername\x18\x01 \x01(\tB\a\xbaH\x04r\x02\x10\x03R\busername\x12\x1a\n" +
"\bpassword\x18\x02 \x01(\tR\bpassword\"%\n" +
"\rLoginResponse\x12\x14\n" +
"\x05token\x18\x01 \x01(\tR\x05token\"r\n" +
"\rSignUpRequest\x12\x1a\n" +
"\busername\x18\x01 \x01(\tR\busername\x12\x1a\n" +
"\x05token\x18\x01 \x01(\tR\x05token\"{\n" +
"\rSignUpRequest\x12#\n" +
"\busername\x18\x01 \x01(\tB\a\xbaH\x04r\x02\x10\x03R\busername\x12\x1a\n" +
"\bpassword\x18\x02 \x01(\tR\bpassword\x12)\n" +
"\x10confirm_password\x18\x03 \x01(\tR\x0fconfirmPassword\"\x10\n" +
"\x0eSignUpResponse\"\x0f\n" +
"\rLogoutRequest\"\x10\n" +
"\x0eLogoutResponse2\xc1\x01\n" +
"\x0eLogoutResponse\"6\n" +
"\x18BeginPasskeyLoginRequest\x12\x1a\n" +
"\busername\x18\x01 \x01(\tR\busername\">\n" +
"\x19BeginPasskeyLoginResponse\x12!\n" +
"\foptions_json\x18\x01 \x01(\tR\voptionsJson\"Y\n" +
"\x19FinishPasskeyLoginRequest\x12\x1a\n" +
"\busername\x18\x01 \x01(\tR\busername\x12 \n" +
"\vattestation\x18\x02 \x01(\tR\vattestation\"2\n" +
"\x1aFinishPasskeyLoginResponse\x12\x14\n" +
"\x05token\x18\x01 \x01(\tR\x05token2\x80\x03\n" +
"\vAuthService\x128\n" +
"\x05Login\x12\x15.user.v1.LoginRequest\x1a\x16.user.v1.LoginResponse\"\x00\x12;\n" +
"\x06SignUp\x12\x16.user.v1.SignUpRequest\x1a\x17.user.v1.SignUpResponse\"\x00\x12;\n" +
"\x06Logout\x12\x16.user.v1.LogoutRequest\x1a\x17.user.v1.LogoutResponse\"\x00B\x9c\x01\n" +
"\x06Logout\x12\x16.user.v1.LogoutRequest\x1a\x17.user.v1.LogoutResponse\"\x00\x12\\\n" +
"\x11BeginPasskeyLogin\x12!.user.v1.BeginPasskeyLoginRequest\x1a\".user.v1.BeginPasskeyLoginResponse\"\x00\x12_\n" +
"\x12FinishPasskeyLogin\x12\".user.v1.FinishPasskeyLoginRequest\x1a#.user.v1.FinishPasskeyLoginResponse\"\x00B\x9c\x01\n" +
"\vcom.user.v1B\tAuthProtoP\x01ZEgithub.com/spotdemo4/trevstack/server/internal/connect/user/v1;userv1\xa2\x02\x03UXX\xaa\x02\aUser.V1\xca\x02\aUser\\V1\xe2\x02\x13User\\V1\\GPBMetadata\xea\x02\bUser::V1b\x06proto3"
var (
@ -320,24 +516,32 @@ func file_user_v1_auth_proto_rawDescGZIP() []byte {
return file_user_v1_auth_proto_rawDescData
}
var file_user_v1_auth_proto_msgTypes = make([]protoimpl.MessageInfo, 6)
var file_user_v1_auth_proto_msgTypes = make([]protoimpl.MessageInfo, 10)
var file_user_v1_auth_proto_goTypes = []any{
(*LoginRequest)(nil), // 0: user.v1.LoginRequest
(*LoginResponse)(nil), // 1: user.v1.LoginResponse
(*SignUpRequest)(nil), // 2: user.v1.SignUpRequest
(*SignUpResponse)(nil), // 3: user.v1.SignUpResponse
(*LogoutRequest)(nil), // 4: user.v1.LogoutRequest
(*LogoutResponse)(nil), // 5: user.v1.LogoutResponse
(*LoginRequest)(nil), // 0: user.v1.LoginRequest
(*LoginResponse)(nil), // 1: user.v1.LoginResponse
(*SignUpRequest)(nil), // 2: user.v1.SignUpRequest
(*SignUpResponse)(nil), // 3: user.v1.SignUpResponse
(*LogoutRequest)(nil), // 4: user.v1.LogoutRequest
(*LogoutResponse)(nil), // 5: user.v1.LogoutResponse
(*BeginPasskeyLoginRequest)(nil), // 6: user.v1.BeginPasskeyLoginRequest
(*BeginPasskeyLoginResponse)(nil), // 7: user.v1.BeginPasskeyLoginResponse
(*FinishPasskeyLoginRequest)(nil), // 8: user.v1.FinishPasskeyLoginRequest
(*FinishPasskeyLoginResponse)(nil), // 9: user.v1.FinishPasskeyLoginResponse
}
var file_user_v1_auth_proto_depIdxs = []int32{
0, // 0: user.v1.AuthService.Login:input_type -> user.v1.LoginRequest
2, // 1: user.v1.AuthService.SignUp:input_type -> user.v1.SignUpRequest
4, // 2: user.v1.AuthService.Logout:input_type -> user.v1.LogoutRequest
1, // 3: user.v1.AuthService.Login:output_type -> user.v1.LoginResponse
3, // 4: user.v1.AuthService.SignUp:output_type -> user.v1.SignUpResponse
5, // 5: user.v1.AuthService.Logout:output_type -> user.v1.LogoutResponse
3, // [3:6] is the sub-list for method output_type
0, // [0:3] is the sub-list for method input_type
6, // 3: user.v1.AuthService.BeginPasskeyLogin:input_type -> user.v1.BeginPasskeyLoginRequest
8, // 4: user.v1.AuthService.FinishPasskeyLogin:input_type -> user.v1.FinishPasskeyLoginRequest
1, // 5: user.v1.AuthService.Login:output_type -> user.v1.LoginResponse
3, // 6: user.v1.AuthService.SignUp:output_type -> user.v1.SignUpResponse
5, // 7: user.v1.AuthService.Logout:output_type -> user.v1.LogoutResponse
7, // 8: user.v1.AuthService.BeginPasskeyLogin:output_type -> user.v1.BeginPasskeyLoginResponse
9, // 9: user.v1.AuthService.FinishPasskeyLogin:output_type -> user.v1.FinishPasskeyLoginResponse
5, // [5:10] is the sub-list for method output_type
0, // [0:5] is the sub-list for method input_type
0, // [0:0] is the sub-list for extension type_name
0, // [0:0] is the sub-list for extension extendee
0, // [0:0] is the sub-list for field type_name
@ -354,7 +558,7 @@ func file_user_v1_auth_proto_init() {
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: unsafe.Slice(unsafe.StringData(file_user_v1_auth_proto_rawDesc), len(file_user_v1_auth_proto_rawDesc)),
NumEnums: 0,
NumMessages: 6,
NumMessages: 10,
NumExtensions: 0,
NumServices: 1,
},

View File

@ -457,6 +457,166 @@ func (x *UpdateProfilePictureResponse) GetUser() *User {
return nil
}
type BeginPasskeyRegistrationRequest struct {
state protoimpl.MessageState `protogen:"open.v1"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *BeginPasskeyRegistrationRequest) Reset() {
*x = BeginPasskeyRegistrationRequest{}
mi := &file_user_v1_user_proto_msgTypes[9]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *BeginPasskeyRegistrationRequest) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*BeginPasskeyRegistrationRequest) ProtoMessage() {}
func (x *BeginPasskeyRegistrationRequest) ProtoReflect() protoreflect.Message {
mi := &file_user_v1_user_proto_msgTypes[9]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use BeginPasskeyRegistrationRequest.ProtoReflect.Descriptor instead.
func (*BeginPasskeyRegistrationRequest) Descriptor() ([]byte, []int) {
return file_user_v1_user_proto_rawDescGZIP(), []int{9}
}
type BeginPasskeyRegistrationResponse struct {
state protoimpl.MessageState `protogen:"open.v1"`
OptionsJson string `protobuf:"bytes,1,opt,name=options_json,json=optionsJson,proto3" json:"options_json,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *BeginPasskeyRegistrationResponse) Reset() {
*x = BeginPasskeyRegistrationResponse{}
mi := &file_user_v1_user_proto_msgTypes[10]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *BeginPasskeyRegistrationResponse) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*BeginPasskeyRegistrationResponse) ProtoMessage() {}
func (x *BeginPasskeyRegistrationResponse) ProtoReflect() protoreflect.Message {
mi := &file_user_v1_user_proto_msgTypes[10]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use BeginPasskeyRegistrationResponse.ProtoReflect.Descriptor instead.
func (*BeginPasskeyRegistrationResponse) Descriptor() ([]byte, []int) {
return file_user_v1_user_proto_rawDescGZIP(), []int{10}
}
func (x *BeginPasskeyRegistrationResponse) GetOptionsJson() string {
if x != nil {
return x.OptionsJson
}
return ""
}
type FinishPasskeyRegistrationRequest struct {
state protoimpl.MessageState `protogen:"open.v1"`
Attestation string `protobuf:"bytes,1,opt,name=attestation,proto3" json:"attestation,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *FinishPasskeyRegistrationRequest) Reset() {
*x = FinishPasskeyRegistrationRequest{}
mi := &file_user_v1_user_proto_msgTypes[11]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *FinishPasskeyRegistrationRequest) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*FinishPasskeyRegistrationRequest) ProtoMessage() {}
func (x *FinishPasskeyRegistrationRequest) ProtoReflect() protoreflect.Message {
mi := &file_user_v1_user_proto_msgTypes[11]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use FinishPasskeyRegistrationRequest.ProtoReflect.Descriptor instead.
func (*FinishPasskeyRegistrationRequest) Descriptor() ([]byte, []int) {
return file_user_v1_user_proto_rawDescGZIP(), []int{11}
}
func (x *FinishPasskeyRegistrationRequest) GetAttestation() string {
if x != nil {
return x.Attestation
}
return ""
}
type FinishPasskeyRegistrationResponse struct {
state protoimpl.MessageState `protogen:"open.v1"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *FinishPasskeyRegistrationResponse) Reset() {
*x = FinishPasskeyRegistrationResponse{}
mi := &file_user_v1_user_proto_msgTypes[12]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *FinishPasskeyRegistrationResponse) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*FinishPasskeyRegistrationResponse) ProtoMessage() {}
func (x *FinishPasskeyRegistrationResponse) ProtoReflect() protoreflect.Message {
mi := &file_user_v1_user_proto_msgTypes[12]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use FinishPasskeyRegistrationResponse.ProtoReflect.Descriptor instead.
func (*FinishPasskeyRegistrationResponse) Descriptor() ([]byte, []int) {
return file_user_v1_user_proto_rawDescGZIP(), []int{12}
}
var File_user_v1_user_proto protoreflect.FileDescriptor
const file_user_v1_user_proto_rawDesc = "" +
@ -485,12 +645,20 @@ const file_user_v1_user_proto_rawDesc = "" +
"\tfile_name\x18\x01 \x01(\tR\bfileName\x12\x12\n" +
"\x04data\x18\x02 \x01(\fR\x04data\"A\n" +
"\x1cUpdateProfilePictureResponse\x12!\n" +
"\x04user\x18\x01 \x01(\v2\r.user.v1.UserR\x04user2\xcf\x02\n" +
"\x04user\x18\x01 \x01(\v2\r.user.v1.UserR\x04user\"!\n" +
"\x1fBeginPasskeyRegistrationRequest\"E\n" +
" BeginPasskeyRegistrationResponse\x12!\n" +
"\foptions_json\x18\x01 \x01(\tR\voptionsJson\"D\n" +
" FinishPasskeyRegistrationRequest\x12 \n" +
"\vattestation\x18\x01 \x01(\tR\vattestation\"#\n" +
"!FinishPasskeyRegistrationResponse2\xb8\x04\n" +
"\vUserService\x12>\n" +
"\aGetUser\x12\x17.user.v1.GetUserRequest\x1a\x18.user.v1.GetUserResponse\"\x00\x12S\n" +
"\x0eUpdatePassword\x12\x1e.user.v1.UpdatePasswordRequest\x1a\x1f.user.v1.UpdatePasswordResponse\"\x00\x12D\n" +
"\tGetAPIKey\x12\x19.user.v1.GetAPIKeyRequest\x1a\x1a.user.v1.GetAPIKeyResponse\"\x00\x12e\n" +
"\x14UpdateProfilePicture\x12$.user.v1.UpdateProfilePictureRequest\x1a%.user.v1.UpdateProfilePictureResponse\"\x00B\x9c\x01\n" +
"\x14UpdateProfilePicture\x12$.user.v1.UpdateProfilePictureRequest\x1a%.user.v1.UpdateProfilePictureResponse\"\x00\x12q\n" +
"\x18BeginPasskeyRegistration\x12(.user.v1.BeginPasskeyRegistrationRequest\x1a).user.v1.BeginPasskeyRegistrationResponse\"\x00\x12t\n" +
"\x19FinishPasskeyRegistration\x12).user.v1.FinishPasskeyRegistrationRequest\x1a*.user.v1.FinishPasskeyRegistrationResponse\"\x00B\x9c\x01\n" +
"\vcom.user.v1B\tUserProtoP\x01ZEgithub.com/spotdemo4/trevstack/server/internal/connect/user/v1;userv1\xa2\x02\x03UXX\xaa\x02\aUser.V1\xca\x02\aUser\\V1\xe2\x02\x13User\\V1\\GPBMetadata\xea\x02\bUser::V1b\x06proto3"
var (
@ -505,35 +673,43 @@ func file_user_v1_user_proto_rawDescGZIP() []byte {
return file_user_v1_user_proto_rawDescData
}
var file_user_v1_user_proto_msgTypes = make([]protoimpl.MessageInfo, 9)
var file_user_v1_user_proto_msgTypes = make([]protoimpl.MessageInfo, 13)
var file_user_v1_user_proto_goTypes = []any{
(*User)(nil), // 0: user.v1.User
(*GetUserRequest)(nil), // 1: user.v1.GetUserRequest
(*GetUserResponse)(nil), // 2: user.v1.GetUserResponse
(*UpdatePasswordRequest)(nil), // 3: user.v1.UpdatePasswordRequest
(*UpdatePasswordResponse)(nil), // 4: user.v1.UpdatePasswordResponse
(*GetAPIKeyRequest)(nil), // 5: user.v1.GetAPIKeyRequest
(*GetAPIKeyResponse)(nil), // 6: user.v1.GetAPIKeyResponse
(*UpdateProfilePictureRequest)(nil), // 7: user.v1.UpdateProfilePictureRequest
(*UpdateProfilePictureResponse)(nil), // 8: user.v1.UpdateProfilePictureResponse
(*User)(nil), // 0: user.v1.User
(*GetUserRequest)(nil), // 1: user.v1.GetUserRequest
(*GetUserResponse)(nil), // 2: user.v1.GetUserResponse
(*UpdatePasswordRequest)(nil), // 3: user.v1.UpdatePasswordRequest
(*UpdatePasswordResponse)(nil), // 4: user.v1.UpdatePasswordResponse
(*GetAPIKeyRequest)(nil), // 5: user.v1.GetAPIKeyRequest
(*GetAPIKeyResponse)(nil), // 6: user.v1.GetAPIKeyResponse
(*UpdateProfilePictureRequest)(nil), // 7: user.v1.UpdateProfilePictureRequest
(*UpdateProfilePictureResponse)(nil), // 8: user.v1.UpdateProfilePictureResponse
(*BeginPasskeyRegistrationRequest)(nil), // 9: user.v1.BeginPasskeyRegistrationRequest
(*BeginPasskeyRegistrationResponse)(nil), // 10: user.v1.BeginPasskeyRegistrationResponse
(*FinishPasskeyRegistrationRequest)(nil), // 11: user.v1.FinishPasskeyRegistrationRequest
(*FinishPasskeyRegistrationResponse)(nil), // 12: user.v1.FinishPasskeyRegistrationResponse
}
var file_user_v1_user_proto_depIdxs = []int32{
0, // 0: user.v1.GetUserResponse.user:type_name -> user.v1.User
0, // 1: user.v1.UpdatePasswordResponse.user:type_name -> user.v1.User
0, // 2: user.v1.UpdateProfilePictureResponse.user:type_name -> user.v1.User
1, // 3: user.v1.UserService.GetUser:input_type -> user.v1.GetUserRequest
3, // 4: user.v1.UserService.UpdatePassword:input_type -> user.v1.UpdatePasswordRequest
5, // 5: user.v1.UserService.GetAPIKey:input_type -> user.v1.GetAPIKeyRequest
7, // 6: user.v1.UserService.UpdateProfilePicture:input_type -> user.v1.UpdateProfilePictureRequest
2, // 7: user.v1.UserService.GetUser:output_type -> user.v1.GetUserResponse
4, // 8: user.v1.UserService.UpdatePassword:output_type -> user.v1.UpdatePasswordResponse
6, // 9: user.v1.UserService.GetAPIKey:output_type -> user.v1.GetAPIKeyResponse
8, // 10: user.v1.UserService.UpdateProfilePicture:output_type -> user.v1.UpdateProfilePictureResponse
7, // [7:11] is the sub-list for method output_type
3, // [3:7] is the sub-list for method input_type
3, // [3:3] is the sub-list for extension type_name
3, // [3:3] is the sub-list for extension extendee
0, // [0:3] is the sub-list for field type_name
0, // 0: user.v1.GetUserResponse.user:type_name -> user.v1.User
0, // 1: user.v1.UpdatePasswordResponse.user:type_name -> user.v1.User
0, // 2: user.v1.UpdateProfilePictureResponse.user:type_name -> user.v1.User
1, // 3: user.v1.UserService.GetUser:input_type -> user.v1.GetUserRequest
3, // 4: user.v1.UserService.UpdatePassword:input_type -> user.v1.UpdatePasswordRequest
5, // 5: user.v1.UserService.GetAPIKey:input_type -> user.v1.GetAPIKeyRequest
7, // 6: user.v1.UserService.UpdateProfilePicture:input_type -> user.v1.UpdateProfilePictureRequest
9, // 7: user.v1.UserService.BeginPasskeyRegistration:input_type -> user.v1.BeginPasskeyRegistrationRequest
11, // 8: user.v1.UserService.FinishPasskeyRegistration:input_type -> user.v1.FinishPasskeyRegistrationRequest
2, // 9: user.v1.UserService.GetUser:output_type -> user.v1.GetUserResponse
4, // 10: user.v1.UserService.UpdatePassword:output_type -> user.v1.UpdatePasswordResponse
6, // 11: user.v1.UserService.GetAPIKey:output_type -> user.v1.GetAPIKeyResponse
8, // 12: user.v1.UserService.UpdateProfilePicture:output_type -> user.v1.UpdateProfilePictureResponse
10, // 13: user.v1.UserService.BeginPasskeyRegistration:output_type -> user.v1.BeginPasskeyRegistrationResponse
12, // 14: user.v1.UserService.FinishPasskeyRegistration:output_type -> user.v1.FinishPasskeyRegistrationResponse
9, // [9:15] is the sub-list for method output_type
3, // [3:9] is the sub-list for method input_type
3, // [3:3] is the sub-list for extension type_name
3, // [3:3] is the sub-list for extension extendee
0, // [0:3] is the sub-list for field type_name
}
func init() { file_user_v1_user_proto_init() }
@ -548,7 +724,7 @@ func file_user_v1_user_proto_init() {
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: unsafe.Slice(unsafe.StringData(file_user_v1_user_proto_rawDesc), len(file_user_v1_user_proto_rawDesc)),
NumEnums: 0,
NumMessages: 9,
NumMessages: 13,
NumExtensions: 0,
NumServices: 1,
},

View File

@ -39,6 +39,12 @@ const (
AuthServiceSignUpProcedure = "/user.v1.AuthService/SignUp"
// AuthServiceLogoutProcedure is the fully-qualified name of the AuthService's Logout RPC.
AuthServiceLogoutProcedure = "/user.v1.AuthService/Logout"
// AuthServiceBeginPasskeyLoginProcedure is the fully-qualified name of the AuthService's
// BeginPasskeyLogin RPC.
AuthServiceBeginPasskeyLoginProcedure = "/user.v1.AuthService/BeginPasskeyLogin"
// AuthServiceFinishPasskeyLoginProcedure is the fully-qualified name of the AuthService's
// FinishPasskeyLogin RPC.
AuthServiceFinishPasskeyLoginProcedure = "/user.v1.AuthService/FinishPasskeyLogin"
)
// AuthServiceClient is a client for the user.v1.AuthService service.
@ -46,6 +52,8 @@ type AuthServiceClient interface {
Login(context.Context, *connect.Request[v1.LoginRequest]) (*connect.Response[v1.LoginResponse], error)
SignUp(context.Context, *connect.Request[v1.SignUpRequest]) (*connect.Response[v1.SignUpResponse], error)
Logout(context.Context, *connect.Request[v1.LogoutRequest]) (*connect.Response[v1.LogoutResponse], error)
BeginPasskeyLogin(context.Context, *connect.Request[v1.BeginPasskeyLoginRequest]) (*connect.Response[v1.BeginPasskeyLoginResponse], error)
FinishPasskeyLogin(context.Context, *connect.Request[v1.FinishPasskeyLoginRequest]) (*connect.Response[v1.FinishPasskeyLoginResponse], error)
}
// NewAuthServiceClient constructs a client for the user.v1.AuthService service. By default, it uses
@ -77,14 +85,28 @@ func NewAuthServiceClient(httpClient connect.HTTPClient, baseURL string, opts ..
connect.WithSchema(authServiceMethods.ByName("Logout")),
connect.WithClientOptions(opts...),
),
beginPasskeyLogin: connect.NewClient[v1.BeginPasskeyLoginRequest, v1.BeginPasskeyLoginResponse](
httpClient,
baseURL+AuthServiceBeginPasskeyLoginProcedure,
connect.WithSchema(authServiceMethods.ByName("BeginPasskeyLogin")),
connect.WithClientOptions(opts...),
),
finishPasskeyLogin: connect.NewClient[v1.FinishPasskeyLoginRequest, v1.FinishPasskeyLoginResponse](
httpClient,
baseURL+AuthServiceFinishPasskeyLoginProcedure,
connect.WithSchema(authServiceMethods.ByName("FinishPasskeyLogin")),
connect.WithClientOptions(opts...),
),
}
}
// authServiceClient implements AuthServiceClient.
type authServiceClient struct {
login *connect.Client[v1.LoginRequest, v1.LoginResponse]
signUp *connect.Client[v1.SignUpRequest, v1.SignUpResponse]
logout *connect.Client[v1.LogoutRequest, v1.LogoutResponse]
login *connect.Client[v1.LoginRequest, v1.LoginResponse]
signUp *connect.Client[v1.SignUpRequest, v1.SignUpResponse]
logout *connect.Client[v1.LogoutRequest, v1.LogoutResponse]
beginPasskeyLogin *connect.Client[v1.BeginPasskeyLoginRequest, v1.BeginPasskeyLoginResponse]
finishPasskeyLogin *connect.Client[v1.FinishPasskeyLoginRequest, v1.FinishPasskeyLoginResponse]
}
// Login calls user.v1.AuthService.Login.
@ -102,11 +124,23 @@ func (c *authServiceClient) Logout(ctx context.Context, req *connect.Request[v1.
return c.logout.CallUnary(ctx, req)
}
// BeginPasskeyLogin calls user.v1.AuthService.BeginPasskeyLogin.
func (c *authServiceClient) BeginPasskeyLogin(ctx context.Context, req *connect.Request[v1.BeginPasskeyLoginRequest]) (*connect.Response[v1.BeginPasskeyLoginResponse], error) {
return c.beginPasskeyLogin.CallUnary(ctx, req)
}
// FinishPasskeyLogin calls user.v1.AuthService.FinishPasskeyLogin.
func (c *authServiceClient) FinishPasskeyLogin(ctx context.Context, req *connect.Request[v1.FinishPasskeyLoginRequest]) (*connect.Response[v1.FinishPasskeyLoginResponse], error) {
return c.finishPasskeyLogin.CallUnary(ctx, req)
}
// AuthServiceHandler is an implementation of the user.v1.AuthService service.
type AuthServiceHandler interface {
Login(context.Context, *connect.Request[v1.LoginRequest]) (*connect.Response[v1.LoginResponse], error)
SignUp(context.Context, *connect.Request[v1.SignUpRequest]) (*connect.Response[v1.SignUpResponse], error)
Logout(context.Context, *connect.Request[v1.LogoutRequest]) (*connect.Response[v1.LogoutResponse], error)
BeginPasskeyLogin(context.Context, *connect.Request[v1.BeginPasskeyLoginRequest]) (*connect.Response[v1.BeginPasskeyLoginResponse], error)
FinishPasskeyLogin(context.Context, *connect.Request[v1.FinishPasskeyLoginRequest]) (*connect.Response[v1.FinishPasskeyLoginResponse], error)
}
// NewAuthServiceHandler builds an HTTP handler from the service implementation. It returns the path
@ -134,6 +168,18 @@ func NewAuthServiceHandler(svc AuthServiceHandler, opts ...connect.HandlerOption
connect.WithSchema(authServiceMethods.ByName("Logout")),
connect.WithHandlerOptions(opts...),
)
authServiceBeginPasskeyLoginHandler := connect.NewUnaryHandler(
AuthServiceBeginPasskeyLoginProcedure,
svc.BeginPasskeyLogin,
connect.WithSchema(authServiceMethods.ByName("BeginPasskeyLogin")),
connect.WithHandlerOptions(opts...),
)
authServiceFinishPasskeyLoginHandler := connect.NewUnaryHandler(
AuthServiceFinishPasskeyLoginProcedure,
svc.FinishPasskeyLogin,
connect.WithSchema(authServiceMethods.ByName("FinishPasskeyLogin")),
connect.WithHandlerOptions(opts...),
)
return "/user.v1.AuthService/", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
switch r.URL.Path {
case AuthServiceLoginProcedure:
@ -142,6 +188,10 @@ func NewAuthServiceHandler(svc AuthServiceHandler, opts ...connect.HandlerOption
authServiceSignUpHandler.ServeHTTP(w, r)
case AuthServiceLogoutProcedure:
authServiceLogoutHandler.ServeHTTP(w, r)
case AuthServiceBeginPasskeyLoginProcedure:
authServiceBeginPasskeyLoginHandler.ServeHTTP(w, r)
case AuthServiceFinishPasskeyLoginProcedure:
authServiceFinishPasskeyLoginHandler.ServeHTTP(w, r)
default:
http.NotFound(w, r)
}
@ -162,3 +212,11 @@ func (UnimplementedAuthServiceHandler) SignUp(context.Context, *connect.Request[
func (UnimplementedAuthServiceHandler) Logout(context.Context, *connect.Request[v1.LogoutRequest]) (*connect.Response[v1.LogoutResponse], error) {
return nil, connect.NewError(connect.CodeUnimplemented, errors.New("user.v1.AuthService.Logout is not implemented"))
}
func (UnimplementedAuthServiceHandler) BeginPasskeyLogin(context.Context, *connect.Request[v1.BeginPasskeyLoginRequest]) (*connect.Response[v1.BeginPasskeyLoginResponse], error) {
return nil, connect.NewError(connect.CodeUnimplemented, errors.New("user.v1.AuthService.BeginPasskeyLogin is not implemented"))
}
func (UnimplementedAuthServiceHandler) FinishPasskeyLogin(context.Context, *connect.Request[v1.FinishPasskeyLoginRequest]) (*connect.Response[v1.FinishPasskeyLoginResponse], error) {
return nil, connect.NewError(connect.CodeUnimplemented, errors.New("user.v1.AuthService.FinishPasskeyLogin is not implemented"))
}

View File

@ -43,6 +43,12 @@ const (
// UserServiceUpdateProfilePictureProcedure is the fully-qualified name of the UserService's
// UpdateProfilePicture RPC.
UserServiceUpdateProfilePictureProcedure = "/user.v1.UserService/UpdateProfilePicture"
// UserServiceBeginPasskeyRegistrationProcedure is the fully-qualified name of the UserService's
// BeginPasskeyRegistration RPC.
UserServiceBeginPasskeyRegistrationProcedure = "/user.v1.UserService/BeginPasskeyRegistration"
// UserServiceFinishPasskeyRegistrationProcedure is the fully-qualified name of the UserService's
// FinishPasskeyRegistration RPC.
UserServiceFinishPasskeyRegistrationProcedure = "/user.v1.UserService/FinishPasskeyRegistration"
)
// UserServiceClient is a client for the user.v1.UserService service.
@ -51,6 +57,8 @@ type UserServiceClient interface {
UpdatePassword(context.Context, *connect.Request[v1.UpdatePasswordRequest]) (*connect.Response[v1.UpdatePasswordResponse], error)
GetAPIKey(context.Context, *connect.Request[v1.GetAPIKeyRequest]) (*connect.Response[v1.GetAPIKeyResponse], error)
UpdateProfilePicture(context.Context, *connect.Request[v1.UpdateProfilePictureRequest]) (*connect.Response[v1.UpdateProfilePictureResponse], error)
BeginPasskeyRegistration(context.Context, *connect.Request[v1.BeginPasskeyRegistrationRequest]) (*connect.Response[v1.BeginPasskeyRegistrationResponse], error)
FinishPasskeyRegistration(context.Context, *connect.Request[v1.FinishPasskeyRegistrationRequest]) (*connect.Response[v1.FinishPasskeyRegistrationResponse], error)
}
// NewUserServiceClient constructs a client for the user.v1.UserService service. By default, it uses
@ -88,15 +96,29 @@ func NewUserServiceClient(httpClient connect.HTTPClient, baseURL string, opts ..
connect.WithSchema(userServiceMethods.ByName("UpdateProfilePicture")),
connect.WithClientOptions(opts...),
),
beginPasskeyRegistration: connect.NewClient[v1.BeginPasskeyRegistrationRequest, v1.BeginPasskeyRegistrationResponse](
httpClient,
baseURL+UserServiceBeginPasskeyRegistrationProcedure,
connect.WithSchema(userServiceMethods.ByName("BeginPasskeyRegistration")),
connect.WithClientOptions(opts...),
),
finishPasskeyRegistration: connect.NewClient[v1.FinishPasskeyRegistrationRequest, v1.FinishPasskeyRegistrationResponse](
httpClient,
baseURL+UserServiceFinishPasskeyRegistrationProcedure,
connect.WithSchema(userServiceMethods.ByName("FinishPasskeyRegistration")),
connect.WithClientOptions(opts...),
),
}
}
// userServiceClient implements UserServiceClient.
type userServiceClient struct {
getUser *connect.Client[v1.GetUserRequest, v1.GetUserResponse]
updatePassword *connect.Client[v1.UpdatePasswordRequest, v1.UpdatePasswordResponse]
getAPIKey *connect.Client[v1.GetAPIKeyRequest, v1.GetAPIKeyResponse]
updateProfilePicture *connect.Client[v1.UpdateProfilePictureRequest, v1.UpdateProfilePictureResponse]
getUser *connect.Client[v1.GetUserRequest, v1.GetUserResponse]
updatePassword *connect.Client[v1.UpdatePasswordRequest, v1.UpdatePasswordResponse]
getAPIKey *connect.Client[v1.GetAPIKeyRequest, v1.GetAPIKeyResponse]
updateProfilePicture *connect.Client[v1.UpdateProfilePictureRequest, v1.UpdateProfilePictureResponse]
beginPasskeyRegistration *connect.Client[v1.BeginPasskeyRegistrationRequest, v1.BeginPasskeyRegistrationResponse]
finishPasskeyRegistration *connect.Client[v1.FinishPasskeyRegistrationRequest, v1.FinishPasskeyRegistrationResponse]
}
// GetUser calls user.v1.UserService.GetUser.
@ -119,12 +141,24 @@ func (c *userServiceClient) UpdateProfilePicture(ctx context.Context, req *conne
return c.updateProfilePicture.CallUnary(ctx, req)
}
// BeginPasskeyRegistration calls user.v1.UserService.BeginPasskeyRegistration.
func (c *userServiceClient) BeginPasskeyRegistration(ctx context.Context, req *connect.Request[v1.BeginPasskeyRegistrationRequest]) (*connect.Response[v1.BeginPasskeyRegistrationResponse], error) {
return c.beginPasskeyRegistration.CallUnary(ctx, req)
}
// FinishPasskeyRegistration calls user.v1.UserService.FinishPasskeyRegistration.
func (c *userServiceClient) FinishPasskeyRegistration(ctx context.Context, req *connect.Request[v1.FinishPasskeyRegistrationRequest]) (*connect.Response[v1.FinishPasskeyRegistrationResponse], error) {
return c.finishPasskeyRegistration.CallUnary(ctx, req)
}
// UserServiceHandler is an implementation of the user.v1.UserService service.
type UserServiceHandler interface {
GetUser(context.Context, *connect.Request[v1.GetUserRequest]) (*connect.Response[v1.GetUserResponse], error)
UpdatePassword(context.Context, *connect.Request[v1.UpdatePasswordRequest]) (*connect.Response[v1.UpdatePasswordResponse], error)
GetAPIKey(context.Context, *connect.Request[v1.GetAPIKeyRequest]) (*connect.Response[v1.GetAPIKeyResponse], error)
UpdateProfilePicture(context.Context, *connect.Request[v1.UpdateProfilePictureRequest]) (*connect.Response[v1.UpdateProfilePictureResponse], error)
BeginPasskeyRegistration(context.Context, *connect.Request[v1.BeginPasskeyRegistrationRequest]) (*connect.Response[v1.BeginPasskeyRegistrationResponse], error)
FinishPasskeyRegistration(context.Context, *connect.Request[v1.FinishPasskeyRegistrationRequest]) (*connect.Response[v1.FinishPasskeyRegistrationResponse], error)
}
// NewUserServiceHandler builds an HTTP handler from the service implementation. It returns the path
@ -158,6 +192,18 @@ func NewUserServiceHandler(svc UserServiceHandler, opts ...connect.HandlerOption
connect.WithSchema(userServiceMethods.ByName("UpdateProfilePicture")),
connect.WithHandlerOptions(opts...),
)
userServiceBeginPasskeyRegistrationHandler := connect.NewUnaryHandler(
UserServiceBeginPasskeyRegistrationProcedure,
svc.BeginPasskeyRegistration,
connect.WithSchema(userServiceMethods.ByName("BeginPasskeyRegistration")),
connect.WithHandlerOptions(opts...),
)
userServiceFinishPasskeyRegistrationHandler := connect.NewUnaryHandler(
UserServiceFinishPasskeyRegistrationProcedure,
svc.FinishPasskeyRegistration,
connect.WithSchema(userServiceMethods.ByName("FinishPasskeyRegistration")),
connect.WithHandlerOptions(opts...),
)
return "/user.v1.UserService/", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
switch r.URL.Path {
case UserServiceGetUserProcedure:
@ -168,6 +214,10 @@ func NewUserServiceHandler(svc UserServiceHandler, opts ...connect.HandlerOption
userServiceGetAPIKeyHandler.ServeHTTP(w, r)
case UserServiceUpdateProfilePictureProcedure:
userServiceUpdateProfilePictureHandler.ServeHTTP(w, r)
case UserServiceBeginPasskeyRegistrationProcedure:
userServiceBeginPasskeyRegistrationHandler.ServeHTTP(w, r)
case UserServiceFinishPasskeyRegistrationProcedure:
userServiceFinishPasskeyRegistrationHandler.ServeHTTP(w, r)
default:
http.NotFound(w, r)
}
@ -192,3 +242,11 @@ func (UnimplementedUserServiceHandler) GetAPIKey(context.Context, *connect.Reque
func (UnimplementedUserServiceHandler) UpdateProfilePicture(context.Context, *connect.Request[v1.UpdateProfilePictureRequest]) (*connect.Response[v1.UpdateProfilePictureResponse], error) {
return nil, connect.NewError(connect.CodeUnimplemented, errors.New("user.v1.UserService.UpdateProfilePicture is not implemented"))
}
func (UnimplementedUserServiceHandler) BeginPasskeyRegistration(context.Context, *connect.Request[v1.BeginPasskeyRegistrationRequest]) (*connect.Response[v1.BeginPasskeyRegistrationResponse], error) {
return nil, connect.NewError(connect.CodeUnimplemented, errors.New("user.v1.UserService.BeginPasskeyRegistration is not implemented"))
}
func (UnimplementedUserServiceHandler) FinishPasskeyRegistration(context.Context, *connect.Request[v1.FinishPasskeyRegistrationRequest]) (*connect.Response[v1.FinishPasskeyRegistrationResponse], error) {
return nil, connect.NewError(connect.CodeUnimplemented, errors.New("user.v1.UserService.FinishPasskeyRegistration is not implemented"))
}

View File

@ -2,7 +2,6 @@ package database
import (
"embed"
"io"
"log"
"net/url"
@ -11,8 +10,8 @@ import (
)
func Migrate(dsn string, dbFS embed.FS) error {
_, err := dbFS.ReadDir(".")
if err == io.EOF {
entries, err := dbFS.ReadDir(".")
if err != nil || len(entries) == 0 {
return nil
}
@ -35,7 +34,7 @@ func Migrate(dsn string, dbFS embed.FS) error {
log.Println(m.Version, m.FilePath)
}
log.Println("\nApplying...")
log.Println("Applying...")
err = db.CreateAndMigrate()
if err != nil {
return err

View File

@ -2,7 +2,6 @@ package client
import (
"embed"
"io"
"io/fs"
"net/http"
@ -10,8 +9,8 @@ import (
)
func NewClientHandler(key string, clientFS embed.FS) http.Handler {
_, err := clientFS.ReadDir(".")
if err == io.EOF {
entries, err := clientFS.ReadDir(".")
if err != nil || len(entries) == 0 {
return http.NotFoundHandler()
}

View File

@ -8,6 +8,7 @@ import (
"time"
"connectrpc.com/connect"
"connectrpc.com/validate"
itemv1 "github.com/spotdemo4/trevstack/server/internal/connect/item/v1"
"github.com/spotdemo4/trevstack/server/internal/connect/item/v1/itemv1connect"
"github.com/spotdemo4/trevstack/server/internal/interceptors"
@ -190,8 +191,8 @@ func (h *Handler) DeleteItem(ctx context.Context, req *connect.Request[itemv1.De
return res, nil
}
func NewHandler(db *sqlc.Queries, key string) (string, http.Handler) {
interceptors := connect.WithInterceptors(interceptors.NewAuthInterceptor(key))
func NewHandler(vi *validate.Interceptor, db *sqlc.Queries, key string) (string, http.Handler) {
interceptors := connect.WithInterceptors(vi, interceptors.NewAuthInterceptor(key))
return itemv1connect.NewItemServiceHandler(
&Handler{

View File

@ -3,15 +3,20 @@ package user
import (
"context"
"database/sql"
"encoding/json"
"errors"
"net/http"
"strconv"
"sync"
"time"
_ "crypto/sha256" // Crypto
"connectrpc.com/connect"
"connectrpc.com/validate"
"github.com/go-webauthn/webauthn/protocol"
"github.com/go-webauthn/webauthn/webauthn"
"github.com/golang-jwt/jwt/v5"
"github.com/google/uuid"
"github.com/spotdemo4/trevstack/server/internal/auth"
userv1 "github.com/spotdemo4/trevstack/server/internal/connect/user/v1"
"github.com/spotdemo4/trevstack/server/internal/connect/user/v1/userv1connect"
"github.com/spotdemo4/trevstack/server/internal/interceptors"
@ -20,8 +25,13 @@ import (
)
type AuthHandler struct {
db *sqlc.Queries
key []byte
db *sqlc.Queries
webAuthn *webauthn.WebAuthn
key []byte
name string
sessions *map[string]*webauthn.SessionData
mu sync.Mutex
}
func (h *AuthHandler) Login(ctx context.Context, req *connect.Request[userv1.LoginRequest]) (*connect.Response[userv1.LoginResponse], error) {
@ -40,37 +50,18 @@ func (h *AuthHandler) Login(ctx context.Context, req *connect.Request[userv1.Log
return nil, connect.NewError(connect.CodePermissionDenied, errors.New("invalid username or password"))
}
// Generate JWT
t := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.RegisteredClaims{
Issuer: "trevstack",
Subject: strconv.FormatInt(user.ID, 10),
IssuedAt: &jwt.NumericDate{
Time: time.Now(),
},
ExpiresAt: &jwt.NumericDate{
Time: time.Now().Add(time.Hour * 24),
},
})
ss, err := t.SignedString(h.key)
// Create JWT
token, cookie, err := h.createJWT(user.ID)
if err != nil {
return nil, connect.NewError(connect.CodeInternal, err)
}
// Create cookie
cookie := http.Cookie{
Name: "token",
Value: ss,
Path: "/",
MaxAge: 86400,
HttpOnly: true,
Secure: true,
SameSite: http.SameSiteStrictMode,
}
// Create response
res := connect.NewResponse(&userv1.LoginResponse{
Token: ss,
Token: token,
})
res.Header().Set("Set-Cookie", cookie.String())
return res, nil
}
@ -82,7 +73,7 @@ func (h *AuthHandler) SignUp(ctx context.Context, req *connect.Request[userv1.Si
return nil, connect.NewError(connect.CodeInternal, err)
}
} else {
return nil, connect.NewError(connect.CodeAlreadyExists, err)
return nil, connect.NewError(connect.CodeAlreadyExists, errors.New("user already exists"))
}
// Check if confirmation passwords match
@ -98,8 +89,9 @@ func (h *AuthHandler) SignUp(ctx context.Context, req *connect.Request[userv1.Si
// Create user
_, err = h.db.InsertUser(ctx, sqlc.InsertUserParams{
Username: req.Msg.Username,
Password: string(hash),
Username: req.Msg.Username,
Password: string(hash),
WebauthnID: uuid.New().String(),
})
if err != nil {
return nil, connect.NewError(connect.CodeInternal, err)
@ -123,104 +115,173 @@ func (h *AuthHandler) Logout(_ context.Context, _ *connect.Request[userv1.Logout
res := connect.NewResponse(&userv1.LogoutResponse{})
res.Header().Set("Set-Cookie", cookie.String())
return res, nil
}
// func (h *AuthHandler) GetPasskeyIDs(_ context.Context, req *connect.Request[userv1.GetPasskeyIDsRequest]) (*connect.Response[userv1.GetPasskeyIDsResponse], error) {
// // Get user
// user := models.User{}
// if err := h.db.Preload("Passkeys").First(&user, "username = ?", req.Msg.Username).Error; err != nil {
// return nil, connect.NewError(connect.CodeNotFound, err)
// }
func (h *AuthHandler) BeginPasskeyLogin(ctx context.Context, req *connect.Request[userv1.BeginPasskeyLoginRequest]) (*connect.Response[userv1.BeginPasskeyLoginResponse], error) {
// Get user
pUser, _, err := h.getPasskeyUser(ctx, req.Msg.Username)
if err != nil {
return nil, connect.NewError(connect.CodeUnauthenticated, err)
}
// // Get IDs
// ids := []string{}
// for _, passkey := range user.Passkeys {
// ids = append(ids, passkey.ID)
// }
// Get options for user
options, session, err := h.webAuthn.BeginLogin(pUser)
if err != nil {
return nil, connect.NewError(connect.CodeInvalidArgument, err)
}
// return connect.NewResponse(&userv1.GetPasskeyIDsResponse{
// PasskeyIds: ids,
// }), nil
// }
// Turn the options into json
optionsJSON, err := json.Marshal(options)
if err != nil {
return nil, connect.NewError(connect.CodeInternal, err)
}
// func (h *AuthHandler) PasskeyLogin(_ context.Context, req *connect.Request[userv1.PasskeyLoginRequest]) (*connect.Response[userv1.PasskeyLoginResponse], error) {
// // Get passkey
// passkey := models.Passkey{}
// if err := h.db.First(&passkey, "id = ?", req.Msg.Id).Error; err != nil {
// return nil, connect.NewError(connect.CodeNotFound, err)
// }
// Set session for validation later
h.setSession(req.Msg.Username, session)
// // create a verifier from a trusted private key
// var verifier cose.Verifier
// var err error
// switch req.Msg.Algorithm {
// case -7:
// verifier, err = cose.NewVerifier(cose.AlgorithmES256, passkey.PublicKey)
return connect.NewResponse(&userv1.BeginPasskeyLoginResponse{
OptionsJson: string(optionsJSON),
}), nil
}
// case -257:
// verifier, err = cose.NewVerifier(cose.AlgorithmRS256, passkey.PublicKey)
func (h *AuthHandler) FinishPasskeyLogin(ctx context.Context, req *connect.Request[userv1.FinishPasskeyLoginRequest]) (*connect.Response[userv1.FinishPasskeyLoginResponse], error) {
// Get user
pUser, userID, err := h.getPasskeyUser(ctx, req.Msg.Username)
if err != nil {
return nil, connect.NewError(connect.CodeInvalidArgument, err)
}
// default:
// return nil, connect.NewError(connect.CodeInternal, errors.New("decode algorithm not implemented"))
// }
// if err != nil {
// return nil, connect.NewError(connect.CodeInternal, err)
// }
// Get the session data previously set
session, err := h.getSession(req.Msg.Username)
if err != nil {
return nil, connect.NewError(connect.CodeInvalidArgument, err)
}
// // create a sign message from a raw signature payload
// var msg cose.Sign1Message
// if err = msg.UnmarshalCBOR(req.Msg.Signature); err != nil {
// return nil, connect.NewError(connect.CodeInternal, err)
// }
// Parse the attestation response
parsedResponse, err := protocol.ParseCredentialRequestResponseBytes([]byte(req.Msg.Attestation))
if err != nil {
return nil, connect.NewError(connect.CodeInvalidArgument, err)
}
// // Validate passkey
// err = msg.Verify(nil, verifier)
// if err != nil {
// return nil, connect.NewError(connect.CodeUnauthenticated, err)
// }
// Validate the login
credential, err := h.webAuthn.ValidateLogin(pUser, *session, parsedResponse)
if err != nil {
return nil, connect.NewError(connect.CodeUnauthenticated, err)
}
// // Generate JWT
// t := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.RegisteredClaims{
// Issuer: "trevstack",
// Subject: strconv.FormatUint(uint64(passkey.UserID), 10),
// IssuedAt: &jwt.NumericDate{
// Time: time.Now(),
// },
// ExpiresAt: &jwt.NumericDate{
// Time: time.Now().Add(time.Hour * 24),
// },
// })
// ss, err := t.SignedString(h.key)
// if err != nil {
// return nil, connect.NewError(connect.CodeInternal, err)
// }
// Update the credential in the database
lastUsed := time.Now()
signCount := int64(credential.Authenticator.SignCount)
err = h.db.UpdateCredential(ctx, sqlc.UpdateCredentialParams{
// set
LastUsed: &lastUsed,
SignCount: &signCount,
// // Create cookie
// cookie := http.Cookie{
// Name: "token",
// Value: ss,
// Path: "/",
// MaxAge: 86400,
// HttpOnly: true,
// Secure: true,
// SameSite: http.SameSiteStrictMode,
// }
// where
ID: string(credential.ID),
UserID: userID,
})
if err != nil {
return nil, connect.NewError(connect.CodeInternal, err)
}
// res := connect.NewResponse(&userv1.PasskeyLoginResponse{
// Token: ss,
// })
// res.Header().Set("Set-Cookie", cookie.String())
// return res, nil
// }
// Create JWT
token, cookie, err := h.createJWT(userID)
if err != nil {
return nil, connect.NewError(connect.CodeInternal, err)
}
func NewAuthHandler(db *sqlc.Queries, key string) (string, http.Handler) {
interceptors := connect.WithInterceptors(interceptors.NewRateLimitInterceptor(key))
// Create response
resp := connect.NewResponse(&userv1.FinishPasskeyLoginResponse{
Token: token,
})
resp.Header().Set("Set-Cookie", cookie.String())
return resp, nil
}
func (h *AuthHandler) getPasskeyUser(ctx context.Context, username string) (*auth.User, int64, error) {
user, err := h.db.GetUserbyUsername(ctx, username)
if err != nil {
return nil, 0, err
}
creds, err := h.db.GetCredentials(ctx, user.ID)
if err != nil {
return nil, 0, err
}
webCreds := auth.NewCreds(creds)
webUser := auth.NewUser(user.WebauthnID, user.Username, webCreds)
return &webUser, user.ID, nil
}
func (h *AuthHandler) getSession(username string) (*webauthn.SessionData, error) {
h.mu.Lock()
defer h.mu.Unlock()
session, ok := (*h.sessions)[username]
if !ok {
return nil, errors.New("session does not exist")
}
delete(*h.sessions, username)
return session, nil
}
func (h *AuthHandler) setSession(username string, data *webauthn.SessionData) {
h.mu.Lock()
defer h.mu.Unlock()
(*h.sessions)[username] = data
}
func (h *AuthHandler) createJWT(userid int64) (string, *http.Cookie, error) {
token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.RegisteredClaims{
Issuer: h.name,
Subject: strconv.Itoa(int(userid)),
IssuedAt: &jwt.NumericDate{
Time: time.Now(),
},
ExpiresAt: &jwt.NumericDate{
Time: time.Now().Add(time.Hour * 24),
},
})
tokenString, err := token.SignedString(h.key)
if err != nil {
return "", nil, err
}
cookie := http.Cookie{
Name: "token",
Value: tokenString,
Path: "/",
MaxAge: 86400,
HttpOnly: true,
Secure: true,
SameSite: http.SameSiteStrictMode,
}
return tokenString, &cookie, nil
}
func NewAuthHandler(vi *validate.Interceptor, db *sqlc.Queries, webauth *webauthn.WebAuthn, name string, key string) (string, http.Handler) {
interceptors := connect.WithInterceptors(vi, interceptors.NewRateLimitInterceptor(key))
sd := map[string]*webauthn.SessionData{}
return userv1connect.NewAuthServiceHandler(
&AuthHandler{
db: db,
key: []byte(key),
db: db,
webAuthn: webauth,
name: name,
key: []byte(key),
sessions: &sd,
mu: sync.Mutex{},
},
interceptors,
)

View File

@ -3,13 +3,19 @@ package user
import (
"context"
"database/sql"
"encoding/json"
"errors"
"net/http"
"strconv"
"sync"
"time"
"connectrpc.com/connect"
"connectrpc.com/validate"
"github.com/go-webauthn/webauthn/protocol"
"github.com/go-webauthn/webauthn/webauthn"
"github.com/golang-jwt/jwt/v5"
"github.com/spotdemo4/trevstack/server/internal/auth"
userv1 "github.com/spotdemo4/trevstack/server/internal/connect/user/v1"
"github.com/spotdemo4/trevstack/server/internal/connect/user/v1/userv1connect"
"github.com/spotdemo4/trevstack/server/internal/interceptors"
@ -27,8 +33,12 @@ func userToConnect(item sqlc.User) *userv1.User {
}
type Handler struct {
db *sqlc.Queries
key []byte
db *sqlc.Queries
webAuthn *webauthn.WebAuthn
key []byte
sessions *map[int64]*webauthn.SessionData
mu sync.Mutex
}
func (h *Handler) GetUser(ctx context.Context, _ *connect.Request[userv1.GetUserRequest]) (*connect.Response[userv1.GetUserResponse], error) {
@ -200,75 +210,149 @@ func (h *Handler) UpdateProfilePicture(ctx context.Context, req *connect.Request
return res, nil
}
// func (h *Handler) BeginPasskeyRegistration(ctx context.Context, req *connect.Request[userv1.BeginPasskeyRegistrationRequest]) (*connect.Response[userv1.BeginPasskeyRegistrationResponse], error) {
// // Get user ID from context
// userid, ok := interceptors.GetUserContext(ctx)
// if !ok {
// return nil, connect.NewError(connect.CodeUnauthenticated, errors.New("user not authenticated"))
// }
func (h *Handler) BeginPasskeyRegistration(ctx context.Context, _ *connect.Request[userv1.BeginPasskeyRegistrationRequest]) (*connect.Response[userv1.BeginPasskeyRegistrationResponse], error) {
userid, ok := interceptors.GetUserContext(ctx)
if !ok {
return nil, connect.NewError(connect.CodeUnauthenticated, errors.New("unauthenticated"))
}
// // Get user
// user := models.User{}
// if err := h.db.First(&user, "id = ?", userid).Error; err != nil {
// return nil, connect.NewError(connect.CodeInternal, err)
// }
// Get user
pUser, err := h.getPasskeyUser(ctx, userid)
if err != nil {
return nil, connect.NewError(connect.CodeInvalidArgument, err)
}
// return connect.NewResponse(&userv1.BeginPasskeyRegistrationResponse{}), nil
// }
// Get options for user
options, session, err := h.webAuthn.BeginRegistration(pUser)
if err != nil {
return nil, connect.NewError(connect.CodeInvalidArgument, err)
}
// func (h *Handler) FinishPasskeyRegistration(ctx context.Context, req *connect.Request[userv1.FinishPasskeyRegistrationRequest]) (*connect.Response[userv1.FinishPasskeyRegistrationResponse], error) {
// // Get user ID from context
// userid, ok := interceptors.GetUserContext(ctx)
// if !ok {
// return nil, connect.NewError(connect.CodeUnauthenticated, errors.New("user not authenticated"))
// }
// Turn options into json
optionsJSON, err := json.Marshal(options)
if err != nil {
return nil, connect.NewError(connect.CodeInternal, err)
}
// // Get user
// user := models.User{}
// if err := h.db.First(&user, "id = ?", userid).Error; err != nil {
// return nil, connect.NewError(connect.CodeInternal, err)
// }
// Set session for validation later
h.setSession(userid, session)
// return connect.NewResponse(&userv1.FinishPasskeyRegistrationResponse{}), nil
// }
return connect.NewResponse(&userv1.BeginPasskeyRegistrationResponse{
OptionsJson: string(optionsJSON),
}), nil
}
// func BeginRegistration(ctx context.Context) error {
// userid, ok := interceptors.GetUserContext(ctx)
// if !ok {
// return nil
// }
func (h *Handler) FinishPasskeyRegistration(ctx context.Context, req *connect.Request[userv1.FinishPasskeyRegistrationRequest]) (*connect.Response[userv1.FinishPasskeyRegistrationResponse], error) {
userid, ok := interceptors.GetUserContext(ctx)
if !ok {
return nil, connect.NewError(connect.CodeUnauthenticated, errors.New("unauthenticated"))
}
// wconfig := &webauthn.Config{
// RPDisplayName: "Go Webauthn", // Display Name for your site
// RPID: "go-webauthn.local", // Generally the FQDN for your site
// RPOrigins: []string{"https://login.go-webauthn.local"}, // The origin URLs allowed for WebAuthn requests
// }
// webAuthn, err := webauthn.New(wconfig)
// if err != nil {
// return nil
// }
// Get user
pUser, err := h.getPasskeyUser(ctx, userid)
if err != nil {
return nil, connect.NewError(connect.CodeInvalidArgument, err)
}
// var user webauthn.User
// user.WebAuthnCredentials()
// Get the session data previously set
session, err := h.getSession(userid)
if err != nil {
return nil, connect.NewError(connect.CodeInvalidArgument, err)
}
// var cred webauthn.Credential
// cred.Verify()
// Parse the attestation response
parsedResponse, err := protocol.ParseCredentialCreationResponseBytes([]byte(req.Msg.Attestation))
if err != nil {
return nil, connect.NewError(connect.CodeInvalidArgument, err)
}
// var test metadata.Provider
// test.
// Create the credential
credential, err := h.webAuthn.CreateCredential(pUser, *session, parsedResponse)
if err != nil {
return nil, connect.NewError(connect.CodeInternal, err)
}
// options, session, err := webAuthn.BeginRegistration(user)
transports := transportsToString(credential.Transport)
// return nil
// }
// Save the credential
err = h.db.InsertCredential(ctx, sqlc.InsertCredentialParams{
CredID: string(credential.ID),
CredPublicKey: credential.PublicKey,
SignCount: int64(credential.Authenticator.SignCount),
Transports: &transports,
UserVerified: &credential.Flags.UserVerified,
BackupEligible: &credential.Flags.BackupEligible,
BackupState: &credential.Flags.BackupState,
AttestationObject: credential.Attestation.Object,
AttestationClientData: credential.Attestation.ClientDataJSON,
CreatedAt: time.Now(),
LastUsed: time.Now(),
UserID: userid,
})
if err != nil {
return nil, connect.NewError(connect.CodeInternal, err)
}
func NewHandler(db *sqlc.Queries, key string) (string, http.Handler) {
interceptors := connect.WithInterceptors(interceptors.NewAuthInterceptor(key))
return connect.NewResponse(&userv1.FinishPasskeyRegistrationResponse{}), nil
}
func (h *Handler) getPasskeyUser(ctx context.Context, userid int64) (*auth.User, error) {
user, err := h.db.GetUser(ctx, userid)
if err != nil {
return nil, err
}
creds, err := h.db.GetCredentials(ctx, user.ID)
if err != nil {
return nil, err
}
webCreds := auth.NewCreds(creds)
webUser := auth.NewUser(user.WebauthnID, user.Username, webCreds)
return &webUser, nil
}
func (h *Handler) getSession(userid int64) (*webauthn.SessionData, error) {
h.mu.Lock()
defer h.mu.Unlock()
session, ok := (*h.sessions)[userid]
if !ok {
return nil, errors.New("session does not exist")
}
delete(*h.sessions, userid)
return session, nil
}
func (h *Handler) setSession(userid int64, data *webauthn.SessionData) {
h.mu.Lock()
defer h.mu.Unlock()
(*h.sessions)[userid] = data
}
func transportsToString(transports []protocol.AuthenticatorTransport) string {
s := ""
for _, transport := range transports {
s += string(transport) + ", "
}
return s
}
func NewHandler(vi *validate.Interceptor, db *sqlc.Queries, webauth *webauthn.WebAuthn, key string) (string, http.Handler) {
interceptors := connect.WithInterceptors(interceptors.NewAuthInterceptor(key), vi)
sd := map[int64]*webauthn.SessionData{}
return userv1connect.NewUserServiceHandler(
&Handler{
db: db,
key: []byte(key),
db: db,
webAuthn: webauth,
key: []byte(key),
sessions: &sd,
mu: sync.Mutex{},
},
interceptors,
)

View File

@ -96,7 +96,13 @@ func (i *AuthInterceptor) WrapUnary(next connect.UnaryFunc) connect.UnaryFunc {
ctx, err = newUserContext(ctx, subject)
if err == nil {
return next(ctx, req)
} else {
log.Println("huh")
log.Println(err)
}
} else {
log.Println("what")
log.Println(err)
}
}
}
@ -176,7 +182,7 @@ func getCookies(rawCookies string) []*http.Cookie {
}
func validateToken(tokenString string, key string) (subject string, err error) {
token, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) {
token, err := jwt.Parse(tokenString, func(token *jwt.Token) (any, error) {
// Don't forget to validate the alg is what you expect:
if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
return nil, fmt.Errorf("unexpected signing method: %v", token.Header["alg"])

View File

@ -0,0 +1,221 @@
// Code generated by sqlc. DO NOT EDIT.
// versions:
// sqlc v1.29.0
// source: credential.sql
package sqlc
import (
"context"
"time"
)
const deleteCredential = `-- name: DeleteCredential :exec
DELETE FROM credential
WHERE
cred_id = ?1
AND
user_id = ?2
`
type DeleteCredentialParams struct {
ID string
UserID int64
}
func (q *Queries) DeleteCredential(ctx context.Context, arg DeleteCredentialParams) error {
_, err := q.db.ExecContext(ctx, deleteCredential, arg.ID, arg.UserID)
return err
}
const getCredential = `-- name: GetCredential :one
SELECT
cred_id,
cred_public_key,
sign_count,
transports,
user_verified,
backup_eligible,
backup_state,
attestation_object,
attestation_client_data,
created_at,
last_used,
user_id
FROM credential
WHERE
cred_id = ?1
AND
user_id = ?2
LIMIT 1
`
type GetCredentialParams struct {
ID string
UserID int64
}
func (q *Queries) GetCredential(ctx context.Context, arg GetCredentialParams) (Credential, error) {
row := q.db.QueryRowContext(ctx, getCredential, arg.ID, arg.UserID)
var i Credential
err := row.Scan(
&i.CredID,
&i.CredPublicKey,
&i.SignCount,
&i.Transports,
&i.UserVerified,
&i.BackupEligible,
&i.BackupState,
&i.AttestationObject,
&i.AttestationClientData,
&i.CreatedAt,
&i.LastUsed,
&i.UserID,
)
return i, err
}
const getCredentials = `-- name: GetCredentials :many
SELECT
cred_id,
cred_public_key,
sign_count,
transports,
user_verified,
backup_eligible,
backup_state,
attestation_object,
attestation_client_data,
created_at,
last_used,
user_id
FROM credential
WHERE user_id = ?1
`
func (q *Queries) GetCredentials(ctx context.Context, userID int64) ([]Credential, error) {
rows, err := q.db.QueryContext(ctx, getCredentials, userID)
if err != nil {
return nil, err
}
defer rows.Close()
var items []Credential
for rows.Next() {
var i Credential
if err := rows.Scan(
&i.CredID,
&i.CredPublicKey,
&i.SignCount,
&i.Transports,
&i.UserVerified,
&i.BackupEligible,
&i.BackupState,
&i.AttestationObject,
&i.AttestationClientData,
&i.CreatedAt,
&i.LastUsed,
&i.UserID,
); err != nil {
return nil, err
}
items = append(items, i)
}
if err := rows.Close(); err != nil {
return nil, err
}
if err := rows.Err(); err != nil {
return nil, err
}
return items, nil
}
const insertCredential = `-- name: InsertCredential :exec
INSERT INTO credential (
cred_id,
cred_public_key,
sign_count,
transports,
user_verified,
backup_eligible,
backup_state,
attestation_object,
attestation_client_data,
created_at,
last_used,
user_id
) VALUES (
?1,
?2,
?3,
?4,
?5,
?6,
?7,
?8,
?9,
?10,
?11,
?12
)
`
type InsertCredentialParams struct {
CredID string
CredPublicKey []byte
SignCount int64
Transports *string
UserVerified *bool
BackupEligible *bool
BackupState *bool
AttestationObject []byte
AttestationClientData []byte
CreatedAt time.Time
LastUsed time.Time
UserID int64
}
func (q *Queries) InsertCredential(ctx context.Context, arg InsertCredentialParams) error {
_, err := q.db.ExecContext(ctx, insertCredential,
arg.CredID,
arg.CredPublicKey,
arg.SignCount,
arg.Transports,
arg.UserVerified,
arg.BackupEligible,
arg.BackupState,
arg.AttestationObject,
arg.AttestationClientData,
arg.CreatedAt,
arg.LastUsed,
arg.UserID,
)
return err
}
const updateCredential = `-- name: UpdateCredential :exec
UPDATE credential
SET
last_used = COALESCE(?1, last_used),
sign_count = COALESCE(?2, sign_count)
WHERE
cred_id = ?3
AND
user_id = ?4
`
type UpdateCredentialParams struct {
LastUsed *time.Time
SignCount *int64
ID string
UserID int64
}
func (q *Queries) UpdateCredential(ctx context.Context, arg UpdateCredentialParams) error {
_, err := q.db.ExecContext(ctx, updateCredential,
arg.LastUsed,
arg.SignCount,
arg.ID,
arg.UserID,
)
return err
}

View File

@ -1,6 +1,6 @@
// Code generated by sqlc. DO NOT EDIT.
// versions:
// sqlc v1.28.0
// sqlc v1.29.0
package sqlc

View File

@ -1,6 +1,6 @@
// Code generated by sqlc. DO NOT EDIT.
// versions:
// sqlc v1.28.0
// sqlc v1.29.0
// source: file.sql
package sqlc

View File

@ -1,6 +1,6 @@
// Code generated by sqlc. DO NOT EDIT.
// versions:
// sqlc v1.28.0
// sqlc v1.29.0
// source: item.sql
package sqlc
@ -85,6 +85,7 @@ WHERE
AND
(added <= ?4 OR ?4 IS NULL)
)
ORDER BY added DESC
LIMIT
?6
OFFSET

View File

@ -1,6 +1,6 @@
// Code generated by sqlc. DO NOT EDIT.
// versions:
// sqlc v1.28.0
// sqlc v1.29.0
package sqlc
@ -8,6 +8,21 @@ import (
"time"
)
type Credential struct {
CredID string
CredPublicKey []byte
SignCount int64
Transports *string
UserVerified *bool
BackupEligible *bool
BackupState *bool
AttestationObject []byte
AttestationClientData []byte
CreatedAt time.Time
LastUsed time.Time
UserID int64
}
type File struct {
ID int64
Name string
@ -34,4 +49,5 @@ type User struct {
Username string
Password string
ProfilePictureID *int64
WebauthnID string
}

View File

@ -1,6 +1,6 @@
// Code generated by sqlc. DO NOT EDIT.
// versions:
// sqlc v1.28.0
// sqlc v1.29.0
// source: user.sql
package sqlc
@ -24,7 +24,8 @@ SELECT
id,
username,
password,
profile_picture_id
profile_picture_id,
webauthn_id
FROM user
WHERE
id = ?1
@ -39,6 +40,7 @@ func (q *Queries) GetUser(ctx context.Context, id int64) (User, error) {
&i.Username,
&i.Password,
&i.ProfilePictureID,
&i.WebauthnID,
)
return i, err
}
@ -48,7 +50,8 @@ SELECT
id,
username,
password,
profile_picture_id
profile_picture_id,
webauthn_id
FROM user
WHERE
username = ?1
@ -63,6 +66,7 @@ func (q *Queries) GetUserbyUsername(ctx context.Context, username string) (User,
&i.Username,
&i.Password,
&i.ProfilePictureID,
&i.WebauthnID,
)
return i, err
}
@ -70,21 +74,24 @@ func (q *Queries) GetUserbyUsername(ctx context.Context, username string) (User,
const insertUser = `-- name: InsertUser :one
INSERT INTO user (
username,
password
password,
webauthn_id
) VALUES (
?1,
?2
?2,
?3
)
RETURNING id
`
type InsertUserParams struct {
Username string
Password string
Username string
Password string
WebauthnID string
}
func (q *Queries) InsertUser(ctx context.Context, arg InsertUserParams) (int64, error) {
row := q.db.QueryRowContext(ctx, insertUser, arg.Username, arg.Password)
row := q.db.QueryRowContext(ctx, insertUser, arg.Username, arg.Password, arg.WebauthnID)
var id int64
err := row.Scan(&id)
return id, err