mirror of Walter-Sparrow / lunar-tear
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

Add --no-register flag and register-account CLI

Author: https://github.com/REUSS-dev

authored by

AnyUnion and committed by
GitHub
56457400 b414db73

+205 -38
+28 -5
README.md
··· 173 173 | `--grpc.public-addr` | `10.0.2.2:8003` | lunar-tear externally-reachable addr | 174 174 | `--grpc.octo-url` | `http://10.0.2.2:8080` | Octo CDN base URL passed to lunar-tear | 175 175 | `--grpc.auth-url` | `http://localhost:3000` | auth server base URL passed to lunar-tear | 176 + | `--no-register` | `false` | disable new user registrations (only already registered users can connect). | 176 177 | `--admin.listen` | *(empty)* | lunar-tear admin webhook bind. Empty = leave default; webhook only binds when `LUNAR_ADMIN_TOKEN` is set in the env. | 177 178 | `--no-color` | `false` | disable colored output | 178 179 ··· 195 196 | `--db` | `db/game.db` | SQLite database path | 196 197 | `--auth-url` | *(empty)* | Auth server base URL (e.g. `http://localhost:3000`) | 197 198 | `--admin-listen` | `127.0.0.1:8082` | Admin webhook listen address. Only binds when `LUNAR_ADMIN_TOKEN` is set. | 199 + | `--no-register` | `false` | Disable new user registrations (only already registered users can connect). | 198 200 199 201 ### Live Master Data Reload 200 202 ··· 270 272 | `make build-all` | Build all service binaries to `bin/` | 271 273 | `make build-import` | Build the import-snapshot tool | 272 274 | `make build-claim-account` | Build the claim-account tool | 275 + | `make build-register-account` | Build the register-account tool | 273 276 | `make clean` | Remove the `bin/` directory | 274 277 | `make dev` | Run all three services with one command | 275 278 | `make migrate` | Run goose migrations on `db/game.db` | ··· 308 311 309 312 ### Flags 310 313 311 - | Flag | Default | Description | 312 - | ---------- | --------------- | -------------------------------------------- | 313 - | `--listen` | `0.0.0.0:3000` | HTTP listen address (host:port) | 314 - | `--db` | `db/auth.db` | SQLite database path for auth users | 315 - | `--secret` | *(generated)* | Hex-encoded HMAC secret for token signing | 314 + | Flag | Default | Description | 315 + | ---------------- | --------------- | -------------------------------------------- | 316 + | `--listen` | `0.0.0.0:3000` | HTTP listen address (host:port) | 317 + | `--db` | `db/auth.db` | SQLite database path for auth users | 318 + | `--secret` | *(generated)* | Hex-encoded HMAC secret for token signing | 319 + | `--no-register` | `false` | Disable new user registrations (only already registered users can log in). | 320 + 321 + ## Create account 322 + 323 + This tool creates a fresh account in main db and new account in Auth Server store with given name & password and automatically binds them together. 324 + A primary mean of registering new accounts when `--no-register` flag is passed to lunar-tear for controlled server access. 325 + 326 + ```bash 327 + go run ./cmd/register-account --name "AccountName" --password "AccountPassword" --platform "android" 328 + ``` 329 + 330 + | Flag | Default | Description | 331 + | ------------ | ------------ | ------------------------------------------------------------ | 332 + | `--name` | *(required)* | Auth Server account nickname to be registered | 333 + | `--password` | *(required)* | Auth Server account password to be registered | 334 + | `--platform` | `android` | Platform of new user account (`android` or `ios`) | 335 + | `--db` | `db/game.db` | SQLite main database path | 336 + | `--auth-db` | `db/auth.db` | SQLite Auth Server database path | 337 + 338 + This only sets the nickname of Auth Server account, a player can choose their in-game nickname upon first login! 316 339 317 340 ## ⚠️ Legal Disclaimer 318 341
+4 -1
server/Makefile
··· 30 30 build-claim-account: 31 31 go build -o claim-account$(EXE) ./cmd/claim-account 32 32 33 + build-register-account: 34 + go build -o register-account$(EXE) ./cmd/register-account 35 + 33 36 build-dev: 34 37 go build -o bin/dev$(EXE) ./cmd/dev 35 38 ··· 57 60 endif 58 61 go run ./cmd/import-snapshot --snapshot $(SNAPSHOT) --uuid $(UUID) 59 62 60 - .PHONY: proto build build-cdn build-auth build-import build-claim-account build-dev build-all clean dev migrate import 63 + .PHONY: proto build build-cdn build-auth build-import build-claim-account build-register-account build-dev build-all clean dev migrate import
+15 -8
server/cmd/auth-server/handlers.go
··· 7 7 "fmt" 8 8 "html/template" 9 9 "log" 10 + "lunar-tear/server/internal/auth" 10 11 "net/http" 11 12 "net/url" 12 13 "strconv" ··· 39 40 `)) 40 41 41 42 type Handlers struct { 42 - store *AuthStore 43 - tok *TokenService 43 + store *auth.AuthStore 44 + tok *auth.TokenService 45 + noRegister bool 44 46 } 45 47 46 - func NewHandlers(store *AuthStore, tok *TokenService) *Handlers { 47 - return &Handlers{store: store, tok: tok} 48 + func NewHandlers(store *auth.AuthStore, tok *auth.TokenService, noRegister bool) *Handlers { 49 + return &Handlers{store: store, tok: tok, noRegister: noRegister} 48 50 } 49 51 50 52 type loginPageData struct { ··· 139 141 return 140 142 } 141 143 142 - var user AuthUser 144 + var user auth.AuthUser 143 145 var err error 144 146 145 147 switch action { 146 148 case "register": 149 + if h.noRegister { 150 + renderErr("This server does not accept user registrations.") 151 + return 152 + } 153 + 147 154 user, err = h.store.CreateUser(username, password) 148 - if err == ErrUserExists { 155 + if err == auth.ErrUserExists { 149 156 renderErr("Username is already taken.") 150 157 return 151 158 } ··· 158 165 159 166 case "login": 160 167 user, err = h.store.VerifyUser(username, password) 161 - if err == ErrInvalidCreds { 168 + if err == auth.ErrInvalidCreds { 162 169 renderErr("Invalid username or password.") 163 170 return 164 171 } ··· 187 194 fragment := url.Values{} 188 195 fragment.Set("access_token", token) 189 196 fragment.Set("token_type", "bearer") 190 - fragment.Set("expires_in", strconv.FormatInt(int64(tokenTTL.Seconds()), 10)) 197 + fragment.Set("expires_in", strconv.FormatInt(int64(auth.TokenTTL.Seconds()), 10)) 191 198 fragment.Set("signed_request", "0."+b64) 192 199 // iOS FBSDKLoginManager treats an empty granted_scopes set as a cancelled login 193 200 // (LoginManager.swift -> getSuccessResult -> getCancelledResult). Echo back the
+6 -3
server/cmd/auth-server/main.go
··· 8 8 "log" 9 9 "net/http" 10 10 11 + "lunar-tear/server/internal/auth" 12 + 11 13 _ "modernc.org/sqlite" 12 14 ) 13 15 ··· 15 17 listen := flag.String("listen", "0.0.0.0:3000", "HTTP listen address (host:port)") 16 18 dbPath := flag.String("db", "db/auth.db", "SQLite database path for auth users") 17 19 secret := flag.String("secret", "", "HMAC secret for tokens (auto-generated if empty)") 20 + noRegister := flag.Bool("no-register", false, "Disallow new account registrations for clients, when present. Default = false") 18 21 flag.Parse() 19 22 20 23 hmacSecret := []byte(*secret) ··· 33 36 } 34 37 defer db.Close() 35 38 36 - store, err := NewAuthStore(db) 39 + store, err := auth.NewAuthStore(db) 37 40 if err != nil { 38 41 log.Fatalf("init auth store: %v", err) 39 42 } 40 43 41 - tok := NewTokenService(hmacSecret) 42 - h := NewHandlers(store, tok) 44 + tok := auth.NewTokenService(hmacSecret) 45 + h := NewHandlers(store, tok, *noRegister) 43 46 44 47 mux := http.NewServeMux() 45 48 mux.HandleFunc("/", h.HandleOAuth)
+1 -1
server/cmd/auth-server/store.go server/internal/auth/store.go
··· 1 - package main 1 + package auth 2 2 3 3 import ( 4 4 "database/sql"
+3 -3
server/cmd/auth-server/token.go server/internal/auth/token.go
··· 1 - package main 1 + package auth 2 2 3 3 import ( 4 4 "crypto/hmac" ··· 10 10 "time" 11 11 ) 12 12 13 - const tokenTTL = 24 * time.Hour 13 + const TokenTTL = 24 * time.Hour 14 14 15 15 var ( 16 16 ErrTokenInvalid = errors.New("invalid token") ··· 38 38 Sub: user.ID, 39 39 Name: user.Username, 40 40 Iat: now, 41 - Exp: now + int64(tokenTTL.Seconds()), 41 + Exp: now + int64(TokenTTL.Seconds()), 42 42 } 43 43 44 44 payload, err := json.Marshal(claims)
+18 -2
server/cmd/dev/main.go
··· 97 97 // (the listener still only binds if LUNAR_ADMIN_TOKEN is set in the env). 98 98 adminListen := flag.String("admin.listen", "", "lunar-tear admin webhook listen address (host:port). Empty = leave default; webhook only binds when LUNAR_ADMIN_TOKEN is set in the env.") 99 99 100 + // Controlled server access 101 + noRegister := flag.Bool("no-register", false, "Disallow new account registrations for clients, when present. Default = false") 102 + 103 + // dev utility output config 100 104 noColor := flag.Bool("no-color", false, "disable colored output") 105 + 101 106 flag.Parse() 102 107 103 108 if *grpcOctoURL == "" { ··· 121 126 ext := binExt() 122 127 ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM) 123 128 defer stop() 129 + 130 + noreg_s := "" 131 + if *noRegister { 132 + noreg_s = "--no-register" 133 + } 124 134 125 135 services := []service{ 126 136 { ··· 129 139 cmd: exec.CommandContext(ctx, filepath.Join("bin", "auth-server"+ext), 130 140 "--listen", *authListen, 131 141 "--db", *authDB, 142 + noreg_s, 132 143 ), 133 144 }, 134 145 { ··· 143 154 label: "grpc", 144 155 color: colorYellow, 145 156 cmd: exec.CommandContext(ctx, filepath.Join("bin", "lunar-tear"+ext), 146 - grpcArgs(*grpcListen, *grpcPublicAddr, *grpcDB, *grpcOctoURL, *grpcAuthURL, *adminListen)..., 157 + grpcArgs(*grpcListen, *grpcPublicAddr, *grpcDB, *grpcOctoURL, *grpcAuthURL, *adminListen, *noRegister)..., 147 158 ), 148 159 }, 149 160 } ··· 204 215 // grpcArgs assembles the argv for the lunar-tear subprocess. The admin flag 205 216 // is appended only when --admin.listen was supplied so we don't override 206 217 // lunar-tear's own default when the operator hasn't opted in. 207 - func grpcArgs(listen, publicAddr, db, octoURL, authURL, adminListen string) []string { 218 + func grpcArgs(listen, publicAddr, db, octoURL, authURL, adminListen string, noRegister bool) []string { 208 219 args := []string{ 209 220 "--listen", listen, 210 221 "--public-addr", publicAddr, ··· 212 223 "--octo-url", octoURL, 213 224 "--auth-url", authURL, 214 225 } 226 + 215 227 if adminListen != "" { 216 228 args = append(args, "--admin-listen", adminListen) 229 + } 230 + 231 + if noRegister { 232 + args = append(args, "--no-register") 217 233 } 218 234 return args 219 235 }
+8 -2
server/cmd/lunar-tear/grpc.go
··· 39 39 store.SessionRepository 40 40 }, 41 41 holder *runtime.Holder, 42 + noRegister bool, 42 43 ) *grpc.Server { 43 44 lis, err := net.Listen("tcp", listenAddr) 44 45 if err != nil { ··· 52 53 grpc.UnknownServiceHandler(interceptor.UnknownService), 53 54 ) 54 55 55 - registerServices(grpcServer, publicAddr, octoURL, authURL, userStore, holder) 56 + registerServices(grpcServer, publicAddr, octoURL, authURL, userStore, holder, noRegister) 56 57 57 58 reflection.Register(grpcServer) 58 59 59 60 log.Printf("gRPC server listening on %s", lis.Addr()) 60 61 log.Printf("public address: %s", publicAddr) 61 62 63 + if noRegister { 64 + log.Print("[!!WARNING!!] The gRPC server is running in NO-REGISTER mode. All new user registrations are denied, only existing accounts and auth-server logins are permitted.") 65 + } 66 + 62 67 go func() { 63 68 if err := grpcServer.Serve(lis); err != nil { 64 69 log.Printf("gRPC server stopped: %v", err) ··· 77 82 store.SessionRepository 78 83 }, 79 84 holder *runtime.Holder, 85 + noRegister bool, 80 86 ) { 81 87 pubHost, pubPortStr, _ := net.SplitHostPort(publicAddr) 82 88 pubPort, _ := strconv.Atoi(pubPortStr) 83 89 84 90 pb.RegisterBannerServiceServer(srv, service.NewBannerServiceServer(holder)) 85 - pb.RegisterUserServiceServer(srv, service.NewUserServiceServer(userStore, userStore, authURL)) 91 + pb.RegisterUserServiceServer(srv, service.NewUserServiceServer(userStore, userStore, authURL, noRegister)) 86 92 pb.RegisterBattleServiceServer(srv, service.NewBattleServiceServer(userStore, userStore)) 87 93 pb.RegisterConfigServiceServer(srv, service.NewConfigServiceServer(pubHost, int32(pubPort), octoURL)) 88 94 pb.RegisterDataServiceServer(srv, service.NewDataServiceServer(userStore, userStore))
+2 -1
server/cmd/lunar-tear/main.go
··· 23 23 octoURL := flag.String("octo-url", "", "Octo CDN base URL the client will use for assets (e.g. http://10.0.2.2:8080)") 24 24 authURL := flag.String("auth-url", "", "Auth server base URL for Facebook token validation (e.g. http://localhost:3000)") 25 25 adminListen := flag.String("admin-listen", "127.0.0.1:8082", "admin webhook listen address (host:port). Loopback by default; only binds when LUNAR_ADMIN_TOKEN is set.") 26 + noRegister := flag.Bool("no-register", false, "Disallow new account registrations for clients, when present. Default = false") 26 27 flag.Parse() 27 28 28 29 if *octoURL == "" { ··· 46 47 47 48 userStore := sqlite.New(db, gametime.Now) 48 49 49 - grpcServer := startGRPC(*listen, *publicAddr, *octoURL, *authURL, userStore, holder) 50 + grpcServer := startGRPC(*listen, *publicAddr, *octoURL, *authURL, userStore, holder, *noRegister) 50 51 51 52 startAdmin(*adminListen, holder) 52 53
+99
server/cmd/register-account/main.go
··· 1 + package main 2 + 3 + import ( 4 + "flag" 5 + "log" 6 + 7 + "github.com/google/uuid" 8 + 9 + "lunar-tear/server/internal/auth" 10 + "lunar-tear/server/internal/database" 11 + "lunar-tear/server/internal/model" 12 + "lunar-tear/server/internal/store/sqlite" 13 + ) 14 + 15 + func main() { 16 + dbPath := flag.String("db", "db/game.db", "SQLite database path") 17 + authdbPath := flag.String("auth-db", "db/auth.db", "SQLite auth server database path") 18 + 19 + name := flag.String("name", "", "Nickname of the new account to-be") 20 + password := flag.String("password", "", "Password of the new account to-be") 21 + platform := flag.String("platform", "android", "Platform of the user. Can be: \"android\", \"ios\"") 22 + 23 + flag.Parse() 24 + 25 + if *name == "" { 26 + log.Fatal("--name flag is required") 27 + } 28 + 29 + if *password == "" { 30 + log.Fatal("--password flag is required") 31 + } 32 + 33 + if (*platform != "android") && (*platform != "ios") { 34 + log.Fatal("--platform can be either \"android\" or \"ios\"") 35 + } 36 + 37 + db, err := database.Open(*dbPath) 38 + if err != nil { 39 + log.Fatalf("open database: %v", err) 40 + } 41 + defer db.Close() 42 + 43 + userStore := sqlite.New(db, nil) 44 + 45 + authdb, err := database.Open(*authdbPath) 46 + if err != nil { 47 + log.Fatalf("open auth database: %v", err) 48 + } 49 + defer db.Close() 50 + 51 + authStore, err := auth.NewAuthStore(authdb) 52 + if err != nil { 53 + log.Fatalf("init auth store: %v", err) 54 + } 55 + 56 + // Auth user check 57 + 58 + userExists := authStore.UserExists(*name) 59 + if userExists { 60 + log.Fatal("Username is already taken") 61 + } 62 + 63 + // lunar-tear user 64 + 65 + var userPlatform model.ClientPlatform 66 + 67 + if *platform == "android" { 68 + userPlatform.OsType = 2 69 + userPlatform.PlatformType = 2 70 + } else { 71 + userPlatform.OsType = 1 72 + userPlatform.PlatformType = 1 73 + } 74 + 75 + userUuid := uuid.New().String() 76 + id, err := userStore.CreateUser(userUuid, userPlatform) 77 + 78 + if err == nil { 79 + log.Printf("Registered user %d in database successfully", id) 80 + } else { 81 + log.Fatalf("Register user in database: %v", err) 82 + } 83 + 84 + // Bind 85 + 86 + authUser, err := authStore.CreateUser(*name, *password) 87 + if err != nil { 88 + log.Fatalf("Register auth account: %v", err) 89 + } 90 + 91 + err = userStore.SetFacebookId(id, authUser.ID) 92 + if err == nil { 93 + log.Printf("Bound user %d with facebook account %v", id, authUser.Username) 94 + } else { 95 + log.Fatalf("failed to bind user with facebook account: %v", err) 96 + } 97 + 98 + log.Printf("Account %v created successfully.", *name) 99 + }
+21 -6
server/internal/service/user.go
··· 12 12 13 13 "google.golang.org/grpc/codes" 14 14 "google.golang.org/grpc/metadata" 15 + "google.golang.org/grpc/peer" 15 16 "google.golang.org/grpc/status" 16 17 "google.golang.org/protobuf/types/known/emptypb" 17 18 "google.golang.org/protobuf/types/known/timestamppb" ··· 23 24 24 25 type UserServiceServer struct { 25 26 pb.UnimplementedUserServiceServer 26 - users store.UserRepository 27 - sessions store.SessionRepository 28 - authURL string 27 + users store.UserRepository 28 + sessions store.SessionRepository 29 + authURL string 30 + noRegister bool 29 31 } 30 32 31 - func NewUserServiceServer(users store.UserRepository, sessions store.SessionRepository, authURL string) *UserServiceServer { 33 + func NewUserServiceServer(users store.UserRepository, sessions store.SessionRepository, authURL string, noRegister bool) *UserServiceServer { 32 34 if authURL != "" && !strings.Contains(authURL, "://") { 33 35 authURL = "http://" + authURL 34 36 } 35 - return &UserServiceServer{users: users, sessions: sessions, authURL: authURL} 37 + return &UserServiceServer{users: users, sessions: sessions, authURL: authURL, noRegister: noRegister} 36 38 } 37 39 38 40 func (s *UserServiceServer) RegisterUser(ctx context.Context, req *pb.RegisterUserRequest) (*pb.RegisterUserResponse, error) { 41 + if s.noRegister { 42 + ip := "invalid" 43 + 44 + if p, ok := peer.FromContext(ctx); ok { 45 + ip = p.Addr.String() 46 + } 47 + 48 + return nil, fmt.Errorf("Denied user registration: ip=%s uuid=%s", ip, req.Uuid) 49 + } 50 + 39 51 platform := model.ClientPlatformFromContext(ctx) 40 52 userId, err := s.users.CreateUser(req.Uuid, platform) 41 53 if err != nil { ··· 89 101 90 102 func (s *UserServiceServer) TransferUser(ctx context.Context, req *pb.TransferUserRequest) (*pb.TransferUserResponse, error) { 91 103 platform := model.ClientPlatformFromContext(ctx) 104 + 92 105 log.Printf("[UserService] TransferUser: platform=%s", platform) 93 - userId, err := s.users.CreateUser(req.Uuid, platform) 106 + 107 + userId, err := s.users.GetUserByUUID(req.Uuid) 94 108 if err != nil { 95 109 return nil, fmt.Errorf("create user: %w", err) 96 110 } 111 + 97 112 return &pb.TransferUserResponse{ 98 113 UserId: userId, 99 114 Signature: "transferred-sig",
-6
server/internal/store/sqlite/user.go
··· 15 15 } 16 16 defer tx.Rollback() 17 17 18 - var existingId int64 19 - err = tx.QueryRow(`SELECT user_id FROM users WHERE uuid = ?`, uuid).Scan(&existingId) 20 - if err == nil { 21 - return existingId, nil 22 - } 23 - 24 18 nowMillis := s.clock().UnixMilli() 25 19 26 20 res, err := tx.Exec(`INSERT INTO users (uuid, player_id, os_type, platform_type, user_restriction_type,