feat: expose account registration, login and admin recovery over HTTP
This commit is contained in:
1 parent
62d99221fe
commit
246baf5a0e
17 files changed
+1643
-53
No files matched your search
@@ -10,6 +10,9 @@ import (
|
||||
var (
|
||||
patternAdminCreateRoomRequestSlug = regexp.MustCompile(`^[a-z0-9-]{1,32}$`)
|
||||
patternAdminUpdateRoomRequestSlug = regexp.MustCompile(`^[a-z0-9-]{0,32}$`)
|
||||
patternLoginRequestDevice = regexp.MustCompile(`^[A-Za-z0-9_-]{43}$`)
|
||||
patternLoginRequestUsername = regexp.MustCompile(`^[a-z0-9_-]{3,32}$`)
|
||||
patternRegisterRequestUsername = regexp.MustCompile(`^[a-z0-9_-]{3,32}$`)
|
||||
patternTrackIDWhenBilibili = regexp.MustCompile(`^BV[1-9A-HJ-NP-Za-km-z]{10}(?:/([2-9]|[1-9][0-9]{1,3}))?$`)
|
||||
)
|
||||
|
||||
@@ -72,6 +75,18 @@ func DecodeUpdateMeRequest(body []byte) (UpdateMeRequest, error) {
|
||||
return decodeUpdateMeRequest(body, "")
|
||||
}
|
||||
|
||||
func DecodeRegisterRequest(body []byte) (RegisterRequest, error) {
|
||||
return decodeRegisterRequest(body, "")
|
||||
}
|
||||
|
||||
func DecodeChangePasswordRequest(body []byte) (ChangePasswordRequest, error) {
|
||||
return decodeChangePasswordRequest(body, "")
|
||||
}
|
||||
|
||||
func DecodeLoginRequest(body []byte) (LoginRequest, error) {
|
||||
return decodeLoginRequest(body, "")
|
||||
}
|
||||
|
||||
func DecodeCreateRoomRequest(body []byte) (CreateRoomRequest, error) {
|
||||
return decodeCreateRoomRequest(body, "")
|
||||
}
|
||||
@@ -124,6 +139,14 @@ func DecodeAdminUpdateSiteRequest(body []byte) (AdminUpdateSiteRequest, error) {
|
||||
return decodeAdminUpdateSiteRequest(body, "")
|
||||
}
|
||||
|
||||
func DecodeAdminOpenRegistrationRequest(body []byte) (AdminOpenRegistrationRequest, error) {
|
||||
return decodeAdminOpenRegistrationRequest(body, "")
|
||||
}
|
||||
|
||||
func DecodeAdminResetAccountPasswordRequest(body []byte) (AdminResetAccountPasswordRequest, error) {
|
||||
return decodeAdminResetAccountPasswordRequest(body, "")
|
||||
}
|
||||
|
||||
func decodeAdminCheckBilibiliLoginRequest(raw json.RawMessage, path string) (AdminCheckBilibiliLoginRequest, error) {
|
||||
var v AdminCheckBilibiliLoginRequest
|
||||
m, err := decodeObject(raw, path, "key")
|
||||
@@ -225,6 +248,18 @@ func decodeAdminCreateRoomRequest(raw json.RawMessage, path string) (AdminCreate
|
||||
return v, nil
|
||||
}
|
||||
|
||||
func decodeAdminOpenRegistrationRequest(raw json.RawMessage, path string) (AdminOpenRegistrationRequest, error) {
|
||||
var v AdminOpenRegistrationRequest
|
||||
_, err := decodeObject(raw, path)
|
||||
return v, err
|
||||
}
|
||||
|
||||
func decodeAdminResetAccountPasswordRequest(raw json.RawMessage, path string) (AdminResetAccountPasswordRequest, error) {
|
||||
var v AdminResetAccountPasswordRequest
|
||||
_, err := decodeObject(raw, path)
|
||||
return v, err
|
||||
}
|
||||
|
||||
func decodeAdminResetRoomApiKeyRequest(raw json.RawMessage, path string) (AdminResetRoomApiKeyRequest, error) {
|
||||
var v AdminResetRoomApiKeyRequest
|
||||
_, err := decodeObject(raw, path)
|
||||
@@ -340,6 +375,36 @@ func decodeAdminUpdateSiteRequest(raw json.RawMessage, path string) (AdminUpdate
|
||||
return v, nil
|
||||
}
|
||||
|
||||
func decodeChangePasswordRequest(raw json.RawMessage, path string) (ChangePasswordRequest, error) {
|
||||
var v ChangePasswordRequest
|
||||
m, err := decodeObject(raw, path, "currentPassword", "newPassword")
|
||||
if err != nil {
|
||||
return v, err
|
||||
}
|
||||
if err = requireFields(m, path, "currentPassword", "newPassword"); err != nil {
|
||||
return v, err
|
||||
}
|
||||
if v.CurrentPassword, err = decodeString(m["currentPassword"], field(path, "currentPassword")); err != nil {
|
||||
return v, err
|
||||
}
|
||||
if err = checkMinLength(v.CurrentPassword, field(path, "currentPassword"), 8); err != nil {
|
||||
return v, err
|
||||
}
|
||||
if err = checkMaxLength(v.CurrentPassword, field(path, "currentPassword"), 128); err != nil {
|
||||
return v, err
|
||||
}
|
||||
if v.NewPassword, err = decodeString(m["newPassword"], field(path, "newPassword")); err != nil {
|
||||
return v, err
|
||||
}
|
||||
if err = checkMinLength(v.NewPassword, field(path, "newPassword"), 8); err != nil {
|
||||
return v, err
|
||||
}
|
||||
if err = checkMaxLength(v.NewPassword, field(path, "newPassword"), 128); err != nil {
|
||||
return v, err
|
||||
}
|
||||
return v, nil
|
||||
}
|
||||
|
||||
func decodeChatSendRequest(raw json.RawMessage, path string) (ChatSendRequest, error) {
|
||||
var v ChatSendRequest
|
||||
m, err := decodeObject(raw, path, "content")
|
||||
@@ -432,6 +497,43 @@ func decodeHelloRequest(raw json.RawMessage, path string) (HelloRequest, error)
|
||||
return v, nil
|
||||
}
|
||||
|
||||
func decodeLoginRequest(raw json.RawMessage, path string) (LoginRequest, error) {
|
||||
var v LoginRequest
|
||||
m, err := decodeObject(raw, path, "device", "password", "username")
|
||||
if err != nil {
|
||||
return v, err
|
||||
}
|
||||
if err = requireFields(m, path, "password", "username"); err != nil {
|
||||
return v, err
|
||||
}
|
||||
if r, ok := m["device"]; ok {
|
||||
x, err := decodeString(r, field(path, "device"))
|
||||
if err != nil {
|
||||
return v, err
|
||||
}
|
||||
if err = checkPattern(x, field(path, "device"), patternLoginRequestDevice); err != nil {
|
||||
return v, err
|
||||
}
|
||||
v.Device = &x
|
||||
}
|
||||
if v.Password, err = decodeString(m["password"], field(path, "password")); err != nil {
|
||||
return v, err
|
||||
}
|
||||
if err = checkMinLength(v.Password, field(path, "password"), 8); err != nil {
|
||||
return v, err
|
||||
}
|
||||
if err = checkMaxLength(v.Password, field(path, "password"), 128); err != nil {
|
||||
return v, err
|
||||
}
|
||||
if v.Username, err = decodeString(m["username"], field(path, "username")); err != nil {
|
||||
return v, err
|
||||
}
|
||||
if err = checkPattern(v.Username, field(path, "username"), patternLoginRequestUsername); err != nil {
|
||||
return v, err
|
||||
}
|
||||
return v, nil
|
||||
}
|
||||
|
||||
func decodePingRequest(raw json.RawMessage, path string) (PingRequest, error) {
|
||||
var v PingRequest
|
||||
m, err := decodeObject(raw, path, "clientTimeMs")
|
||||
@@ -583,6 +685,33 @@ func decodeRecommendListRequest(raw json.RawMessage, path string) (RecommendList
|
||||
return v, err
|
||||
}
|
||||
|
||||
func decodeRegisterRequest(raw json.RawMessage, path string) (RegisterRequest, error) {
|
||||
var v RegisterRequest
|
||||
m, err := decodeObject(raw, path, "password", "username")
|
||||
if err != nil {
|
||||
return v, err
|
||||
}
|
||||
if err = requireFields(m, path, "password", "username"); err != nil {
|
||||
return v, err
|
||||
}
|
||||
if v.Password, err = decodeString(m["password"], field(path, "password")); err != nil {
|
||||
return v, err
|
||||
}
|
||||
if err = checkMinLength(v.Password, field(path, "password"), 8); err != nil {
|
||||
return v, err
|
||||
}
|
||||
if err = checkMaxLength(v.Password, field(path, "password"), 128); err != nil {
|
||||
return v, err
|
||||
}
|
||||
if v.Username, err = decodeString(m["username"], field(path, "username")); err != nil {
|
||||
return v, err
|
||||
}
|
||||
if err = checkPattern(v.Username, field(path, "username"), patternRegisterRequestUsername); err != nil {
|
||||
return v, err
|
||||
}
|
||||
return v, nil
|
||||
}
|
||||
|
||||
func decodeRoomSyncRequest(raw json.RawMessage, path string) (RoomSyncRequest, error) {
|
||||
var v RoomSyncRequest
|
||||
_, err := decodeObject(raw, path)
|
||||
|
||||
+172
-6
@@ -12,6 +12,7 @@
|
||||
"enum": [
|
||||
"INVALID_ARGUMENT",
|
||||
"UNAUTHORIZED",
|
||||
"INVALID_CREDENTIALS",
|
||||
"FORBIDDEN",
|
||||
"NOT_FOUND",
|
||||
"CONFLICT",
|
||||
@@ -26,6 +27,7 @@
|
||||
"x-httpStatus": {
|
||||
"INVALID_ARGUMENT": 400,
|
||||
"UNAUTHORIZED": 401,
|
||||
"INVALID_CREDENTIALS": 401,
|
||||
"FORBIDDEN": 403,
|
||||
"NOT_FOUND": 404,
|
||||
"CONFLICT": 409,
|
||||
@@ -139,6 +141,40 @@
|
||||
},
|
||||
"required": ["id", "name", "avatarHash"]
|
||||
},
|
||||
"Username": {
|
||||
"type": "string",
|
||||
"description": "用户名:3 到 32 个小写字母、数字、下划线或连字符",
|
||||
"pattern": "^[a-z0-9_-]{3,32}$"
|
||||
},
|
||||
"AccountPassword": {
|
||||
"type": "string",
|
||||
"description": "账号密码",
|
||||
"minLength": 8,
|
||||
"maxLength": 128
|
||||
},
|
||||
"LoginDevice": {
|
||||
"type": "string",
|
||||
"description": "登录设备凭证:用账号密码登录成功时发给这台设备,43 个 base64url 字符",
|
||||
"pattern": "^[A-Za-z0-9_-]{43}$"
|
||||
},
|
||||
"Account": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"username": {"type": "string"},
|
||||
"email": {"type": "string", "description": "账号设置了邮箱时才有"}
|
||||
},
|
||||
"required": ["username"]
|
||||
},
|
||||
"AdminAccount": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"member": {"$ref": "#/$defs/Member"},
|
||||
"username": {"type": "string"}
|
||||
},
|
||||
"required": ["member", "username"]
|
||||
},
|
||||
"Members": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
@@ -658,7 +694,7 @@
|
||||
},
|
||||
{
|
||||
"name": "getMe",
|
||||
"description": "当前成员的资料",
|
||||
"description": "当前成员的资料;已注册的成员带上账号",
|
||||
"method": "GET",
|
||||
"path": "/api/me",
|
||||
"auth": "member",
|
||||
@@ -666,14 +702,15 @@
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"member": {"$ref": "#/$defs/Member"}
|
||||
"member": {"$ref": "#/$defs/Member"},
|
||||
"account": {"$ref": "#/$defs/Account"}
|
||||
},
|
||||
"required": ["member"]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "updateMe",
|
||||
"description": "修改昵称和头像;email 只用来计算头像哈希,空字符串表示清除头像",
|
||||
"description": "修改昵称和头像;email 用来计算头像哈希,已注册的账号同时保存它,去掉首尾空白后应形如 name@example.com,空字符串表示清除",
|
||||
"method": "PATCH",
|
||||
"path": "/api/me",
|
||||
"auth": "member",
|
||||
@@ -694,6 +731,85 @@
|
||||
"required": ["member"]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "register",
|
||||
"description": "游客注册账号:设置用户名和密码,成员和成员 token 都不变;站点关闭注册时返回 FORBIDDEN",
|
||||
"method": "POST",
|
||||
"path": "/api/account",
|
||||
"auth": "member",
|
||||
"status": 201,
|
||||
"request": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"username": {"$ref": "#/$defs/Username"},
|
||||
"password": {"$ref": "#/$defs/AccountPassword"}
|
||||
},
|
||||
"required": ["username", "password"]
|
||||
},
|
||||
"response": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"account": {"$ref": "#/$defs/Account"}
|
||||
},
|
||||
"required": ["account"]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "changePassword",
|
||||
"description": "修改密码:账号在其他设备上的成员 token 随即失效,发出请求的这个不变;当前密码错误时返回 INVALID_CREDENTIALS",
|
||||
"method": "PUT",
|
||||
"path": "/api/account/password",
|
||||
"auth": "member",
|
||||
"status": 204,
|
||||
"request": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"currentPassword": {"$ref": "#/$defs/AccountPassword"},
|
||||
"newPassword": {"$ref": "#/$defs/AccountPassword"}
|
||||
},
|
||||
"required": ["currentPassword", "newPassword"]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "login",
|
||||
"description": "游客登录账号:返回账号的新成员 token,发出请求的游客 token 随即失效;用户名或密码错误时返回 INVALID_CREDENTIALS,已经登录了账号时返回 CONFLICT",
|
||||
"method": "POST",
|
||||
"path": "/api/sessions",
|
||||
"auth": "member",
|
||||
"status": 201,
|
||||
"request": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"username": {"$ref": "#/$defs/Username"},
|
||||
"password": {"$ref": "#/$defs/AccountPassword"},
|
||||
"device": {"$ref": "#/$defs/LoginDevice", "description": "本机此前登录这个账号时拿到的凭证:有人反复猜这个账号的密码时,带上它本机仍然能登录"}
|
||||
},
|
||||
"required": ["username", "password"]
|
||||
},
|
||||
"response": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"memberToken": {"type": "string"},
|
||||
"member": {"$ref": "#/$defs/Member"},
|
||||
"account": {"$ref": "#/$defs/Account"},
|
||||
"device": {"$ref": "#/$defs/LoginDevice", "description": "本机以后登录这个账号时要带上的凭证,退出登录之后也要保留:带来的凭证有效时原样返回,否则是新签发的"}
|
||||
},
|
||||
"required": ["memberToken", "member", "account", "device"]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "logout",
|
||||
"description": "退出登录:发出请求的成员 token 随即失效",
|
||||
"method": "DELETE",
|
||||
"path": "/api/sessions/current",
|
||||
"auth": "member",
|
||||
"status": 204
|
||||
},
|
||||
{
|
||||
"name": "listRooms",
|
||||
"description": "房间列表",
|
||||
@@ -769,7 +885,7 @@
|
||||
},
|
||||
{
|
||||
"name": "getSite",
|
||||
"description": "品牌名;没有设置过时是 Alisten",
|
||||
"description": "品牌名和是否开放注册;没有设置过品牌名时是 Alisten",
|
||||
"method": "GET",
|
||||
"path": "/api/site",
|
||||
"auth": "none",
|
||||
@@ -777,9 +893,10 @@
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"brandName": {"type": "string"}
|
||||
"brandName": {"type": "string"},
|
||||
"registrationOpen": {"type": "boolean"}
|
||||
},
|
||||
"required": ["brandName"]
|
||||
"required": ["brandName", "registrationOpen"]
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -1097,6 +1214,55 @@
|
||||
},
|
||||
"required": ["brandName"]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "adminOpenRegistration",
|
||||
"description": "开放注册",
|
||||
"method": "PUT",
|
||||
"path": "/api/admin/registration",
|
||||
"auth": "admin",
|
||||
"status": 204,
|
||||
"request": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"properties": {}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "adminCloseRegistration",
|
||||
"description": "关闭注册:不能再注册新账号,已有账号照常登录,游客照常使用",
|
||||
"method": "DELETE",
|
||||
"path": "/api/admin/registration",
|
||||
"auth": "admin",
|
||||
"status": 204
|
||||
},
|
||||
{
|
||||
"name": "adminGetAccount",
|
||||
"description": "按用户名查找账号",
|
||||
"method": "GET",
|
||||
"path": "/api/admin/accounts/{username}",
|
||||
"auth": "admin",
|
||||
"response": {"$ref": "#/$defs/AdminAccount"}
|
||||
},
|
||||
{
|
||||
"name": "adminResetAccountPassword",
|
||||
"description": "重置账号的密码:生成随机的新密码,只在这次响应中返回;账号在所有设备上的成员 token 随即失效",
|
||||
"method": "POST",
|
||||
"path": "/api/admin/accounts/{username}/password",
|
||||
"auth": "admin",
|
||||
"request": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"properties": {}
|
||||
},
|
||||
"response": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"password": {"type": "string"}
|
||||
},
|
||||
"required": ["password"]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
+477
-5
@@ -14,6 +14,22 @@
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"Account": {
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"email": {
|
||||
"description": "账号设置了邮箱时才有",
|
||||
"type": "string"
|
||||
},
|
||||
"username": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"username"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"ActivityKind": {
|
||||
"enum": [
|
||||
"added",
|
||||
@@ -25,6 +41,22 @@
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"AdminAccount": {
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"member": {
|
||||
"$ref": "#/components/schemas/Member"
|
||||
},
|
||||
"username": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"member",
|
||||
"username"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"AdminCheckBilibiliLoginRequest": {
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
@@ -246,6 +278,26 @@
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"AdminOpenRegistrationRequest": {
|
||||
"additionalProperties": false,
|
||||
"type": "object"
|
||||
},
|
||||
"AdminResetAccountPasswordRequest": {
|
||||
"additionalProperties": false,
|
||||
"type": "object"
|
||||
},
|
||||
"AdminResetAccountPasswordResponse": {
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"password": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"password"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"AdminResetRoomApiKeyRequest": {
|
||||
"additionalProperties": false,
|
||||
"type": "object"
|
||||
@@ -331,6 +383,28 @@
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"ChangePasswordRequest": {
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"currentPassword": {
|
||||
"description": "账号密码",
|
||||
"maxLength": 128,
|
||||
"minLength": 8,
|
||||
"type": "string"
|
||||
},
|
||||
"newPassword": {
|
||||
"description": "账号密码",
|
||||
"maxLength": 128,
|
||||
"minLength": 8,
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"currentPassword",
|
||||
"newPassword"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"ChatSendRequest": {
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
@@ -479,6 +553,7 @@
|
||||
"enum": [
|
||||
"INVALID_ARGUMENT",
|
||||
"UNAUTHORIZED",
|
||||
"INVALID_CREDENTIALS",
|
||||
"FORBIDDEN",
|
||||
"NOT_FOUND",
|
||||
"CONFLICT",
|
||||
@@ -496,6 +571,7 @@
|
||||
"FORBIDDEN": 403,
|
||||
"INTERNAL": 500,
|
||||
"INVALID_ARGUMENT": 400,
|
||||
"INVALID_CREDENTIALS": 401,
|
||||
"NOTHING_PLAYING": 409,
|
||||
"NOT_FOUND": 404,
|
||||
"PROVIDER_ERROR": 502,
|
||||
@@ -537,6 +613,9 @@
|
||||
"GetMeResponse": {
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"account": {
|
||||
"$ref": "#/components/schemas/Account"
|
||||
},
|
||||
"member": {
|
||||
"$ref": "#/components/schemas/Member"
|
||||
}
|
||||
@@ -551,10 +630,14 @@
|
||||
"properties": {
|
||||
"brandName": {
|
||||
"type": "string"
|
||||
},
|
||||
"registrationOpen": {
|
||||
"type": "boolean"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"brandName"
|
||||
"brandName",
|
||||
"registrationOpen"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
@@ -573,6 +656,58 @@
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"LoginRequest": {
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"device": {
|
||||
"description": "本机此前登录这个账号时拿到的凭证:有人反复猜这个账号的密码时,带上它本机仍然能登录",
|
||||
"pattern": "^[A-Za-z0-9_-]{43}$",
|
||||
"type": "string"
|
||||
},
|
||||
"password": {
|
||||
"description": "账号密码",
|
||||
"maxLength": 128,
|
||||
"minLength": 8,
|
||||
"type": "string"
|
||||
},
|
||||
"username": {
|
||||
"description": "用户名:3 到 32 个小写字母、数字、下划线或连字符",
|
||||
"pattern": "^[a-z0-9_-]{3,32}$",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"username",
|
||||
"password"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"LoginResponse": {
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"account": {
|
||||
"$ref": "#/components/schemas/Account"
|
||||
},
|
||||
"device": {
|
||||
"description": "本机以后登录这个账号时要带上的凭证,退出登录之后也要保留:带来的凭证有效时原样返回,否则是新签发的",
|
||||
"pattern": "^[A-Za-z0-9_-]{43}$",
|
||||
"type": "string"
|
||||
},
|
||||
"member": {
|
||||
"$ref": "#/components/schemas/Member"
|
||||
},
|
||||
"memberToken": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"memberToken",
|
||||
"member",
|
||||
"account",
|
||||
"device"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"Member": {
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
@@ -892,6 +1027,39 @@
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"RegisterRequest": {
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"password": {
|
||||
"description": "账号密码",
|
||||
"maxLength": 128,
|
||||
"minLength": 8,
|
||||
"type": "string"
|
||||
},
|
||||
"username": {
|
||||
"description": "用户名:3 到 32 个小写字母、数字、下划线或连字符",
|
||||
"pattern": "^[a-z0-9_-]{3,32}$",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"username",
|
||||
"password"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"RegisterResponse": {
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"account": {
|
||||
"$ref": "#/components/schemas/Account"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"account"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"RoomInfo": {
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
@@ -1343,10 +1511,185 @@
|
||||
},
|
||||
"info": {
|
||||
"title": "Alisten",
|
||||
"version": "473ee31f5ef912b4"
|
||||
"version": "bb812527ea1133df"
|
||||
},
|
||||
"openapi": "3.1.0",
|
||||
"paths": {
|
||||
"/api/account": {
|
||||
"post": {
|
||||
"operationId": "register",
|
||||
"requestBody": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/RegisterRequest"
|
||||
}
|
||||
}
|
||||
},
|
||||
"required": true
|
||||
},
|
||||
"responses": {
|
||||
"201": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/RegisterResponse"
|
||||
}
|
||||
}
|
||||
},
|
||||
"description": "成功"
|
||||
},
|
||||
"default": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/ErrorResponse"
|
||||
}
|
||||
}
|
||||
},
|
||||
"description": "失败"
|
||||
}
|
||||
},
|
||||
"security": [
|
||||
{
|
||||
"memberToken": []
|
||||
}
|
||||
],
|
||||
"summary": "游客注册账号:设置用户名和密码,成员和成员 token 都不变;站点关闭注册时返回 FORBIDDEN"
|
||||
}
|
||||
},
|
||||
"/api/account/password": {
|
||||
"put": {
|
||||
"operationId": "changePassword",
|
||||
"requestBody": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/ChangePasswordRequest"
|
||||
}
|
||||
}
|
||||
},
|
||||
"required": true
|
||||
},
|
||||
"responses": {
|
||||
"204": {
|
||||
"description": "成功"
|
||||
},
|
||||
"default": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/ErrorResponse"
|
||||
}
|
||||
}
|
||||
},
|
||||
"description": "失败"
|
||||
}
|
||||
},
|
||||
"security": [
|
||||
{
|
||||
"memberToken": []
|
||||
}
|
||||
],
|
||||
"summary": "修改密码:账号在其他设备上的成员 token 随即失效,发出请求的这个不变;当前密码错误时返回 INVALID_CREDENTIALS"
|
||||
}
|
||||
},
|
||||
"/api/admin/accounts/{username}": {
|
||||
"get": {
|
||||
"operationId": "adminGetAccount",
|
||||
"parameters": [
|
||||
{
|
||||
"in": "path",
|
||||
"name": "username",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/AdminAccount"
|
||||
}
|
||||
}
|
||||
},
|
||||
"description": "成功"
|
||||
},
|
||||
"default": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/ErrorResponse"
|
||||
}
|
||||
}
|
||||
},
|
||||
"description": "失败"
|
||||
}
|
||||
},
|
||||
"security": [
|
||||
{
|
||||
"adminToken": []
|
||||
}
|
||||
],
|
||||
"summary": "按用户名查找账号"
|
||||
}
|
||||
},
|
||||
"/api/admin/accounts/{username}/password": {
|
||||
"post": {
|
||||
"operationId": "adminResetAccountPassword",
|
||||
"parameters": [
|
||||
{
|
||||
"in": "path",
|
||||
"name": "username",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
],
|
||||
"requestBody": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/AdminResetAccountPasswordRequest"
|
||||
}
|
||||
}
|
||||
},
|
||||
"required": true
|
||||
},
|
||||
"responses": {
|
||||
"200": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/AdminResetAccountPasswordResponse"
|
||||
}
|
||||
}
|
||||
},
|
||||
"description": "成功"
|
||||
},
|
||||
"default": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/ErrorResponse"
|
||||
}
|
||||
}
|
||||
},
|
||||
"description": "失败"
|
||||
}
|
||||
},
|
||||
"security": [
|
||||
{
|
||||
"adminToken": []
|
||||
}
|
||||
],
|
||||
"summary": "重置账号的密码:生成随机的新密码,只在这次响应中返回;账号在所有设备上的成员 token 随即失效"
|
||||
}
|
||||
},
|
||||
"/api/admin/credentials/bilibili": {
|
||||
"get": {
|
||||
"operationId": "adminGetBilibiliCredential",
|
||||
@@ -1864,6 +2207,66 @@
|
||||
"summary": "删除一首已存储的歌的音频和封面,并清除它的准备结果"
|
||||
}
|
||||
},
|
||||
"/api/admin/registration": {
|
||||
"delete": {
|
||||
"operationId": "adminCloseRegistration",
|
||||
"responses": {
|
||||
"204": {
|
||||
"description": "成功"
|
||||
},
|
||||
"default": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/ErrorResponse"
|
||||
}
|
||||
}
|
||||
},
|
||||
"description": "失败"
|
||||
}
|
||||
},
|
||||
"security": [
|
||||
{
|
||||
"adminToken": []
|
||||
}
|
||||
],
|
||||
"summary": "关闭注册:不能再注册新账号,已有账号照常登录,游客照常使用"
|
||||
},
|
||||
"put": {
|
||||
"operationId": "adminOpenRegistration",
|
||||
"requestBody": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/AdminOpenRegistrationRequest"
|
||||
}
|
||||
}
|
||||
},
|
||||
"required": true
|
||||
},
|
||||
"responses": {
|
||||
"204": {
|
||||
"description": "成功"
|
||||
},
|
||||
"default": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/ErrorResponse"
|
||||
}
|
||||
}
|
||||
},
|
||||
"description": "失败"
|
||||
}
|
||||
},
|
||||
"security": [
|
||||
{
|
||||
"adminToken": []
|
||||
}
|
||||
],
|
||||
"summary": "开放注册"
|
||||
}
|
||||
},
|
||||
"/api/admin/rooms": {
|
||||
"get": {
|
||||
"operationId": "adminListRooms",
|
||||
@@ -2201,7 +2604,7 @@
|
||||
"memberToken": []
|
||||
}
|
||||
],
|
||||
"summary": "当前成员的资料"
|
||||
"summary": "当前成员的资料;已注册的成员带上账号"
|
||||
},
|
||||
"patch": {
|
||||
"operationId": "updateMe",
|
||||
@@ -2242,7 +2645,7 @@
|
||||
"memberToken": []
|
||||
}
|
||||
],
|
||||
"summary": "修改昵称和头像;email 只用来计算头像哈希,空字符串表示清除头像"
|
||||
"summary": "修改昵称和头像;email 用来计算头像哈希,已注册的账号同时保存它,去掉首尾空白后应形如 name@example.com,空字符串表示清除"
|
||||
}
|
||||
},
|
||||
"/api/members": {
|
||||
@@ -2973,6 +3376,75 @@
|
||||
"summary": "按 UUID 或别名查找房间"
|
||||
}
|
||||
},
|
||||
"/api/sessions": {
|
||||
"post": {
|
||||
"operationId": "login",
|
||||
"requestBody": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/LoginRequest"
|
||||
}
|
||||
}
|
||||
},
|
||||
"required": true
|
||||
},
|
||||
"responses": {
|
||||
"201": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/LoginResponse"
|
||||
}
|
||||
}
|
||||
},
|
||||
"description": "成功"
|
||||
},
|
||||
"default": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/ErrorResponse"
|
||||
}
|
||||
}
|
||||
},
|
||||
"description": "失败"
|
||||
}
|
||||
},
|
||||
"security": [
|
||||
{
|
||||
"memberToken": []
|
||||
}
|
||||
],
|
||||
"summary": "游客登录账号:返回账号的新成员 token,发出请求的游客 token 随即失效;用户名或密码错误时返回 INVALID_CREDENTIALS,已经登录了账号时返回 CONFLICT"
|
||||
}
|
||||
},
|
||||
"/api/sessions/current": {
|
||||
"delete": {
|
||||
"operationId": "logout",
|
||||
"responses": {
|
||||
"204": {
|
||||
"description": "成功"
|
||||
},
|
||||
"default": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/ErrorResponse"
|
||||
}
|
||||
}
|
||||
},
|
||||
"description": "失败"
|
||||
}
|
||||
},
|
||||
"security": [
|
||||
{
|
||||
"memberToken": []
|
||||
}
|
||||
],
|
||||
"summary": "退出登录:发出请求的成员 token 随即失效"
|
||||
}
|
||||
},
|
||||
"/api/site": {
|
||||
"get": {
|
||||
"operationId": "getSite",
|
||||
@@ -2999,7 +3471,7 @@
|
||||
}
|
||||
},
|
||||
"security": [],
|
||||
"summary": "品牌名;没有设置过时是 Alisten"
|
||||
"summary": "品牌名和是否开放注册;没有设置过品牌名时是 Alisten"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+83
-26
@@ -4,12 +4,17 @@ package protocol
|
||||
|
||||
import "encoding/json"
|
||||
|
||||
const Version = "473ee31f5ef912b4"
|
||||
const Version = "bb812527ea1133df"
|
||||
|
||||
type APIKey struct {
|
||||
Key string `json:"key"`
|
||||
}
|
||||
|
||||
type Account struct {
|
||||
Email *string `json:"email,omitempty"`
|
||||
Username string `json:"username"`
|
||||
}
|
||||
|
||||
type ActivityKind string
|
||||
|
||||
const (
|
||||
@@ -21,6 +26,11 @@ const (
|
||||
ActivityKindFailed ActivityKind = "failed"
|
||||
)
|
||||
|
||||
type AdminAccount struct {
|
||||
Member Member `json:"member"`
|
||||
Username string `json:"username"`
|
||||
}
|
||||
|
||||
type AdminCheckBilibiliLoginRequest struct {
|
||||
Key string `json:"key"`
|
||||
}
|
||||
@@ -94,6 +104,14 @@ func (v AdminListRoomsResponse) MarshalJSON() ([]byte, error) {
|
||||
return json.Marshal(plain(v))
|
||||
}
|
||||
|
||||
type AdminOpenRegistrationRequest struct{}
|
||||
|
||||
type AdminResetAccountPasswordRequest struct{}
|
||||
|
||||
type AdminResetAccountPasswordResponse struct {
|
||||
Password string `json:"password"`
|
||||
}
|
||||
|
||||
type AdminResetRoomApiKeyRequest struct{}
|
||||
|
||||
type AdminSetYoutubeCredentialRequest struct {
|
||||
@@ -123,6 +141,11 @@ type AdminUpdateSiteResponse struct {
|
||||
BrandName string `json:"brandName"`
|
||||
}
|
||||
|
||||
type ChangePasswordRequest struct {
|
||||
CurrentPassword string `json:"currentPassword"`
|
||||
NewPassword string `json:"newPassword"`
|
||||
}
|
||||
|
||||
type ChatMessageEvent struct {
|
||||
Content string `json:"content"`
|
||||
Member Member `json:"member"`
|
||||
@@ -180,18 +203,19 @@ const (
|
||||
type ErrorCode string
|
||||
|
||||
const (
|
||||
ErrorCodeInvalidArgument ErrorCode = "INVALID_ARGUMENT"
|
||||
ErrorCodeUnauthorized ErrorCode = "UNAUTHORIZED"
|
||||
ErrorCodeForbidden ErrorCode = "FORBIDDEN"
|
||||
ErrorCodeNotFound ErrorCode = "NOT_FOUND"
|
||||
ErrorCodeConflict ErrorCode = "CONFLICT"
|
||||
ErrorCodeQueueFull ErrorCode = "QUEUE_FULL"
|
||||
ErrorCodeNothingPlaying ErrorCode = "NOTHING_PLAYING"
|
||||
ErrorCodeRateLimited ErrorCode = "RATE_LIMITED"
|
||||
ErrorCodeProviderError ErrorCode = "PROVIDER_ERROR"
|
||||
ErrorCodeUnavailable ErrorCode = "UNAVAILABLE"
|
||||
ErrorCodeClientOutdated ErrorCode = "CLIENT_OUTDATED"
|
||||
ErrorCodeInternal ErrorCode = "INTERNAL"
|
||||
ErrorCodeInvalidArgument ErrorCode = "INVALID_ARGUMENT"
|
||||
ErrorCodeUnauthorized ErrorCode = "UNAUTHORIZED"
|
||||
ErrorCodeInvalidCredentials ErrorCode = "INVALID_CREDENTIALS"
|
||||
ErrorCodeForbidden ErrorCode = "FORBIDDEN"
|
||||
ErrorCodeNotFound ErrorCode = "NOT_FOUND"
|
||||
ErrorCodeConflict ErrorCode = "CONFLICT"
|
||||
ErrorCodeQueueFull ErrorCode = "QUEUE_FULL"
|
||||
ErrorCodeNothingPlaying ErrorCode = "NOTHING_PLAYING"
|
||||
ErrorCodeRateLimited ErrorCode = "RATE_LIMITED"
|
||||
ErrorCodeProviderError ErrorCode = "PROVIDER_ERROR"
|
||||
ErrorCodeUnavailable ErrorCode = "UNAVAILABLE"
|
||||
ErrorCodeClientOutdated ErrorCode = "CLIENT_OUTDATED"
|
||||
ErrorCodeInternal ErrorCode = "INTERNAL"
|
||||
)
|
||||
|
||||
type ErrorInfo struct {
|
||||
@@ -200,11 +224,13 @@ type ErrorInfo struct {
|
||||
}
|
||||
|
||||
type GetMeResponse struct {
|
||||
Member Member `json:"member"`
|
||||
Account *Account `json:"account,omitempty"`
|
||||
Member Member `json:"member"`
|
||||
}
|
||||
|
||||
type GetSiteResponse struct {
|
||||
BrandName string `json:"brandName"`
|
||||
BrandName string `json:"brandName"`
|
||||
RegistrationOpen bool `json:"registrationOpen"`
|
||||
}
|
||||
|
||||
type HelloRequest struct {
|
||||
@@ -231,6 +257,19 @@ func (v ListRoomsResponse) MarshalJSON() ([]byte, error) {
|
||||
return json.Marshal(plain(v))
|
||||
}
|
||||
|
||||
type LoginRequest struct {
|
||||
Device *string `json:"device,omitempty"`
|
||||
Password string `json:"password"`
|
||||
Username string `json:"username"`
|
||||
}
|
||||
|
||||
type LoginResponse struct {
|
||||
Account Account `json:"account"`
|
||||
Device string `json:"device"`
|
||||
Member Member `json:"member"`
|
||||
MemberToken string `json:"memberToken"`
|
||||
}
|
||||
|
||||
type Member struct {
|
||||
AvatarHash string `json:"avatarHash"`
|
||||
ID string `json:"id"`
|
||||
@@ -397,6 +436,15 @@ func (v RecommendListResponse) MarshalJSON() ([]byte, error) {
|
||||
return json.Marshal(plain(v))
|
||||
}
|
||||
|
||||
type RegisterRequest struct {
|
||||
Password string `json:"password"`
|
||||
Username string `json:"username"`
|
||||
}
|
||||
|
||||
type RegisterResponse struct {
|
||||
Account Account `json:"account"`
|
||||
}
|
||||
|
||||
type RoomActivityEvent struct {
|
||||
Entry *Entry `json:"entry,omitempty"`
|
||||
Kind ActivityKind `json:"kind"`
|
||||
@@ -609,6 +657,10 @@ var (
|
||||
RouteCreateMember = Route{Pattern: "POST /api/members", Auth: AuthNone, Status: 201}
|
||||
RouteGetMe = Route{Pattern: "GET /api/me", Auth: AuthMember, Status: 200}
|
||||
RouteUpdateMe = Route{Pattern: "PATCH /api/me", Auth: AuthMember, Status: 200}
|
||||
RouteRegister = Route{Pattern: "POST /api/account", Auth: AuthMember, Status: 201}
|
||||
RouteChangePassword = Route{Pattern: "PUT /api/account/password", Auth: AuthMember, Status: 204}
|
||||
RouteLogin = Route{Pattern: "POST /api/sessions", Auth: AuthMember, Status: 201}
|
||||
RouteLogout = Route{Pattern: "DELETE /api/sessions/current", Auth: AuthMember, Status: 204}
|
||||
RouteListRooms = Route{Pattern: "GET /api/rooms", Auth: AuthNone, Status: 200}
|
||||
RouteGetRoom = Route{Pattern: "GET /api/rooms/{ref}", Auth: AuthNone, Status: 200}
|
||||
RouteCreateRoom = Route{Pattern: "POST /api/rooms", Auth: AuthNone, Status: 201, Location: true}
|
||||
@@ -635,6 +687,10 @@ var (
|
||||
RouteAdminListMedia = Route{Pattern: "GET /api/admin/media", Auth: AuthAdmin, Status: 200}
|
||||
RouteAdminDeleteMedia = Route{Pattern: "DELETE /api/admin/media/{key}", Auth: AuthAdmin, Status: 204}
|
||||
RouteAdminUpdateSite = Route{Pattern: "PATCH /api/admin/site", Auth: AuthAdmin, Status: 200}
|
||||
RouteAdminOpenRegistration = Route{Pattern: "PUT /api/admin/registration", Auth: AuthAdmin, Status: 204}
|
||||
RouteAdminCloseRegistration = Route{Pattern: "DELETE /api/admin/registration", Auth: AuthAdmin, Status: 204}
|
||||
RouteAdminGetAccount = Route{Pattern: "GET /api/admin/accounts/{username}", Auth: AuthAdmin, Status: 200}
|
||||
RouteAdminResetAccountPassword = Route{Pattern: "POST /api/admin/accounts/{username}/password", Auth: AuthAdmin, Status: 200}
|
||||
)
|
||||
|
||||
var RoomRoutes = []RoomRoute{
|
||||
@@ -652,17 +708,18 @@ var RoomRoutes = []RoomRoute{
|
||||
}
|
||||
|
||||
var errorCodeHTTPStatus = map[ErrorCode]int{
|
||||
ErrorCodeInvalidArgument: 400,
|
||||
ErrorCodeUnauthorized: 401,
|
||||
ErrorCodeForbidden: 403,
|
||||
ErrorCodeNotFound: 404,
|
||||
ErrorCodeConflict: 409,
|
||||
ErrorCodeQueueFull: 409,
|
||||
ErrorCodeNothingPlaying: 409,
|
||||
ErrorCodeRateLimited: 429,
|
||||
ErrorCodeProviderError: 502,
|
||||
ErrorCodeUnavailable: 503,
|
||||
ErrorCodeInternal: 500,
|
||||
ErrorCodeInvalidArgument: 400,
|
||||
ErrorCodeUnauthorized: 401,
|
||||
ErrorCodeInvalidCredentials: 401,
|
||||
ErrorCodeForbidden: 403,
|
||||
ErrorCodeNotFound: 404,
|
||||
ErrorCodeConflict: 409,
|
||||
ErrorCodeQueueFull: 409,
|
||||
ErrorCodeNothingPlaying: 409,
|
||||
ErrorCodeRateLimited: 429,
|
||||
ErrorCodeProviderError: 502,
|
||||
ErrorCodeUnavailable: 503,
|
||||
ErrorCodeInternal: 500,
|
||||
}
|
||||
|
||||
// IDL 没有为某个取值指定 HTTP 状态码时返回 0。
|
||||
|
||||
Reference in new issue
Block a user