aboutsummaryrefslogtreecommitdiffhomepage
path: root/backend
diff options
context:
space:
mode:
authornsfisis <nsfisis@gmail.com>2024-08-09 23:30:39 +0900
committernsfisis <nsfisis@gmail.com>2024-08-09 23:30:39 +0900
commit41e98f3b0a936f7982434b74a88b919b99fd94ce (patch)
tree72e1a3cc00044f9c00dc5f22ef9325124f272401 /backend
parent93f2ffc18d1d86bd2999533e8d904c92cb29bc1a (diff)
parentaf36c59851399194bcbb77a3093d46c2757cb7b4 (diff)
downloadiosdc-japan-2024-albatross-41e98f3b0a936f7982434b74a88b919b99fd94ce.tar.gz
iosdc-japan-2024-albatross-41e98f3b0a936f7982434b74a88b919b99fd94ce.tar.zst
iosdc-japan-2024-albatross-41e98f3b0a936f7982434b74a88b919b99fd94ce.zip
Merge branch 'feat/auth-fortee'
Diffstat (limited to 'backend')
-rw-r--r--backend/api/generated.go57
-rw-r--r--backend/api/handler.go5
-rw-r--r--backend/auth/auth.go109
-rw-r--r--backend/db/models.go5
-rw-r--r--backend/db/query.sql.go42
-rw-r--r--backend/fixtures/dev.sql3
-rw-r--r--backend/query.sql15
-rw-r--r--backend/schema.sql6
8 files changed, 211 insertions, 31 deletions
diff --git a/backend/api/generated.go b/backend/api/generated.go
index 3c52f84..33f1a78 100644
--- a/backend/api/generated.go
+++ b/backend/api/generated.go
@@ -257,8 +257,9 @@ type GetGameParams struct {
// PostLoginJSONBody defines parameters for PostLogin.
type PostLoginJSONBody struct {
- Password string `json:"password"`
- Username string `json:"username"`
+ Password string `json:"password"`
+ RegistrationToken *string `json:"registration_token,omitempty"`
+ Username string `json:"username"`
}
// GetTokenParams defines parameters for GetToken.
@@ -1106,32 +1107,32 @@ func (sh *strictHandler) GetToken(ctx echo.Context, params GetTokenParams) error
// Base64 encoded, gzipped, json marshaled Swagger object
var swaggerSpec = []string{
- "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",
- "G+PQ1yEAAA==",
+ "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",
+ "BmCWZdm/AQAA//8u6d2yGyIAAA==",
}
// GetSwagger returns the content of the embedded swagger specification file
diff --git a/backend/api/handler.go b/backend/api/handler.go
index 57d7464..23c3cfe 100644
--- a/backend/api/handler.go
+++ b/backend/api/handler.go
@@ -3,6 +3,7 @@ package api
import (
"context"
"errors"
+ "log"
"net/http"
"github.com/jackc/pgx/v5"
@@ -24,8 +25,10 @@ type GameHubsInterface interface {
func (h *Handler) PostLogin(ctx context.Context, request PostLoginRequestObject) (PostLoginResponseObject, error) {
username := request.Body.Username
password := request.Body.Password
- userID, err := auth.Login(ctx, h.q, username, password)
+ registrationToken := request.Body.RegistrationToken
+ userID, err := auth.Login(ctx, h.q, username, password, registrationToken)
if err != nil {
+ log.Printf("login failed: %v", err)
return PostLogin401JSONResponse{
UnauthorizedJSONResponse: UnauthorizedJSONResponse{
Message: "Invalid username or password",
diff --git a/backend/auth/auth.go b/backend/auth/auth.go
index 401773f..3a292d9 100644
--- a/backend/auth/auth.go
+++ b/backend/auth/auth.go
@@ -1,17 +1,42 @@
package auth
import (
+ "bytes"
"context"
+ "encoding/json"
+ "errors"
"fmt"
+ "net/http"
+ "net/url"
+ "github.com/jackc/pgx/v5"
"golang.org/x/crypto/bcrypt"
"github.com/nsfisis/iosdc-japan-2024-albatross/backend/db"
)
-func Login(ctx context.Context, queries *db.Queries, username, password string) (int, error) {
+var (
+ ErrInvalidRegistrationToken = errors.New("invalid registration token")
+ ErrNoRegistrationToken = errors.New("no registration token")
+ ErrForteeLoginFailed = errors.New("fortee login failed")
+)
+
+func Login(
+ ctx context.Context,
+ queries *db.Queries,
+ username string,
+ password string,
+ registrationToken *string,
+) (int, error) {
userAuth, err := queries.GetUserAuthByUsername(ctx, username)
if err != nil {
+ if errors.Is(err, pgx.ErrNoRows) {
+ err := signup(ctx, queries, username, password, registrationToken)
+ if err != nil {
+ return 0, err
+ }
+ return Login(ctx, queries, username, password, nil)
+ }
return 0, err
}
if userAuth.AuthType == "password" {
@@ -24,6 +49,86 @@ func Login(ctx context.Context, queries *db.Queries, username, password string)
return 0, err
}
return int(userAuth.UserID), nil
+ } else if userAuth.AuthType == "fortee" {
+ if err := verifyForteeAccount(ctx, username, password); err != nil {
+ return 0, err
+ }
+ return int(userAuth.UserID), nil
+ }
+ panic(fmt.Sprintf("unexpected auth type: %s", userAuth.AuthType))
+}
+
+func signup(
+ ctx context.Context,
+ queries *db.Queries,
+ username string,
+ password string,
+ registrationToken *string,
+) error {
+ if err := verifyRegistrationToken(ctx, queries, registrationToken); err != nil {
+ return err
+ }
+ if err := verifyForteeAccount(ctx, username, password); err != nil {
+ return err
+ }
+
+ // TODO: transaction
+ userID, err := queries.CreateUser(ctx, username)
+ if err != nil {
+ return err
+ }
+ if err := queries.CreateUserAuth(ctx, db.CreateUserAuthParams{
+ UserID: userID,
+ AuthType: "fortee",
+ }); err != nil {
+ return err
+ }
+ return nil
+}
+
+func verifyRegistrationToken(ctx context.Context, queries *db.Queries, registrationToken *string) error {
+ if registrationToken == nil {
+ return ErrNoRegistrationToken
+ }
+ exists, err := queries.IsRegistrationTokenValid(ctx, *registrationToken)
+ if err != nil {
+ return err
+ }
+ if !exists {
+ return ErrInvalidRegistrationToken
+ }
+ return nil
+}
+
+func verifyForteeAccount(_ context.Context, username string, password string) error {
+ reqData := url.Values{}
+ reqData.Set("username", username)
+ reqData.Set("password", password)
+ reqBody := reqData.Encode()
+
+ req, err := http.NewRequest("POST", "https://fortee.jp/api/user/login", bytes.NewBufferString(reqBody))
+ if err != nil {
+ return fmt.Errorf("http.NewRequest failed: %w", err)
+ }
+ req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
+ req.Header.Set("Accept", "application/json")
+
+ client := &http.Client{}
+ res, err := client.Do(req)
+ if err != nil {
+ return fmt.Errorf("client.Do failed: %v", err)
+ }
+ defer res.Body.Close()
+
+ resData := struct {
+ LoggedIn bool `json:"loggedIn"`
+ }{}
+ if err := json.NewDecoder(res.Body).Decode(&resData); err != nil {
+ return fmt.Errorf("json.Decode failed: %v", err)
+ }
+
+ if !resData.LoggedIn {
+ return ErrForteeLoginFailed
}
- return 0, fmt.Errorf("not implemented")
+ return nil
}
diff --git a/backend/db/models.go b/backend/db/models.go
index 5bca7b7..800c183 100644
--- a/backend/db/models.go
+++ b/backend/db/models.go
@@ -30,6 +30,11 @@ type Problem struct {
Description string
}
+type RegistrationToken struct {
+ RegistrationTokenID int32
+ Token string
+}
+
type Submission struct {
SubmissionID int32
GameID int32
diff --git a/backend/db/query.sql.go b/backend/db/query.sql.go
index dc87602..47140fc 100644
--- a/backend/db/query.sql.go
+++ b/backend/db/query.sql.go
@@ -106,6 +106,34 @@ func (q *Queries) CreateTestcaseResult(ctx context.Context, arg CreateTestcaseRe
return err
}
+const createUser = `-- name: CreateUser :one
+INSERT INTO users (username, display_name, is_admin)
+VALUES ($1, $1, false)
+RETURNING user_id
+`
+
+func (q *Queries) CreateUser(ctx context.Context, username string) (int32, error) {
+ row := q.db.QueryRow(ctx, createUser, username)
+ var user_id int32
+ err := row.Scan(&user_id)
+ return user_id, err
+}
+
+const createUserAuth = `-- name: CreateUserAuth :exec
+INSERT INTO user_auths (user_id, auth_type)
+VALUES ($1, $2)
+`
+
+type CreateUserAuthParams struct {
+ UserID int32
+ AuthType string
+}
+
+func (q *Queries) CreateUserAuth(ctx context.Context, arg CreateUserAuthParams) error {
+ _, err := q.db.Exec(ctx, createUserAuth, arg.UserID, arg.AuthType)
+ return err
+}
+
const getGameByID = `-- name: GetGameByID :one
SELECT game_id, game_type, state, display_name, duration_seconds, created_at, started_at, games.problem_id, problems.problem_id, title, description FROM games
LEFT JOIN problems ON games.problem_id = problems.problem_id
@@ -204,6 +232,20 @@ func (q *Queries) GetUserByID(ctx context.Context, userID int32) (User, error) {
return i, err
}
+const isRegistrationTokenValid = `-- name: IsRegistrationTokenValid :one
+SELECT EXISTS (
+ SELECT 1 FROM registration_tokens
+ WHERE token = $1
+)
+`
+
+func (q *Queries) IsRegistrationTokenValid(ctx context.Context, token string) (bool, error) {
+ row := q.db.QueryRow(ctx, isRegistrationTokenValid, token)
+ var exists bool
+ err := row.Scan(&exists)
+ return exists, err
+}
+
const listGamePlayers = `-- name: ListGamePlayers :many
SELECT game_id, game_players.user_id, users.user_id, username, display_name, icon_path, is_admin, created_at FROM game_players
LEFT JOIN users ON game_players.user_id = users.user_id
diff --git a/backend/fixtures/dev.sql b/backend/fixtures/dev.sql
index 2daa8f5..9ba7f73 100644
--- a/backend/fixtures/dev.sql
+++ b/backend/fixtures/dev.sql
@@ -12,6 +12,9 @@ VALUES
(2, 'password', '$2a$10$4Wl1M4jQs.GwkB4oT32KvuMQtF.EdqKuOc8z8KKOupnuMJRAVk32W'),
(3, 'password', '$2a$10$F/TePpu1pyJRWgn0e6A14.VL9D/17sRxT/2DyZ2Oi4Eg/lR6n7JcK');
+INSERT INTO registration_tokens (token)
+VALUES ('shah3Iheix6cheig');
+
INSERT INTO problems
(title, description)
VALUES
diff --git a/backend/query.sql b/backend/query.sql
index f0e4034..13bbbe6 100644
--- a/backend/query.sql
+++ b/backend/query.sql
@@ -3,6 +3,11 @@ SELECT * FROM users
WHERE users.user_id = $1
LIMIT 1;
+-- name: CreateUser :one
+INSERT INTO users (username, display_name, is_admin)
+VALUES ($1, $1, false)
+RETURNING user_id;
+
-- name: ListUsers :many
SELECT * FROM users
ORDER BY users.user_id;
@@ -13,6 +18,16 @@ JOIN user_auths ON users.user_id = user_auths.user_id
WHERE users.username = $1
LIMIT 1;
+-- name: CreateUserAuth :exec
+INSERT INTO user_auths (user_id, auth_type)
+VALUES ($1, $2);
+
+-- name: IsRegistrationTokenValid :one
+SELECT EXISTS (
+ SELECT 1 FROM registration_tokens
+ WHERE token = $1
+);
+
-- name: ListGames :many
SELECT * FROM games
LEFT JOIN problems ON games.problem_id = problems.problem_id
diff --git a/backend/schema.sql b/backend/schema.sql
index 64642bc..2779eaf 100644
--- a/backend/schema.sql
+++ b/backend/schema.sql
@@ -17,6 +17,12 @@ CREATE TABLE user_auths (
);
CREATE INDEX idx_user_auths_user_id ON user_auths(user_id);
+CREATE TABLE registration_tokens (
+ registration_token_id SERIAL PRIMARY KEY,
+ token CHAR(16) NOT NULL
+);
+CREATE INDEX idx_registration_tokens_token ON registration_tokens(token);
+
CREATE TABLE problems (
problem_id SERIAL PRIMARY KEY,
title VARCHAR(255) NOT NULL,