commit 6153ccee140c80fa261a4e9ca7aceca072948370 Author: xmdhs Date: Fri Sep 1 21:07:22 2023 +0800 first commit diff --git a/db/mysql/db.go b/db/mysql/db.go new file mode 100644 index 0000000..5240067 --- /dev/null +++ b/db/mysql/db.go @@ -0,0 +1,116 @@ +// Code generated by sqlc. DO NOT EDIT. + +package mysql + +import ( + "context" + "database/sql" + "fmt" +) + +type DBTX interface { + ExecContext(context.Context, string, ...interface{}) (sql.Result, error) + PrepareContext(context.Context, string) (*sql.Stmt, error) + QueryContext(context.Context, string, ...interface{}) (*sql.Rows, error) + QueryRowContext(context.Context, string, ...interface{}) *sql.Row +} + +func New(db DBTX) *Queries { + return &Queries{db: db} +} + +func Prepare(ctx context.Context, db DBTX) (*Queries, error) { + q := Queries{db: db} + var err error + if q.createUserStmt, err = db.PrepareContext(ctx, createUser); err != nil { + return nil, fmt.Errorf("error preparing query CreateUser: %w", err) + } + if q.deleteUserStmt, err = db.PrepareContext(ctx, deleteUser); err != nil { + return nil, fmt.Errorf("error preparing query DeleteUser: %w", err) + } + if q.getUserStmt, err = db.PrepareContext(ctx, getUser); err != nil { + return nil, fmt.Errorf("error preparing query GetUser: %w", err) + } + if q.listUserStmt, err = db.PrepareContext(ctx, listUser); err != nil { + return nil, fmt.Errorf("error preparing query ListUser: %w", err) + } + return &q, nil +} + +func (q *Queries) Close() error { + var err error + if q.createUserStmt != nil { + if cerr := q.createUserStmt.Close(); cerr != nil { + err = fmt.Errorf("error closing createUserStmt: %w", cerr) + } + } + if q.deleteUserStmt != nil { + if cerr := q.deleteUserStmt.Close(); cerr != nil { + err = fmt.Errorf("error closing deleteUserStmt: %w", cerr) + } + } + if q.getUserStmt != nil { + if cerr := q.getUserStmt.Close(); cerr != nil { + err = fmt.Errorf("error closing getUserStmt: %w", cerr) + } + } + if q.listUserStmt != nil { + if cerr := q.listUserStmt.Close(); cerr != nil { + err = fmt.Errorf("error closing listUserStmt: %w", cerr) + } + } + return err +} + +func (q *Queries) exec(ctx context.Context, stmt *sql.Stmt, query string, args ...interface{}) (sql.Result, error) { + switch { + case stmt != nil && q.tx != nil: + return q.tx.StmtContext(ctx, stmt).ExecContext(ctx, args...) + case stmt != nil: + return stmt.ExecContext(ctx, args...) + default: + return q.db.ExecContext(ctx, query, args...) + } +} + +func (q *Queries) query(ctx context.Context, stmt *sql.Stmt, query string, args ...interface{}) (*sql.Rows, error) { + switch { + case stmt != nil && q.tx != nil: + return q.tx.StmtContext(ctx, stmt).QueryContext(ctx, args...) + case stmt != nil: + return stmt.QueryContext(ctx, args...) + default: + return q.db.QueryContext(ctx, query, args...) + } +} + +func (q *Queries) queryRow(ctx context.Context, stmt *sql.Stmt, query string, args ...interface{}) *sql.Row { + switch { + case stmt != nil && q.tx != nil: + return q.tx.StmtContext(ctx, stmt).QueryRowContext(ctx, args...) + case stmt != nil: + return stmt.QueryRowContext(ctx, args...) + default: + return q.db.QueryRowContext(ctx, query, args...) + } +} + +type Queries struct { + db DBTX + tx *sql.Tx + createUserStmt *sql.Stmt + deleteUserStmt *sql.Stmt + getUserStmt *sql.Stmt + listUserStmt *sql.Stmt +} + +func (q *Queries) WithTx(tx *sql.Tx) *Queries { + return &Queries{ + db: tx, + tx: tx, + createUserStmt: q.createUserStmt, + deleteUserStmt: q.deleteUserStmt, + getUserStmt: q.getUserStmt, + listUserStmt: q.listUserStmt, + } +} diff --git a/db/mysql/models.go b/db/mysql/models.go new file mode 100644 index 0000000..bb85e9e --- /dev/null +++ b/db/mysql/models.go @@ -0,0 +1,22 @@ +// Code generated by sqlc. DO NOT EDIT. + +package mysql + +import () + +type Skin struct { + UserID int64 `db:"user_id"` + SkinHash string `db:"skin_hash"` + Type string `db:"type"` + Variant string `db:"variant"` +} + +type User struct { + ID int64 `db:"id"` + Email string `db:"email"` + Password string `db:"password"` + Salt string `db:"salt"` + Disabled int32 `db:"disabled"` + Admin int32 `db:"admin"` + RegTime int64 `db:"reg_time"` +} diff --git a/db/mysql/querier.go b/db/mysql/querier.go new file mode 100644 index 0000000..6348d4d --- /dev/null +++ b/db/mysql/querier.go @@ -0,0 +1,17 @@ +// Code generated by sqlc. DO NOT EDIT. + +package mysql + +import ( + "context" + "database/sql" +) + +type Querier interface { + CreateUser(ctx context.Context, arg CreateUserParams) (sql.Result, error) + DeleteUser(ctx context.Context, id int64) error + GetUser(ctx context.Context, id int64) (User, error) + ListUser(ctx context.Context) ([]User, error) +} + +var _ Querier = (*Queries)(nil) diff --git a/db/mysql/query.sql.go b/db/mysql/query.sql.go new file mode 100644 index 0000000..a3b028f --- /dev/null +++ b/db/mysql/query.sql.go @@ -0,0 +1,124 @@ +// Code generated by sqlc. DO NOT EDIT. +// source: query.sql + +package mysql + +import ( + "context" + "database/sql" +) + +const createUser = `-- name: CreateUser :execresult +INSERT INTO + user ( + id, + email, + password, + salt, + disabled, + admin, + reg_time + ) +VALUES + (?, ?, ?, ?, ?, ?, ?) +` + +type CreateUserParams struct { + ID int64 `db:"id"` + Email string `db:"email"` + Password string `db:"password"` + Salt string `db:"salt"` + Disabled int32 `db:"disabled"` + Admin int32 `db:"admin"` + RegTime int64 `db:"reg_time"` +} + +func (q *Queries) CreateUser(ctx context.Context, arg CreateUserParams) (sql.Result, error) { + return q.exec(ctx, q.createUserStmt, createUser, + arg.ID, + arg.Email, + arg.Password, + arg.Salt, + arg.Disabled, + arg.Admin, + arg.RegTime, + ) +} + +const deleteUser = `-- name: DeleteUser :exec +DELETE FROM + user +WHERE + id = ? +` + +func (q *Queries) DeleteUser(ctx context.Context, id int64) error { + _, err := q.exec(ctx, q.deleteUserStmt, deleteUser, id) + return err +} + +const getUser = `-- name: GetUser :one +SELECT + id, email, password, salt, disabled, admin, reg_time +FROM + user +WHERE + id = ? +LIMIT + 1 +` + +func (q *Queries) GetUser(ctx context.Context, id int64) (User, error) { + row := q.queryRow(ctx, q.getUserStmt, getUser, id) + var i User + err := row.Scan( + &i.ID, + &i.Email, + &i.Password, + &i.Salt, + &i.Disabled, + &i.Admin, + &i.RegTime, + ) + return i, err +} + +const listUser = `-- name: ListUser :many +SELECT + id, email, password, salt, disabled, admin, reg_time +FROM + user +ORDER BY + reg_time +` + +func (q *Queries) ListUser(ctx context.Context) ([]User, error) { + rows, err := q.query(ctx, q.listUserStmt, listUser) + if err != nil { + return nil, err + } + defer rows.Close() + var items []User + for rows.Next() { + var i User + if err := rows.Scan( + &i.ID, + &i.Email, + &i.Password, + &i.Salt, + &i.Disabled, + &i.Admin, + &i.RegTime, + ); 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 +} diff --git a/db/mysql/sql/query.sql b/db/mysql/sql/query.sql new file mode 100644 index 0000000..8424210 --- /dev/null +++ b/db/mysql/sql/query.sql @@ -0,0 +1,37 @@ +-- name: GetUser :one +SELECT + * +FROM + user +WHERE + id = ? +LIMIT + 1; + +-- name: ListUser :many +SELECT + * +FROM + user +ORDER BY + reg_time; + +-- name: CreateUser :execresult +INSERT INTO + user ( + id, + email, + password, + salt, + disabled, + admin, + reg_time + ) +VALUES + (?, ?, ?, ?, ?, ?, ?); + +-- name: DeleteUser :exec +DELETE FROM + user +WHERE + id = ?; \ No newline at end of file diff --git a/db/mysql/sql/schema.sql b/db/mysql/sql/schema.sql new file mode 100644 index 0000000..2e9da0d --- /dev/null +++ b/db/mysql/sql/schema.sql @@ -0,0 +1,33 @@ +CREATE TABLE IF NOT EXISTS `user` ( + id BIGINT PRIMARY KEY, + email VARCHAR(20) NOT NULL, + password text NOT NULL, + salt text NOT NULL, + state INT NOT NULL, + reg_time BIGINT NOT NULL +); + +CREATE TABLE IF NOT EXISTS `skin` ( + id BIGINT PRIMARY KEY, + -- 第一个上传的用户 + user_id BIGINT NOT NULL, + skin_hash VARCHAR(50) NOT NULL, + `type` VARCHAR(10) NOT NULL, + variant VARCHAR(10) NOT NULL +); + +CREATE TABLE IF NOT EXISTS `user_skin` ( + user_id BIGINT PRIMARY KEY, + skin_id BIGINT NOT NULL +); + +CREATE TABLE IF NOT EXISTS `user_token` ( + user_id BIGINT PRIMARY KEY, + token_id INT NOT NULL +); + +CREATE TABLE IF NOT EXISTS `user_profile` ( + user_id BIGINT PRIMARY KEY, + name VARCHAR(20) NOT NULL, + uuid text NOT NULL +); \ No newline at end of file diff --git a/db/mysql/sqlc.go b/db/mysql/sqlc.go new file mode 100644 index 0000000..a64604b --- /dev/null +++ b/db/mysql/sqlc.go @@ -0,0 +1,2 @@ +//go:generate sqlc generate +package mysql diff --git a/db/mysql/sqlc.yaml b/db/mysql/sqlc.yaml new file mode 100644 index 0000000..9f37069 --- /dev/null +++ b/db/mysql/sqlc.yaml @@ -0,0 +1,12 @@ +version: 2 +sql: + - engine: "mysql" + schema: "sql/schema.sql" + queries: "sql/query.sql" + gen: + go: + package: "mysql" + out: "." + emit_db_tags: true + emit_prepared_queries: true + emit_interface: true diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..c68817a --- /dev/null +++ b/go.mod @@ -0,0 +1,19 @@ +module github.com/xmdhs/authlib-skin + +go 1.21.0 + +require ( + github.com/go-playground/validator/v10 v10.15.3 + github.com/julienschmidt/httprouter v1.3.0 +) + +require ( + github.com/gabriel-vasile/mimetype v1.4.2 // indirect + github.com/go-playground/locales v0.14.1 // indirect + github.com/go-playground/universal-translator v0.18.1 // indirect + github.com/leodido/go-urn v1.2.4 // indirect + golang.org/x/crypto v0.7.0 // indirect + golang.org/x/net v0.8.0 // indirect + golang.org/x/sys v0.6.0 // indirect + golang.org/x/text v0.8.0 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..11841cc --- /dev/null +++ b/go.sum @@ -0,0 +1,38 @@ +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/gabriel-vasile/mimetype v1.4.2 h1:w5qFW6JKBz9Y393Y4q372O9A7cUSequkh1Q7OhCmWKU= +github.com/gabriel-vasile/mimetype v1.4.2/go.mod h1:zApsH/mKG4w07erKIaJPFiX0Tsq9BFQgN3qGY5GnNgA= +github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= +github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= +github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= +github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= +github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= +github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= +github.com/go-playground/validator/v10 v10.15.3 h1:S+sSpunYjNPDuXkWbK+x+bA7iXiW296KG4dL3X7xUZo= +github.com/go-playground/validator/v10 v10.15.3/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU= +github.com/julienschmidt/httprouter v1.3.0 h1:U0609e9tgbseu3rBINet9P48AI/D3oJs4dN7jwJOQ1U= +github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= +github.com/leodido/go-urn v1.2.4 h1:XlAE/cm/ms7TE/VMVoduSpNBoyc2dOxHs5MZSwAN63Q= +github.com/leodido/go-urn v1.2.4/go.mod h1:7ZrI8mTSeBSHl/UaRyKQW1qZeMgak41ANeCNaVckg+4= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/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.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8= +github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +golang.org/x/crypto v0.7.0 h1:AvwMYaRytfdeVt3u6mLaxYtErKYjxA2OXjJ1HHq6t3A= +golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU= +golang.org/x/net v0.8.0 h1:Zrh2ngAOFYneWTAIAPethzeaQLuHwhuBkuV6ZiRnUaQ= +golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc= +golang.org/x/sys v0.6.0 h1:MVltZSvRTcU2ljQOhs94SXPftV6DCNnZViHeQps87pQ= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/text v0.8.0 h1:57P1ETyNKtuIjB4SRd15iJxuhj8Gc416Y78H3qgMh68= +golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= +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= diff --git a/handle/error.go b/handle/error.go new file mode 100644 index 0000000..644eb82 --- /dev/null +++ b/handle/error.go @@ -0,0 +1,18 @@ +package handle + +import ( + "context" + "encoding/json" + "net/http" + + "github.com/xmdhs/authlib-skin/model" +) + +func handleError(ctx context.Context, w http.ResponseWriter, msg string, code int, httpcode int) { + w.WriteHeader(httpcode) + b, err := json.Marshal(model.API[any]{Code: code, Msg: msg, Data: nil}) + if err != nil { + panic(err) + } + w.Write(b) +} diff --git a/handle/user.go b/handle/user.go new file mode 100644 index 0000000..944bf07 --- /dev/null +++ b/handle/user.go @@ -0,0 +1 @@ +package handle diff --git a/handle/yggdrasil/authenticate.go b/handle/yggdrasil/authenticate.go new file mode 100644 index 0000000..e9fb0fa --- /dev/null +++ b/handle/yggdrasil/authenticate.go @@ -0,0 +1,33 @@ +package yggdrasil + +import ( + "encoding/json" + "log/slog" + "net/http" + + "github.com/go-playground/validator/v10" + "github.com/julienschmidt/httprouter" + "github.com/xmdhs/authlib-skin/model/yggdrasil" +) + +func Authenticate(l *slog.Logger, v *validator.Validate) httprouter.Handle { + return func(w http.ResponseWriter, r *http.Request, p httprouter.Params) { + cxt := r.Context() + jr := json.NewDecoder(r.Body) + var a yggdrasil.Authenticate + err := jr.Decode(&a) + if err != nil { + l.Info(err.Error()) + handleYgError(cxt, w, yggdrasil.Error{ErrorMessage: err.Error()}, 400) + return + } + + err = v.Struct(a) + if err != nil { + l.Info(err.Error()) + handleYgError(cxt, w, yggdrasil.Error{ErrorMessage: err.Error()}, 400) + return + } + + } +} diff --git a/handle/yggdrasil/error.go b/handle/yggdrasil/error.go new file mode 100644 index 0000000..db4571c --- /dev/null +++ b/handle/yggdrasil/error.go @@ -0,0 +1,18 @@ +package yggdrasil + +import ( + "context" + "encoding/json" + "net/http" + + "github.com/xmdhs/authlib-skin/model/yggdrasil" +) + +func handleYgError(ctx context.Context, w http.ResponseWriter, e yggdrasil.Error, httpcode int) { + w.WriteHeader(httpcode) + b, err := json.Marshal(e) + if err != nil { + panic(err) + } + w.Write(b) +} diff --git a/model/const.go b/model/const.go new file mode 100644 index 0000000..05d35f3 --- /dev/null +++ b/model/const.go @@ -0,0 +1,5 @@ +package model + +const ( + OK = 0 +) diff --git a/model/model.go b/model/model.go new file mode 100644 index 0000000..ba0a8f4 --- /dev/null +++ b/model/model.go @@ -0,0 +1,7 @@ +package model + +type API[T any] struct { + Code int `json:"code"` + Data T `json:"data"` + Msg string `json:"msg"` +} diff --git a/model/yggdrasil/model.go b/model/yggdrasil/model.go new file mode 100644 index 0000000..03962d0 --- /dev/null +++ b/model/yggdrasil/model.go @@ -0,0 +1,18 @@ +package yggdrasil + +type Authenticate struct { + Agent struct { + Name string `json:"name" validate:"required,eq=Minecraft"` + Version int `json:"version" validate:"required,eq=1"` + } `json:"agent"` + ClientToken string `json:"clientToken"` + Password string `json:"password" validate:"required"` + RequestUser bool `json:"requestUser"` + Username string `json:"username" validate:"required"` +} + +type Error struct { + Cause string `json:"cause,omitempty"` + Error string `json:"error,omitempty"` + ErrorMessage string `json:"errorMessage,omitempty"` +} diff --git a/server/server.go b/server/server.go new file mode 100644 index 0000000..5176802 --- /dev/null +++ b/server/server.go @@ -0,0 +1,20 @@ +package server + +import ( + "net/http" + + "github.com/julienschmidt/httprouter" +) + +func NewYggdrasil(r *httprouter.Router) error { + + r.POST("/api/authserver/authenticate", nil) + return nil +} + +func warpCtJSON(handle httprouter.Handle) httprouter.Handle { + return func(w http.ResponseWriter, r *http.Request, p httprouter.Params) { + w.Header().Set("Content-Type", "application/json; charset=utf-8") + handle(w, r, p) + } +}