diff options
| -rw-r--r-- | backend/auth/auth.go | 74 | ||||
| -rw-r--r-- | backend/auth/fortee/fortee.go | 18 | ||||
| -rw-r--r-- | backend/auth/fortee/generated.go | 6 | ||||
| -rw-r--r-- | backend/db/query.sql.go | 13 | ||||
| -rw-r--r-- | backend/query.sql | 5 | ||||
| -rw-r--r-- | frontend/app/routes/login.tsx | 12 | ||||
| -rw-r--r-- | openapi/fortee.yaml | 8 |
7 files changed, 83 insertions, 53 deletions
diff --git a/backend/auth/auth.go b/backend/auth/auth.go index 4224675..0e55d8d 100644 --- a/backend/auth/auth.go +++ b/backend/auth/auth.go @@ -3,8 +3,6 @@ package auth import ( "context" "errors" - "fmt" - "strings" "time" "github.com/jackc/pgx/v5" @@ -18,7 +16,6 @@ var ( ErrInvalidRegistrationToken = errors.New("invalid registration token") ErrNoRegistrationToken = errors.New("no registration token") ErrForteeLoginTimeout = errors.New("fortee login timeout") - ErrForteeEmailUsed = errors.New("fortee email used") ) const ( @@ -33,17 +30,12 @@ func Login( 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) - } + if err != nil && !errors.Is(err, pgx.ErrNoRows) { return 0, err } + if userAuth.AuthType == "password" { + // Authenticate with password. passwordHash := userAuth.PasswordHash if passwordHash == nil { panic("inconsistant data") @@ -53,41 +45,60 @@ func Login( return 0, err } return int(userAuth.UserID), nil - } else if userAuth.AuthType == "fortee" { - if err := verifyForteeAccount(ctx, username, password); err != nil { - return 0, err + } + + // Authenticate with fortee. + return verifyForteeAccountOrSignup(ctx, queries, username, password, registrationToken) +} + +func verifyForteeAccountOrSignup( + ctx context.Context, + queries *db.Queries, + username string, + password string, + registrationToken *string, +) (int, error) { + canonicalizedUsername, err := verifyForteeAccount(ctx, username, password) + if err != nil { + return 0, err + } + userID, err := queries.GetUserIDByUsername(ctx, canonicalizedUsername) + if err != nil { + if errors.Is(err, pgx.ErrNoRows) { + return signup( + ctx, + queries, + canonicalizedUsername, + registrationToken, + ) } - return int(userAuth.UserID), nil + return 0, err } - panic(fmt.Sprintf("unexpected auth type: %s", userAuth.AuthType)) + return int(userID), nil } func signup( ctx context.Context, queries *db.Queries, username string, - password string, registrationToken *string, -) error { +) (int, error) { if err := verifyRegistrationToken(ctx, queries, registrationToken); err != nil { - return err - } - if err := verifyForteeAccount(ctx, username, password); err != nil { - return err + return 0, err } // TODO: transaction userID, err := queries.CreateUser(ctx, username) if err != nil { - return err + return 0, err } if err := queries.CreateUserAuth(ctx, db.CreateUserAuthParams{ UserID: userID, AuthType: "fortee", }); err != nil { - return err + return 0, err } - return nil + return int(userID), nil } func verifyRegistrationToken(ctx context.Context, queries *db.Queries, registrationToken *string) error { @@ -104,18 +115,13 @@ func verifyRegistrationToken(ctx context.Context, queries *db.Queries, registrat return nil } -func verifyForteeAccount(ctx context.Context, username string, password string) error { - // fortee API allows login with email address, but this system disallows it. - if strings.Contains(username, "@") { - return ErrForteeEmailUsed - } - +func verifyForteeAccount(ctx context.Context, username string, password string) (string, error) { ctx, cancel := context.WithTimeout(ctx, forteeAPITimeout) defer cancel() - err := fortee.LoginFortee(ctx, username, password) + canonicalizedUsername, err := fortee.LoginFortee(ctx, username, password) if errors.Is(err, context.DeadlineExceeded) { - return ErrForteeLoginTimeout + return "", ErrForteeLoginTimeout } - return err + return canonicalizedUsername, err } diff --git a/backend/auth/fortee/fortee.go b/backend/auth/fortee/fortee.go index 7f9d816..25ca9c5 100644 --- a/backend/auth/fortee/fortee.go +++ b/backend/auth/fortee/fortee.go @@ -14,25 +14,29 @@ var ( ErrLoginFailed = errors.New("fortee login failed") ) -func LoginFortee(ctx context.Context, username string, password string) error { +func LoginFortee(ctx context.Context, username string, password string) (string, error) { client, err := NewClientWithResponses(apiEndpoint, WithRequestEditorFn(addAcceptHeader)) if err != nil { - return err + return "", err } res, err := client.PostLoginWithFormdataBodyWithResponse(ctx, PostLoginFormdataRequestBody{ Username: username, Password: password, }) if err != nil { - return err + return "", err } if res.StatusCode() != http.StatusOK { - return ErrLoginFailed + return "", ErrLoginFailed } - if !res.JSON200.LoggedIn { - return ErrLoginFailed + resOk := res.JSON200 + if !resOk.LoggedIn { + return "", ErrLoginFailed } - return nil + if resOk.User == nil { + return "", ErrLoginFailed + } + return resOk.User.Username, nil } // fortee API denies requests without Accept header. diff --git a/backend/auth/fortee/generated.go b/backend/auth/fortee/generated.go index e2fd920..53529f9 100644 --- a/backend/auth/fortee/generated.go +++ b/backend/auth/fortee/generated.go @@ -221,6 +221,9 @@ type PostLoginResponse struct { HTTPResponse *http.Response JSON200 *struct { LoggedIn bool `json:"loggedIn"` + User *struct { + Username string `json:"username"` + } `json:"user,omitempty"` } } @@ -274,6 +277,9 @@ func ParsePostLoginResponse(rsp *http.Response) (*PostLoginResponse, error) { case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: var dest struct { LoggedIn bool `json:"loggedIn"` + User *struct { + Username string `json:"username"` + } `json:"user,omitempty"` } if err := json.Unmarshal(bodyBytes, &dest); err != nil { return nil, err diff --git a/backend/db/query.sql.go b/backend/db/query.sql.go index 34b0ae9..02e660a 100644 --- a/backend/db/query.sql.go +++ b/backend/db/query.sql.go @@ -245,6 +245,19 @@ func (q *Queries) GetUserByID(ctx context.Context, userID int32) (User, error) { return i, err } +const getUserIDByUsername = `-- name: GetUserIDByUsername :one +SELECT user_id FROM users +WHERE users.username = $1 +LIMIT 1 +` + +func (q *Queries) GetUserIDByUsername(ctx context.Context, username string) (int32, error) { + row := q.db.QueryRow(ctx, getUserIDByUsername, username) + var user_id int32 + err := row.Scan(&user_id) + return user_id, err +} + const isRegistrationTokenValid = `-- name: IsRegistrationTokenValid :one SELECT EXISTS ( SELECT 1 FROM registration_tokens diff --git a/backend/query.sql b/backend/query.sql index 25eb4df..6b0ecdd 100644 --- a/backend/query.sql +++ b/backend/query.sql @@ -3,6 +3,11 @@ SELECT * FROM users WHERE users.user_id = $1 LIMIT 1; +-- name: GetUserIDByUsername :one +SELECT user_id FROM users +WHERE users.username = $1 +LIMIT 1; + -- name: CreateUser :one INSERT INTO users (username, display_name, is_admin) VALUES ($1, $1, false) diff --git a/frontend/app/routes/login.tsx b/frontend/app/routes/login.tsx index 6d76e84..b1249e0 100644 --- a/frontend/app/routes/login.tsx +++ b/frontend/app/routes/login.tsx @@ -35,18 +35,6 @@ export async function action({ request }: ActionFunctionArgs) { { status: 400 }, ); } - if (username.includes("@")) { - return json( - { - message: "ユーザー名が誤っています", - errors: { - username: "メールアドレスではなくユーザー名を入力してください", - password: undefined, - }, - }, - { status: 400 }, - ); - } try { await login(request); diff --git a/openapi/fortee.yaml b/openapi/fortee.yaml index a27c721..707a29b 100644 --- a/openapi/fortee.yaml +++ b/openapi/fortee.yaml @@ -34,5 +34,13 @@ paths: loggedIn: type: boolean example: true + user: + type: object + properties: + username: + type: string + example: "john" + required: + - username required: - loggedIn |
