Unverified Commit 2e4f1189 authored by Lemon-miaow's avatar Lemon-miaow
Browse files

feat(login): 未绑定 Owner 时说明原因和绑定方式

parent 9139c5bd
Loading
Loading
Loading
Loading
+1 −1
Changes for README.md: 1 added line, 1 removed line.
Original line number Diff line number Diff line
@@ -70,7 +70,7 @@ curl -fsSL https://raw.githubusercontent.com/FelisMC/Felis/main/deploy/bootstrap

脚本将安装 K3s,在 K3s 中部署 PostgreSQL 与控制平面,随后启动设置向导。设置完成后,通过浏览器访问所配置的域名即可进入控制面板。

* **设置向导**:向导首先绑定平台所有者:以 Minecraft Java 版加入向导所示的地址,登录服务器会给出 8 位绑定码(10 分钟内有效),将其输入向导即可。该步骤可以跳过,之后再次执行 `sudo felis setup` 补做;绑定所有者之前,任何人均无法登录控制面板。安装器仅在交互式终端中自动启动向导;输出重定向至日志或经由 cloud-init 安装时,请在安装结束后执行 `sudo felis setup`。设置 `FELIS_NO_SETUP=1` 时,安装器在输出摘要后直接结束。
* **设置向导**:向导首先绑定平台所有者:以 Minecraft Java 版加入向导所示的地址,登录服务器会给出 8 位绑定码(10 分钟内有效),将其输入向导即可。该步骤可以跳过,之后再次执行 `sudo felis setup` 补做;绑定所有者之前,任何人均无法登录控制面板,登录页届时会说明原因并列出绑定步骤及连接地址。安装器仅在交互式终端中自动启动向导;输出重定向至日志或经由 cloud-init 安装时,请在安装结束后执行 `sudo felis setup`。设置 `FELIS_NO_SETUP=1` 时,安装器在输出摘要后直接结束。

* **支持的系统**:CentOS Stream 9(aarch64)已在实机上验证;Ubuntu 24.04(x86_64)在每次推送时由 CI 执行全新安装、重复安装、升级及上述安装命令(参见 [运维手册 §1](docs/operations.md#1-supported-hosts))。

+1 −1
Changes for README_EN.md: 1 added line, 1 removed line.
Original line number Diff line number Diff line
@@ -69,7 +69,7 @@ curl -fsSL https://raw.githubusercontent.com/FelisMC/Felis/main/deploy/bootstrap

The script installs K3s, deploys PostgreSQL and the control plane inside it, and launches a setup wizard. When setup completes, open the configured domain in a browser to reach the control panel.

* **Setup wizard**: The wizard first binds the platform Owner: join the address it shows in Minecraft Java Edition, then enter the 8-character link code that the login server displays (valid for 10 minutes). The step can be skipped and completed later by running `sudo felis setup` again; until an Owner is bound, nobody can sign in to the control panel. The installer launches the wizard automatically only on an interactive terminal; when output is redirected to a log or the install runs under cloud-init, run `sudo felis setup` after it finishes. Setting `FELIS_NO_SETUP=1` makes the installer end at its summary.
* **Setup wizard**: The wizard first binds the platform Owner: join the address it shows in Minecraft Java Edition, then enter the 8-character link code that the login server displays (valid for 10 minutes). The step can be skipped and completed later by running `sudo felis setup` again; until an Owner is bound, nobody can sign in to the control panel, and the sign-in page states this together with the binding steps and the address to join. The installer launches the wizard automatically only on an interactive terminal; when output is redirected to a log or the install runs under cloud-init, run `sudo felis setup` after it finishes. Setting `FELIS_NO_SETUP=1` makes the installer end at its summary.

* **Supported hosts**: CentOS Stream 9 (aarch64) is verified on physical hardware; Ubuntu 24.04 (x86_64) is tested in CI on every push with a fresh install, a rerun, an upgrade and the install command above (see [operations §1](docs/operations.md#1-supported-hosts)).

+28 −0
Changes for docs/openapi.yaml: 28 added lines, 0 removed lines.
Original line number Diff line number Diff line
@@ -3941,6 +3941,34 @@ paths:
            application/json:
              schema: { $ref: '#/components/schemas/Error' }

  /api/v1/auth/owner-status:
    get:
      tags: [auth]
      operationId: ownerStatus
      summary: Report whether an Owner has been bound on this install.
      description: >-
        Public, pre-session probe the sign-in page reads on load. Until `felis setup`
        binds an Owner, local sign-in is off and every login door answers 403
        local_auth_disabled; the page then explains that no Owner exists and how to bind
        one instead of offering the doors. It discloses only whether the install is
        still unclaimed, and claiming it needs root on the host. It is not gated on
        local_auth_enabled and does not draw on the login doors' per-address rate limit.
      x-felis-face: [external]
      x-felis-tier: public
      security: []
      responses:
        '200':
          description: Whether any Owner or admin account exists.
          content:
            application/json:
              schema:
                type: object
                required: [owner_bound]
                properties:
                  owner_bound: { type: boolean }
        '503':
          $ref: '#/components/responses/ServiceUnavailable'

  /api/v1/auth/logout:
    post:
      tags: [auth]
+7 −0
Changes for internal/api/api.go: 7 added lines, 0 removed lines.
Original line number Diff line number Diff line
@@ -21,6 +21,7 @@ import (
	"net/http"
	"strings"
	"sync"
	"sync/atomic"
	"time"

	"felis.lolicon.best/internal/apis/felis/v1alpha1"
@@ -237,6 +238,9 @@ type API struct {
	mailOnce        sync.Once
	mailBuckets     *bucketSet

	// ownerBound caches the first "an Owner exists" answer (handleOwnerStatus).
	ownerBound atomic.Bool

	drainInit  sync.Once
	drainClose sync.Once
	drain      chan struct{}
@@ -502,6 +506,9 @@ func (a *API) externalAPIRoutes() []apiRoute {
		{Method: "POST", Pattern: "/api/v1/auth/options", Public: true, AuthDoor: true, h: a.handleAuthOptions},
		{Method: "POST", Pattern: "/api/v1/auth/setup/redeem", Public: true, AuthDoor: true, h: a.handleSetupRedeem},
		{Method: "GET", Pattern: "/api/v1/auth/setup/status", SetupAllowed: true, h: a.handleSetupStatus},
		// Whether an Owner is bound yet: before one is, every login door here is off, and the
		// sign-in page says so instead of offering them (handlers_auth_owner.go).
		{Method: "GET", Pattern: "/api/v1/auth/owner-status", Public: true, h: a.handleOwnerStatus},
		{Method: "POST", Pattern: "/api/v1/auth/passkey/login/begin", Public: true, AuthDoor: true, h: a.handlePasskeyLoginBegin},
		{Method: "POST", Pattern: "/api/v1/auth/passkey/login/finish", Public: true, AuthDoor: true, h: a.handlePasskeyLoginFinish},
		// Discoverable ("usernameless") passkey login (task #40): the from-zero sibling of the
+12 −0
Changes for internal/api/api_test.go: 12 added lines, 0 removed lines.
Original line number Diff line number Diff line
@@ -94,6 +94,7 @@ type fakeRepo struct {
	failRevokeOthers error
	failMarkReauth   error
	failGetSetting   error
	failAdminExists  error // AdminExists fails with it (a store outage)
	failRedeemSetup  error
	failUserDetail   error
	// player email OTPs (spec §B2). Keyed by row id; the verify path scans for the
@@ -1106,6 +1107,17 @@ func (f *fakeRepo) UpsertOwner(_ context.Context, id, username, email string) er
	}
	return nil
}
func (f *fakeRepo) AdminExists(_ context.Context) (bool, error) {
	if f.failAdminExists != nil {
		return false, f.failAdminExists
	}
	for _, u := range f.staff {
		if u.Role == "admin" || u.Role == "owner" {
			return true, nil
		}
	}
	return false, nil
}
func (f *fakeRepo) CreateSession(_ context.Context, ns NewSession) error {
	f.sessions[ns.TokenHash] = &fakeSession{
		userID: ns.UserID, expiresAt: ns.ExpiresAt, createdAt: ns.CreatedAt, lastSeen: ns.CreatedAt,
Loading