aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--backend/auth/auth.go74
-rw-r--r--backend/auth/fortee/fortee.go18
-rw-r--r--backend/auth/fortee/generated.go6
-rw-r--r--backend/db/query.sql.go13
-rw-r--r--backend/query.sql5
-rw-r--r--frontend/app/routes/login.tsx12
-rw-r--r--openapi/fortee.yaml8
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