mirror of
https://github.com/shtorm-7/sing-box-extended.git
synced 2026-06-25 03:43:12 +03:00
Add SSH inbound, log level. Update MTPROXY. Fixes
This commit is contained in:
@@ -42,6 +42,7 @@ export type UserType =
|
||||
| "mtproxy"
|
||||
| "naive"
|
||||
| "socks"
|
||||
| "ssh"
|
||||
| "trojan"
|
||||
| "trusttunnel"
|
||||
| "tuic"
|
||||
@@ -57,6 +58,7 @@ export interface User {
|
||||
uuid: string;
|
||||
password: string;
|
||||
secret: string;
|
||||
authorized_keys: string[];
|
||||
flow: string;
|
||||
alter_id: number;
|
||||
created_at: string;
|
||||
@@ -70,6 +72,7 @@ export interface UserCreate {
|
||||
uuid?: string;
|
||||
password?: string;
|
||||
secret?: string;
|
||||
authorized_keys?: string[];
|
||||
flow?: string;
|
||||
alter_id?: number;
|
||||
}
|
||||
@@ -77,6 +80,7 @@ export interface UserUpdate {
|
||||
uuid?: string;
|
||||
password?: string;
|
||||
secret?: string;
|
||||
authorized_keys?: string[];
|
||||
flow?: string;
|
||||
alter_id?: number;
|
||||
}
|
||||
|
||||
@@ -64,7 +64,7 @@ import type { Listable } from "../api/types";
|
||||
import { notifyApiError, useNotify } from "../notifications/NotificationsProvider";
|
||||
import { PageHeader } from "./PageHeader";
|
||||
|
||||
export type FieldType = "text" | "number" | "select" | "multiselect" | "ids" | "uuid";
|
||||
export type FieldType = "text" | "number" | "select" | "multiselect" | "ids" | "uuid" | "string-list";
|
||||
|
||||
// FILTER_WIDTH is the fixed CSS width (px) of a single filter cell in the
|
||||
// filter panel. FILTER_GAP is the flex gap between cells (matches the
|
||||
@@ -126,7 +126,7 @@ export interface FieldSpec<TValue = unknown> {
|
||||
// spec, matching what `emptyForm` would produce.
|
||||
function emptyValueForField(f: FieldSpec): unknown {
|
||||
if (f.defaultValue !== undefined) return f.defaultValue;
|
||||
if (f.type === "multiselect") return [];
|
||||
if (f.type === "multiselect" || f.type === "string-list") return [];
|
||||
return "";
|
||||
}
|
||||
|
||||
@@ -575,7 +575,7 @@ function fieldVisible(
|
||||
// strings, missing selections, and empty arrays for multi-select / ids.
|
||||
function isFieldEmpty(f: FieldSpec, value: unknown): boolean {
|
||||
if (value === undefined || value === null) return true;
|
||||
if (f.type === "multiselect" || f.type === "ids") {
|
||||
if (f.type === "multiselect" || f.type === "ids" || f.type === "string-list") {
|
||||
if (Array.isArray(value)) return value.length === 0;
|
||||
if (typeof value === "string") return value.trim() === "";
|
||||
return true;
|
||||
@@ -4764,6 +4764,40 @@ function CrudDialog({
|
||||
/>
|
||||
);
|
||||
}
|
||||
if (f.type === "string-list") {
|
||||
const arr = Array.isArray(value) ? (value as string[]) : [];
|
||||
return (
|
||||
<Box key={f.name} sx={{ mt: -1 }}>
|
||||
<Typography variant="caption" color={errored ? "error" : "textSecondary"} sx={{ mb: 0.5, ml: "14px", display: "block" }}>
|
||||
{f.label}{f.required ? " *" : ""}
|
||||
</Typography>
|
||||
<Stack spacing={1}>
|
||||
{arr.map((item, idx) => (
|
||||
<Stack key={idx} direction="row" spacing={1} alignItems="center">
|
||||
<TextField
|
||||
fullWidth
|
||||
size="small"
|
||||
value={item}
|
||||
placeholder={`Key ${idx + 1}`}
|
||||
onChange={(e) => {
|
||||
const next = [...arr];
|
||||
next[idx] = e.target.value;
|
||||
set(f.name, next);
|
||||
}}
|
||||
/>
|
||||
<IconButton size="small" color="error" onClick={() => set(f.name, arr.filter((_, i) => i !== idx))}>
|
||||
<DeleteIcon fontSize="small" />
|
||||
</IconButton>
|
||||
</Stack>
|
||||
))}
|
||||
</Stack>
|
||||
<Button size="small" startIcon={<AddIcon />} sx={{ mt: 1 }} onClick={() => set(f.name, [...arr, ""])}>
|
||||
Add
|
||||
</Button>
|
||||
{fieldErr && <Typography variant="caption" color="error" sx={{ mt: 0.5, display: "block" }}>{fieldErr}</Typography>}
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
const isNumber = f.type === "number";
|
||||
// Numeric value of the current cell. Falls back to 0 for the
|
||||
// empty state so the up-arrow always has a sensible base to
|
||||
|
||||
@@ -25,6 +25,7 @@ const USER_TYPES: { value: UserType; label: string }[] = [
|
||||
{ value: "mtproxy", label: "MTProxy" },
|
||||
{ value: "naive", label: "Naive" },
|
||||
{ value: "socks", label: "SOCKS" },
|
||||
{ value: "ssh", label: "SSH" },
|
||||
{ value: "trojan", label: "Trojan" },
|
||||
{ value: "trusttunnel", label: "TrustTunnel" },
|
||||
{ value: "tuic", label: "TUIC" },
|
||||
@@ -44,8 +45,9 @@ const FLOW_OPTIONS: { value: string; label: string }[] = [
|
||||
// same rule up-front (required fields invisible for the current type
|
||||
// are filtered out before validateRequired runs).
|
||||
const SHOW_UUID = new Set<UserType>(["vless", "vmess", "tuic"]);
|
||||
const SHOW_PASSWORD = new Set<UserType>(["anytls", "http", "hysteria", "hysteria2", "mixed", "naive", "socks", "trojan", "trusttunnel", "tuic"]);
|
||||
const SHOW_PASSWORD = new Set<UserType>(["anytls", "http", "hysteria", "hysteria2", "mixed", "naive", "socks", "ssh", "trojan", "trusttunnel", "tuic"]);
|
||||
const SHOW_SECRET = new Set<UserType>(["mtproxy"]);
|
||||
const SHOW_AUTHORIZED_KEYS = new Set<UserType>(["ssh"]);
|
||||
const SHOW_FLOW = new Set<UserType>(["vless"]);
|
||||
const SHOW_ALTER_ID = new Set<UserType>(["vmess"]);
|
||||
|
||||
@@ -103,7 +105,7 @@ export function UsersPage() {
|
||||
options: USER_TYPES,
|
||||
// Switching the user type wipes every credential field so the form
|
||||
// matches the legacy admin's behaviour of starting fresh.
|
||||
clears: ["uuid", "password", "secret", "flow", "alter_id"],
|
||||
clears: ["uuid", "password", "secret", "authorized_keys", "flow", "alter_id"],
|
||||
},
|
||||
// Credential fields: the Go struct validator reports "required" for
|
||||
// whichever of these is missing once the type is chosen, so each one
|
||||
@@ -111,8 +113,9 @@ export function UsersPage() {
|
||||
// are filtered out before validateRequired runs, so e.g. Password is
|
||||
// only enforced for hysteria/hysteria2/trojan/tuic and not for vless.
|
||||
{ name: "uuid", label: "UUID", type: "uuid", required: true, visibleWhen: showFor(SHOW_UUID) },
|
||||
{ name: "password", label: "Password", type: "text", required: true, visibleWhen: showFor(SHOW_PASSWORD) },
|
||||
{ name: "password", label: "Password", type: "text", visibleWhen: showFor(SHOW_PASSWORD) },
|
||||
{ name: "secret", label: "Secret", type: "text", required: true, visibleWhen: showFor(SHOW_SECRET) },
|
||||
{ name: "authorized_keys", label: "Authorized Keys", type: "string-list", visibleWhen: showFor(SHOW_AUTHORIZED_KEYS) },
|
||||
{
|
||||
name: "flow",
|
||||
label: "Flow",
|
||||
@@ -135,6 +138,7 @@ export function UsersPage() {
|
||||
uuid: u.uuid,
|
||||
password: u.password,
|
||||
secret: u.secret,
|
||||
authorized_keys: u.authorized_keys ?? [],
|
||||
flow: u.flow,
|
||||
alter_id: u.alter_id,
|
||||
}),
|
||||
@@ -146,6 +150,7 @@ export function UsersPage() {
|
||||
uuid: f.uuid ? String(f.uuid).trim() : undefined,
|
||||
password: f.password ? String(f.password) : undefined,
|
||||
secret: f.secret ? String(f.secret) : undefined,
|
||||
authorized_keys: Array.isArray(f.authorized_keys) ? (f.authorized_keys as string[]).filter(Boolean) : undefined,
|
||||
flow: f.flow ? String(f.flow) : undefined,
|
||||
alter_id: f.alter_id !== undefined && f.alter_id !== "" ? Number(f.alter_id) : undefined,
|
||||
}),
|
||||
@@ -154,6 +159,7 @@ export function UsersPage() {
|
||||
if (f.uuid && String(f.uuid).trim() !== "") out.uuid = String(f.uuid).trim();
|
||||
if (f.password !== undefined && f.password !== "") out.password = String(f.password);
|
||||
if (f.secret !== undefined && f.secret !== "") out.secret = String(f.secret);
|
||||
if (Array.isArray(f.authorized_keys) && (f.authorized_keys as string[]).filter(Boolean).length > 0) out.authorized_keys = (f.authorized_keys as string[]).filter(Boolean);
|
||||
if (f.flow !== undefined && f.flow !== "") out.flow = String(f.flow);
|
||||
if (f.alter_id !== undefined && f.alter_id !== "") out.alter_id = Number(f.alter_id);
|
||||
return out;
|
||||
|
||||
@@ -49,46 +49,50 @@ type BaseNode struct {
|
||||
}
|
||||
|
||||
type User struct {
|
||||
ID int `json:"id" validate:"required"`
|
||||
SquadIDs []int `json:"squad_ids" validate:"required,min=1"`
|
||||
Username string `json:"username" validate:"required"`
|
||||
Inbound string `json:"inbound" validate:"required"`
|
||||
Type string `json:"type" validate:"required"`
|
||||
UUID string `json:"uuid" validate:"required"`
|
||||
Password string `json:"password" validate:"required"`
|
||||
Secret string `json:"secret" validate:"required"`
|
||||
Flow string `json:"flow" validate:"required"`
|
||||
AlterID int `json:"alter_id" validate:"required"`
|
||||
CreatedAt time.Time `json:"created_at" validate:"required"`
|
||||
UpdatedAt time.Time `json:"updated_at" validate:"required"`
|
||||
ID int `json:"id" validate:"required"`
|
||||
SquadIDs []int `json:"squad_ids" validate:"required,min=1"`
|
||||
Username string `json:"username" validate:"required"`
|
||||
Inbound string `json:"inbound" validate:"required"`
|
||||
Type string `json:"type" validate:"required"`
|
||||
UUID string `json:"uuid" validate:"required"`
|
||||
Password string `json:"password" validate:"required"`
|
||||
Secret string `json:"secret" validate:"required"`
|
||||
AuthorizedKeys []string `json:"authorized_keys" validate:"omitempty"`
|
||||
Flow string `json:"flow" validate:"required"`
|
||||
AlterID int `json:"alter_id" validate:"required"`
|
||||
CreatedAt time.Time `json:"created_at" validate:"required"`
|
||||
UpdatedAt time.Time `json:"updated_at" validate:"required"`
|
||||
}
|
||||
|
||||
type UserCreate struct {
|
||||
SquadIDs []int `json:"squad_ids" validate:"required,min=1"`
|
||||
Username string `json:"username" validate:"required"`
|
||||
Inbound string `json:"inbound" validate:"required"`
|
||||
Type string `json:"type" validate:"required,oneof=anytls http hysteria hysteria2 mixed mtproxy naive socks trojan trusttunnel tuic vless vmess"`
|
||||
UUID string `json:"uuid" validate:"omitempty,uuid4"`
|
||||
Password string `json:"password" validate:"omitempty"`
|
||||
Secret string `json:"secret" validate:"omitempty"`
|
||||
Flow string `json:"flow" validate:"omitempty"`
|
||||
AlterID int `json:"alter_id" validate:"omitempty"`
|
||||
SquadIDs []int `json:"squad_ids" validate:"required,min=1"`
|
||||
Username string `json:"username" validate:"required"`
|
||||
Inbound string `json:"inbound" validate:"required"`
|
||||
Type string `json:"type" validate:"required,oneof=anytls http hysteria hysteria2 mixed mtproxy naive socks ssh trojan trusttunnel tuic vless vmess"`
|
||||
UUID string `json:"uuid" validate:"omitempty,uuid4"`
|
||||
Password string `json:"password" validate:"omitempty"`
|
||||
Secret string `json:"secret" validate:"omitempty"`
|
||||
AuthorizedKeys []string `json:"authorized_keys" validate:"omitempty"`
|
||||
Flow string `json:"flow" validate:"omitempty"`
|
||||
AlterID int `json:"alter_id" validate:"omitempty"`
|
||||
}
|
||||
|
||||
type UserUpdate struct {
|
||||
UUID string `json:"uuid" validate:"omitempty,uuid4"`
|
||||
Password string `json:"password" validate:"omitempty"`
|
||||
Secret string `json:"secret" validate:"omitempty"`
|
||||
Flow string `json:"flow" validate:"omitempty"`
|
||||
AlterID int `json:"alter_id" validate:"omitempty"`
|
||||
UUID string `json:"uuid" validate:"omitempty,uuid4"`
|
||||
Password string `json:"password" validate:"omitempty"`
|
||||
Secret string `json:"secret" validate:"omitempty"`
|
||||
AuthorizedKeys []string `json:"authorized_keys" validate:"omitempty"`
|
||||
Flow string `json:"flow" validate:"omitempty"`
|
||||
AlterID int `json:"alter_id" validate:"omitempty"`
|
||||
}
|
||||
|
||||
type BaseUser struct {
|
||||
UUID string `json:"uuid" validate:"omitempty,uuid4"`
|
||||
Password string `json:"password" validate:"omitempty"`
|
||||
Secret string `json:"secret" validate:"omitempty"`
|
||||
Flow string `json:"flow" validate:"omitempty"`
|
||||
AlterID int `json:"alter_id" validate:"omitempty"`
|
||||
UUID string `json:"uuid" validate:"omitempty,uuid4"`
|
||||
Password string `json:"password" validate:"omitempty"`
|
||||
Secret string `json:"secret" validate:"omitempty"`
|
||||
AuthorizedKeys []string `json:"authorized_keys" validate:"omitempty"`
|
||||
Flow string `json:"flow" validate:"omitempty"`
|
||||
AlterID int `json:"alter_id" validate:"omitempty"`
|
||||
}
|
||||
|
||||
type ConnectionLimiter struct {
|
||||
@@ -261,5 +265,3 @@ type BaseRateLimiter struct {
|
||||
Count uint32 `json:"count" validate:"excluded_if=Strategy bypass,required_unless=Strategy bypass"`
|
||||
Interval string `json:"interval" validate:"excluded_if=Strategy bypass,required_unless=Strategy bypass"`
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -51,5 +51,3 @@ type Repository interface {
|
||||
UpdateRateLimiter(id int, limiter RateLimiterUpdate) (RateLimiter, error)
|
||||
DeleteRateLimiter(id int) (RateLimiter, error)
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -331,6 +331,12 @@ var migrations = map[string]string{
|
||||
DROP TABLE IF EXISTS traffic_limiter_to_squad;
|
||||
DROP TABLE IF EXISTS traffic_limiters;
|
||||
`,
|
||||
"3_add_authorized_keys.up.sql": `
|
||||
ALTER TABLE users ADD COLUMN authorized_keys JSONB NOT NULL DEFAULT '[]'::jsonb;
|
||||
`,
|
||||
"3_add_authorized_keys.down.sql": `
|
||||
ALTER TABLE users DROP COLUMN authorized_keys;
|
||||
`,
|
||||
}
|
||||
|
||||
func Migrate(db *sql.DB) error {
|
||||
|
||||
@@ -515,6 +515,11 @@ func (r *PostgreSQLRepository) CreateUser(user constant.UserCreate) (constant.Us
|
||||
}
|
||||
defer tx.Rollback(r.ctx)
|
||||
now := time.Now()
|
||||
authorizedKeysJSON, err := marshalStringSlice(user.AuthorizedKeys)
|
||||
if err != nil {
|
||||
return u, err
|
||||
}
|
||||
var authorizedKeys stringSliceJSON
|
||||
err = tx.QueryRow(
|
||||
r.ctx, `
|
||||
INSERT INTO users (
|
||||
@@ -524,12 +529,13 @@ func (r *PostgreSQLRepository) CreateUser(user constant.UserCreate) (constant.Us
|
||||
uuid,
|
||||
password,
|
||||
secret,
|
||||
authorized_keys,
|
||||
flow,
|
||||
alter_id,
|
||||
created_at,
|
||||
updated_at
|
||||
)
|
||||
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)
|
||||
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11)
|
||||
RETURNING
|
||||
id,
|
||||
username,
|
||||
@@ -538,6 +544,7 @@ func (r *PostgreSQLRepository) CreateUser(user constant.UserCreate) (constant.Us
|
||||
uuid,
|
||||
password,
|
||||
secret,
|
||||
authorized_keys,
|
||||
flow,
|
||||
alter_id,
|
||||
created_at,
|
||||
@@ -549,6 +556,7 @@ func (r *PostgreSQLRepository) CreateUser(user constant.UserCreate) (constant.Us
|
||||
user.UUID,
|
||||
user.Password,
|
||||
user.Secret,
|
||||
authorizedKeysJSON,
|
||||
user.Flow,
|
||||
user.AlterID,
|
||||
now,
|
||||
@@ -561,6 +569,7 @@ func (r *PostgreSQLRepository) CreateUser(user constant.UserCreate) (constant.Us
|
||||
&u.UUID,
|
||||
&u.Password,
|
||||
&u.Secret,
|
||||
&authorizedKeys,
|
||||
&u.Flow,
|
||||
&u.AlterID,
|
||||
&u.CreatedAt,
|
||||
@@ -569,6 +578,7 @@ func (r *PostgreSQLRepository) CreateUser(user constant.UserCreate) (constant.Us
|
||||
if err != nil {
|
||||
return u, err
|
||||
}
|
||||
u.AuthorizedKeys = []string(authorizedKeys)
|
||||
rows := make([][]any, len(user.SquadIDs))
|
||||
for i, squadID := range user.SquadIDs {
|
||||
rows[i] = []any{u.ID, squadID}
|
||||
@@ -605,6 +615,7 @@ func (r *PostgreSQLRepository) GetUsers(filters map[string][]string) ([]constant
|
||||
"uuid",
|
||||
"password",
|
||||
"secret",
|
||||
"authorized_keys",
|
||||
"flow",
|
||||
"alter_id",
|
||||
"created_at",
|
||||
@@ -636,6 +647,7 @@ func (r *PostgreSQLRepository) GetUsers(filters map[string][]string) ([]constant
|
||||
&u.UUID,
|
||||
&u.Password,
|
||||
&u.Secret,
|
||||
&u.AuthorizedKeys,
|
||||
&u.Flow,
|
||||
&u.AlterID,
|
||||
&u.CreatedAt,
|
||||
@@ -681,6 +693,7 @@ func (r *PostgreSQLRepository) GetUser(id int) (constant.User, error) {
|
||||
uuid,
|
||||
password,
|
||||
secret,
|
||||
authorized_keys,
|
||||
flow,
|
||||
alter_id,
|
||||
created_at,
|
||||
@@ -696,6 +709,7 @@ func (r *PostgreSQLRepository) GetUser(id int) (constant.User, error) {
|
||||
&u.UUID,
|
||||
&u.Password,
|
||||
&u.Secret,
|
||||
&u.AuthorizedKeys,
|
||||
&u.Flow,
|
||||
&u.AlterID,
|
||||
&u.CreatedAt,
|
||||
@@ -706,17 +720,22 @@ func (r *PostgreSQLRepository) GetUser(id int) (constant.User, error) {
|
||||
|
||||
func (r *PostgreSQLRepository) UpdateUser(id int, user constant.UserUpdate) (constant.User, error) {
|
||||
var u constant.User
|
||||
err := r.db.QueryRow(
|
||||
authorizedKeysJSON, err := marshalStringSlice(user.AuthorizedKeys)
|
||||
if err != nil {
|
||||
return u, err
|
||||
}
|
||||
err = r.db.QueryRow(
|
||||
r.ctx, `
|
||||
UPDATE users
|
||||
SET
|
||||
uuid = $1,
|
||||
password = $2,
|
||||
secret = $3,
|
||||
flow = $4,
|
||||
alter_id = $5,
|
||||
updated_at = $6
|
||||
WHERE id = $7
|
||||
authorized_keys = $4,
|
||||
flow = $5,
|
||||
alter_id = $6,
|
||||
updated_at = $7
|
||||
WHERE id = $8
|
||||
RETURNING
|
||||
id,
|
||||
ARRAY(
|
||||
@@ -730,6 +749,7 @@ func (r *PostgreSQLRepository) UpdateUser(id int, user constant.UserUpdate) (con
|
||||
uuid,
|
||||
password,
|
||||
secret,
|
||||
authorized_keys,
|
||||
flow,
|
||||
alter_id,
|
||||
created_at,
|
||||
@@ -738,6 +758,7 @@ func (r *PostgreSQLRepository) UpdateUser(id int, user constant.UserUpdate) (con
|
||||
user.UUID,
|
||||
user.Password,
|
||||
user.Secret,
|
||||
authorizedKeysJSON,
|
||||
user.Flow,
|
||||
user.AlterID,
|
||||
time.Now(),
|
||||
@@ -751,6 +772,7 @@ func (r *PostgreSQLRepository) UpdateUser(id int, user constant.UserUpdate) (con
|
||||
&u.UUID,
|
||||
&u.Password,
|
||||
&u.Secret,
|
||||
&u.AuthorizedKeys,
|
||||
&u.Flow,
|
||||
&u.AlterID,
|
||||
&u.CreatedAt,
|
||||
@@ -777,6 +799,7 @@ func (r *PostgreSQLRepository) DeleteUser(id int) (constant.User, error) {
|
||||
uuid,
|
||||
password,
|
||||
secret,
|
||||
authorized_keys,
|
||||
flow,
|
||||
alter_id,
|
||||
created_at,
|
||||
@@ -790,6 +813,7 @@ func (r *PostgreSQLRepository) DeleteUser(id int) (constant.User, error) {
|
||||
&u.UUID,
|
||||
&u.Password,
|
||||
&u.Secret,
|
||||
&u.AuthorizedKeys,
|
||||
&u.Flow,
|
||||
&u.AlterID,
|
||||
&u.CreatedAt,
|
||||
@@ -2143,11 +2167,11 @@ func init() {
|
||||
"updated_at_end": LessThanFilter("updated_at"),
|
||||
"sort_asc": ReplacedSortAscFilter(
|
||||
map[string]string{"speed": "raw_speed"},
|
||||
[]string{"id", "username", "outbound", "strategy", "mode", "raw_speed", "created_at", "updated_at"},
|
||||
[]string{"id", "username", "outbound", "strategy", "connection_type", "mode", "raw_speed", "created_at", "updated_at"},
|
||||
),
|
||||
"sort_desc": ReplacedSortDescFilter(
|
||||
map[string]string{"speed": "raw_speed"},
|
||||
[]string{"id", "username", "outbound", "strategy", "mode", "raw_speed", "created_at", "updated_at"},
|
||||
[]string{"id", "username", "outbound", "strategy", "connection_type", "mode", "raw_speed", "created_at", "updated_at"},
|
||||
),
|
||||
"offset": OffsetFilter(),
|
||||
"limit": LimitFilter(),
|
||||
|
||||
@@ -213,6 +213,12 @@ var migrations = map[string]string{
|
||||
DROP TABLE IF EXISTS nodes;
|
||||
DROP TABLE IF EXISTS squads;
|
||||
`,
|
||||
"2_add_authorized_keys.up.sql": `
|
||||
ALTER TABLE users ADD COLUMN authorized_keys TEXT NOT NULL DEFAULT '[]';
|
||||
`,
|
||||
"2_add_authorized_keys.down.sql": `
|
||||
ALTER TABLE users DROP COLUMN authorized_keys;
|
||||
`,
|
||||
}
|
||||
|
||||
func Migrate(db *sql.DB) error {
|
||||
|
||||
@@ -510,6 +510,11 @@ func (r *SQLiteRepository) CreateUser(user constant.UserCreate) (constant.User,
|
||||
}
|
||||
defer tx.Rollback()
|
||||
now := time.Now()
|
||||
authorizedKeysJSON, err := marshalStringSlice(user.AuthorizedKeys)
|
||||
if err != nil {
|
||||
return u, err
|
||||
}
|
||||
var authorizedKeys stringSliceJSON
|
||||
err = tx.QueryRowContext(
|
||||
r.ctx, `
|
||||
INSERT INTO users (
|
||||
@@ -519,12 +524,13 @@ func (r *SQLiteRepository) CreateUser(user constant.UserCreate) (constant.User,
|
||||
uuid,
|
||||
password,
|
||||
secret,
|
||||
authorized_keys,
|
||||
flow,
|
||||
alter_id,
|
||||
created_at,
|
||||
updated_at
|
||||
)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||
RETURNING
|
||||
id,
|
||||
username,
|
||||
@@ -533,6 +539,7 @@ func (r *SQLiteRepository) CreateUser(user constant.UserCreate) (constant.User,
|
||||
uuid,
|
||||
password,
|
||||
secret,
|
||||
authorized_keys,
|
||||
flow,
|
||||
alter_id,
|
||||
created_at,
|
||||
@@ -544,6 +551,7 @@ func (r *SQLiteRepository) CreateUser(user constant.UserCreate) (constant.User,
|
||||
user.UUID,
|
||||
user.Password,
|
||||
user.Secret,
|
||||
authorizedKeysJSON,
|
||||
user.Flow,
|
||||
user.AlterID,
|
||||
now,
|
||||
@@ -556,6 +564,7 @@ func (r *SQLiteRepository) CreateUser(user constant.UserCreate) (constant.User,
|
||||
&u.UUID,
|
||||
&u.Password,
|
||||
&u.Secret,
|
||||
&authorizedKeys,
|
||||
&u.Flow,
|
||||
&u.AlterID,
|
||||
&u.CreatedAt,
|
||||
@@ -564,6 +573,7 @@ func (r *SQLiteRepository) CreateUser(user constant.UserCreate) (constant.User,
|
||||
if err != nil {
|
||||
return u, err
|
||||
}
|
||||
u.AuthorizedKeys = []string(authorizedKeys)
|
||||
stmt, err := tx.PrepareContext(r.ctx, `INSERT INTO user_to_squad (user_id, squad_id) VALUES (?, ?)`)
|
||||
if err != nil {
|
||||
return u, err
|
||||
@@ -596,6 +606,7 @@ func (r *SQLiteRepository) GetUsers(filters map[string][]string) ([]constant.Use
|
||||
"uuid",
|
||||
"password",
|
||||
"secret",
|
||||
"authorized_keys",
|
||||
"flow",
|
||||
"alter_id",
|
||||
"created_at",
|
||||
@@ -619,6 +630,7 @@ func (r *SQLiteRepository) GetUsers(filters map[string][]string) ([]constant.Use
|
||||
for rows.Next() {
|
||||
var u constant.User
|
||||
var squadIDs intSliceJSON
|
||||
var authorizedKeys stringSliceJSON
|
||||
if err := rows.Scan(
|
||||
&u.ID,
|
||||
&squadIDs,
|
||||
@@ -628,6 +640,7 @@ func (r *SQLiteRepository) GetUsers(filters map[string][]string) ([]constant.Use
|
||||
&u.UUID,
|
||||
&u.Password,
|
||||
&u.Secret,
|
||||
&authorizedKeys,
|
||||
&u.Flow,
|
||||
&u.AlterID,
|
||||
&u.CreatedAt,
|
||||
@@ -636,6 +649,7 @@ func (r *SQLiteRepository) GetUsers(filters map[string][]string) ([]constant.Use
|
||||
return nil, err
|
||||
}
|
||||
u.SquadIDs = []int(squadIDs)
|
||||
u.AuthorizedKeys = []string(authorizedKeys)
|
||||
result = append(result, u)
|
||||
}
|
||||
return result, rows.Err()
|
||||
@@ -661,6 +675,7 @@ func (r *SQLiteRepository) GetUsersCount(filters map[string][]string) (int, erro
|
||||
func (r *SQLiteRepository) GetUser(id int) (constant.User, error) {
|
||||
var u constant.User
|
||||
var squadIDs intSliceJSON
|
||||
var authorizedKeys stringSliceJSON
|
||||
err := r.db.QueryRowContext(r.ctx, `
|
||||
SELECT
|
||||
id,
|
||||
@@ -675,6 +690,7 @@ func (r *SQLiteRepository) GetUser(id int) (constant.User, error) {
|
||||
uuid,
|
||||
password,
|
||||
secret,
|
||||
authorized_keys,
|
||||
flow,
|
||||
alter_id,
|
||||
created_at,
|
||||
@@ -690,25 +706,33 @@ func (r *SQLiteRepository) GetUser(id int) (constant.User, error) {
|
||||
&u.UUID,
|
||||
&u.Password,
|
||||
&u.Secret,
|
||||
&authorizedKeys,
|
||||
&u.Flow,
|
||||
&u.AlterID,
|
||||
&u.CreatedAt,
|
||||
&u.UpdatedAt,
|
||||
)
|
||||
u.SquadIDs = []int(squadIDs)
|
||||
u.AuthorizedKeys = []string(authorizedKeys)
|
||||
return u, notFoundErr(err)
|
||||
}
|
||||
|
||||
func (r *SQLiteRepository) UpdateUser(id int, user constant.UserUpdate) (constant.User, error) {
|
||||
var u constant.User
|
||||
var squadIDs intSliceJSON
|
||||
err := r.db.QueryRowContext(
|
||||
var authorizedKeys stringSliceJSON
|
||||
authorizedKeysJSON, err := marshalStringSlice(user.AuthorizedKeys)
|
||||
if err != nil {
|
||||
return u, err
|
||||
}
|
||||
err = r.db.QueryRowContext(
|
||||
r.ctx, `
|
||||
UPDATE users
|
||||
SET
|
||||
uuid = ?,
|
||||
password = ?,
|
||||
secret = ?,
|
||||
authorized_keys = ?,
|
||||
flow = ?,
|
||||
alter_id = ?,
|
||||
updated_at = ?
|
||||
@@ -726,6 +750,7 @@ func (r *SQLiteRepository) UpdateUser(id int, user constant.UserUpdate) (constan
|
||||
uuid,
|
||||
password,
|
||||
secret,
|
||||
authorized_keys,
|
||||
flow,
|
||||
alter_id,
|
||||
created_at,
|
||||
@@ -734,6 +759,7 @@ func (r *SQLiteRepository) UpdateUser(id int, user constant.UserUpdate) (constan
|
||||
user.UUID,
|
||||
user.Password,
|
||||
user.Secret,
|
||||
authorizedKeysJSON,
|
||||
user.Flow,
|
||||
user.AlterID,
|
||||
time.Now(),
|
||||
@@ -747,18 +773,21 @@ func (r *SQLiteRepository) UpdateUser(id int, user constant.UserUpdate) (constan
|
||||
&u.UUID,
|
||||
&u.Password,
|
||||
&u.Secret,
|
||||
&authorizedKeys,
|
||||
&u.Flow,
|
||||
&u.AlterID,
|
||||
&u.CreatedAt,
|
||||
&u.UpdatedAt,
|
||||
)
|
||||
u.SquadIDs = []int(squadIDs)
|
||||
u.AuthorizedKeys = []string(authorizedKeys)
|
||||
return u, err
|
||||
}
|
||||
|
||||
func (r *SQLiteRepository) DeleteUser(id int) (constant.User, error) {
|
||||
var u constant.User
|
||||
var squadIDs intSliceJSON
|
||||
var authorizedKeys stringSliceJSON
|
||||
err := r.db.QueryRowContext(r.ctx, `
|
||||
DELETE FROM users
|
||||
WHERE id = ?
|
||||
@@ -775,6 +804,7 @@ func (r *SQLiteRepository) DeleteUser(id int) (constant.User, error) {
|
||||
uuid,
|
||||
password,
|
||||
secret,
|
||||
authorized_keys,
|
||||
flow,
|
||||
alter_id,
|
||||
created_at,
|
||||
@@ -788,12 +818,14 @@ func (r *SQLiteRepository) DeleteUser(id int) (constant.User, error) {
|
||||
&u.UUID,
|
||||
&u.Password,
|
||||
&u.Secret,
|
||||
&authorizedKeys,
|
||||
&u.Flow,
|
||||
&u.AlterID,
|
||||
&u.CreatedAt,
|
||||
&u.UpdatedAt,
|
||||
)
|
||||
u.SquadIDs = []int(squadIDs)
|
||||
u.AuthorizedKeys = []string(authorizedKeys)
|
||||
return u, err
|
||||
}
|
||||
|
||||
@@ -2160,11 +2192,11 @@ func init() {
|
||||
"updated_at_end": LessThanFilter("updated_at"),
|
||||
"sort_asc": ReplacedSortAscFilter(
|
||||
map[string]string{"speed": "raw_speed"},
|
||||
[]string{"id", "username", "outbound", "strategy", "mode", "raw_speed", "created_at", "updated_at"},
|
||||
[]string{"id", "username", "outbound", "strategy", "connection_type", "mode", "raw_speed", "created_at", "updated_at"},
|
||||
),
|
||||
"sort_desc": ReplacedSortDescFilter(
|
||||
map[string]string{"speed": "raw_speed"},
|
||||
[]string{"id", "username", "outbound", "strategy", "mode", "raw_speed", "created_at", "updated_at"},
|
||||
[]string{"id", "username", "outbound", "strategy", "connection_type", "mode", "raw_speed", "created_at", "updated_at"},
|
||||
),
|
||||
"offset": OffsetFilter(),
|
||||
"limit": LimitFilter(),
|
||||
|
||||
@@ -32,10 +32,10 @@ func RegisterService(registry *boxService.Registry) {
|
||||
|
||||
type Service struct {
|
||||
boxService.Adapter
|
||||
ctx context.Context
|
||||
logger log.ContextLogger
|
||||
repository constant.Repository
|
||||
nodes map[string]constant.ConnectedNode
|
||||
ctx context.Context
|
||||
logger log.ContextLogger
|
||||
repository constant.Repository
|
||||
nodes map[string]constant.ConnectedNode
|
||||
|
||||
limiterLocks map[int]map[string]*cache.Cache[string, struct{}]
|
||||
trafficUsage map[int]*TrafficUsage
|
||||
@@ -93,6 +93,10 @@ func NewService(ctx context.Context, logger log.ContextLogger, tag string, optio
|
||||
if user.Password == "" {
|
||||
sl.ReportError(user.Password, "password", "Password", "required", "")
|
||||
}
|
||||
case "ssh":
|
||||
if user.Password == "" && len(user.AuthorizedKeys) == 0 {
|
||||
sl.ReportError(user.Password, "password", "Password", "required_without", "")
|
||||
}
|
||||
case "mtproxy":
|
||||
if user.Secret == "" {
|
||||
sl.ReportError(user.Secret, "secret", "Secret", "required", "")
|
||||
|
||||
@@ -59,18 +59,19 @@ func convertNode(v *pb.Node) CM.Node {
|
||||
|
||||
func convertUser(v *pb.User) CM.User {
|
||||
return CM.User{
|
||||
ID: int(v.GetId()),
|
||||
SquadIDs: toIntSlice(v.GetSquadIds()),
|
||||
Username: v.GetUsername(),
|
||||
Inbound: v.GetInbound(),
|
||||
Type: v.GetType(),
|
||||
UUID: v.GetUuid(),
|
||||
Password: v.GetPassword(),
|
||||
Secret: v.GetSecret(),
|
||||
Flow: v.GetFlow(),
|
||||
AlterID: int(v.GetAlterId()),
|
||||
CreatedAt: timeFromNano(v.GetCreatedAt()),
|
||||
UpdatedAt: timeFromNano(v.GetUpdatedAt()),
|
||||
ID: int(v.GetId()),
|
||||
SquadIDs: toIntSlice(v.GetSquadIds()),
|
||||
Username: v.GetUsername(),
|
||||
Inbound: v.GetInbound(),
|
||||
Type: v.GetType(),
|
||||
UUID: v.GetUuid(),
|
||||
Password: v.GetPassword(),
|
||||
Secret: v.GetSecret(),
|
||||
AuthorizedKeys: v.GetAuthorizedKeys(),
|
||||
Flow: v.GetFlow(),
|
||||
AlterID: int(v.GetAlterId()),
|
||||
CreatedAt: timeFromNano(v.GetCreatedAt()),
|
||||
UpdatedAt: timeFromNano(v.GetUpdatedAt()),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -83,7 +84,7 @@ func convertBandwidthLimiter(v *pb.BandwidthLimiter) CM.BandwidthLimiter {
|
||||
Strategy: v.GetStrategy(),
|
||||
ConnectionType: v.GetConnectionType(),
|
||||
Mode: v.GetMode(),
|
||||
FlowKeys: v.GetFlowKeys(),
|
||||
FlowKeys: v.GetFlowKeys(),
|
||||
Speed: v.GetSpeed(),
|
||||
RawSpeed: v.GetRawSpeed(),
|
||||
CreatedAt: timeFromNano(v.GetCreatedAt()),
|
||||
|
||||
@@ -188,15 +188,16 @@ func (s *Client) CreateUser(in CM.UserCreate) (CM.User, error) {
|
||||
return CM.User{}, err
|
||||
}
|
||||
reply, err := c.CreateUser(s.callContext(), &pb.UserCreate{
|
||||
SquadIds: toInt32Slice(in.SquadIDs),
|
||||
Username: in.Username,
|
||||
Inbound: in.Inbound,
|
||||
Type: in.Type,
|
||||
Uuid: in.UUID,
|
||||
Password: in.Password,
|
||||
Secret: in.Secret,
|
||||
Flow: in.Flow,
|
||||
AlterId: int32(in.AlterID),
|
||||
SquadIds: toInt32Slice(in.SquadIDs),
|
||||
Username: in.Username,
|
||||
Inbound: in.Inbound,
|
||||
Type: in.Type,
|
||||
Uuid: in.UUID,
|
||||
Password: in.Password,
|
||||
Secret: in.Secret,
|
||||
AuthorizedKeys: in.AuthorizedKeys,
|
||||
Flow: in.Flow,
|
||||
AlterId: int32(in.AlterID),
|
||||
})
|
||||
if err != nil {
|
||||
return CM.User{}, mapError(err)
|
||||
@@ -252,11 +253,12 @@ func (s *Client) UpdateUser(id int, in CM.UserUpdate) (CM.User, error) {
|
||||
reply, err := c.UpdateUser(s.callContext(), &pb.UserUpdateRequest{
|
||||
Id: int32(id),
|
||||
Update: &pb.UserUpdate{
|
||||
Uuid: in.UUID,
|
||||
Password: in.Password,
|
||||
Secret: in.Secret,
|
||||
Flow: in.Flow,
|
||||
AlterId: int32(in.AlterID),
|
||||
Uuid: in.UUID,
|
||||
Password: in.Password,
|
||||
Secret: in.Secret,
|
||||
AuthorizedKeys: in.AuthorizedKeys,
|
||||
Flow: in.Flow,
|
||||
AlterId: int32(in.AlterID),
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
@@ -289,7 +291,7 @@ func (s *Client) CreateBandwidthLimiter(in CM.BandwidthLimiterCreate) (CM.Bandwi
|
||||
Strategy: in.Strategy,
|
||||
ConnectionType: in.ConnectionType,
|
||||
Mode: in.Mode,
|
||||
FlowKeys: in.FlowKeys,
|
||||
FlowKeys: in.FlowKeys,
|
||||
Speed: in.Speed,
|
||||
})
|
||||
if err != nil {
|
||||
@@ -351,7 +353,7 @@ func (s *Client) UpdateBandwidthLimiter(id int, in CM.BandwidthLimiterUpdate) (C
|
||||
Strategy: in.Strategy,
|
||||
ConnectionType: in.ConnectionType,
|
||||
Mode: in.Mode,
|
||||
FlowKeys: in.FlowKeys,
|
||||
FlowKeys: in.FlowKeys,
|
||||
Speed: in.Speed,
|
||||
},
|
||||
})
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||
// versions:
|
||||
// protoc-gen-go v1.36.11
|
||||
// protoc v6.31.1
|
||||
// protoc v7.34.1
|
||||
// source: service/manager_api/grpc/manager/manager.proto
|
||||
|
||||
package manager
|
||||
@@ -550,21 +550,22 @@ func (x *NodeUpdateRequest) GetUpdate() *NodeUpdate {
|
||||
}
|
||||
|
||||
type User struct {
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
Id int32 `protobuf:"varint,1,opt,name=id,proto3" json:"id,omitempty"`
|
||||
SquadIds []int32 `protobuf:"varint,2,rep,packed,name=squad_ids,json=squadIds,proto3" json:"squad_ids,omitempty"`
|
||||
Username string `protobuf:"bytes,3,opt,name=username,proto3" json:"username,omitempty"`
|
||||
Inbound string `protobuf:"bytes,4,opt,name=inbound,proto3" json:"inbound,omitempty"`
|
||||
Type string `protobuf:"bytes,5,opt,name=type,proto3" json:"type,omitempty"`
|
||||
Uuid string `protobuf:"bytes,6,opt,name=uuid,proto3" json:"uuid,omitempty"`
|
||||
Password string `protobuf:"bytes,7,opt,name=password,proto3" json:"password,omitempty"`
|
||||
Secret string `protobuf:"bytes,8,opt,name=secret,proto3" json:"secret,omitempty"`
|
||||
Flow string `protobuf:"bytes,9,opt,name=flow,proto3" json:"flow,omitempty"`
|
||||
AlterId int32 `protobuf:"varint,10,opt,name=alter_id,json=alterId,proto3" json:"alter_id,omitempty"`
|
||||
CreatedAt int64 `protobuf:"varint,11,opt,name=created_at,json=createdAt,proto3" json:"created_at,omitempty"`
|
||||
UpdatedAt int64 `protobuf:"varint,12,opt,name=updated_at,json=updatedAt,proto3" json:"updated_at,omitempty"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
Id int32 `protobuf:"varint,1,opt,name=id,proto3" json:"id,omitempty"`
|
||||
SquadIds []int32 `protobuf:"varint,2,rep,packed,name=squad_ids,json=squadIds,proto3" json:"squad_ids,omitempty"`
|
||||
Username string `protobuf:"bytes,3,opt,name=username,proto3" json:"username,omitempty"`
|
||||
Inbound string `protobuf:"bytes,4,opt,name=inbound,proto3" json:"inbound,omitempty"`
|
||||
Type string `protobuf:"bytes,5,opt,name=type,proto3" json:"type,omitempty"`
|
||||
Uuid string `protobuf:"bytes,6,opt,name=uuid,proto3" json:"uuid,omitempty"`
|
||||
Password string `protobuf:"bytes,7,opt,name=password,proto3" json:"password,omitempty"`
|
||||
Secret string `protobuf:"bytes,8,opt,name=secret,proto3" json:"secret,omitempty"`
|
||||
AuthorizedKeys []string `protobuf:"bytes,13,rep,name=authorized_keys,json=authorizedKeys,proto3" json:"authorized_keys,omitempty"`
|
||||
Flow string `protobuf:"bytes,9,opt,name=flow,proto3" json:"flow,omitempty"`
|
||||
AlterId int32 `protobuf:"varint,10,opt,name=alter_id,json=alterId,proto3" json:"alter_id,omitempty"`
|
||||
CreatedAt int64 `protobuf:"varint,11,opt,name=created_at,json=createdAt,proto3" json:"created_at,omitempty"`
|
||||
UpdatedAt int64 `protobuf:"varint,12,opt,name=updated_at,json=updatedAt,proto3" json:"updated_at,omitempty"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
||||
func (x *User) Reset() {
|
||||
@@ -653,6 +654,13 @@ func (x *User) GetSecret() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *User) GetAuthorizedKeys() []string {
|
||||
if x != nil {
|
||||
return x.AuthorizedKeys
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *User) GetFlow() string {
|
||||
if x != nil {
|
||||
return x.Flow
|
||||
@@ -682,18 +690,19 @@ func (x *User) GetUpdatedAt() int64 {
|
||||
}
|
||||
|
||||
type UserCreate struct {
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
SquadIds []int32 `protobuf:"varint,1,rep,packed,name=squad_ids,json=squadIds,proto3" json:"squad_ids,omitempty"`
|
||||
Username string `protobuf:"bytes,2,opt,name=username,proto3" json:"username,omitempty"`
|
||||
Inbound string `protobuf:"bytes,3,opt,name=inbound,proto3" json:"inbound,omitempty"`
|
||||
Type string `protobuf:"bytes,4,opt,name=type,proto3" json:"type,omitempty"`
|
||||
Uuid string `protobuf:"bytes,5,opt,name=uuid,proto3" json:"uuid,omitempty"`
|
||||
Password string `protobuf:"bytes,6,opt,name=password,proto3" json:"password,omitempty"`
|
||||
Secret string `protobuf:"bytes,7,opt,name=secret,proto3" json:"secret,omitempty"`
|
||||
Flow string `protobuf:"bytes,8,opt,name=flow,proto3" json:"flow,omitempty"`
|
||||
AlterId int32 `protobuf:"varint,9,opt,name=alter_id,json=alterId,proto3" json:"alter_id,omitempty"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
SquadIds []int32 `protobuf:"varint,1,rep,packed,name=squad_ids,json=squadIds,proto3" json:"squad_ids,omitempty"`
|
||||
Username string `protobuf:"bytes,2,opt,name=username,proto3" json:"username,omitempty"`
|
||||
Inbound string `protobuf:"bytes,3,opt,name=inbound,proto3" json:"inbound,omitempty"`
|
||||
Type string `protobuf:"bytes,4,opt,name=type,proto3" json:"type,omitempty"`
|
||||
Uuid string `protobuf:"bytes,5,opt,name=uuid,proto3" json:"uuid,omitempty"`
|
||||
Password string `protobuf:"bytes,6,opt,name=password,proto3" json:"password,omitempty"`
|
||||
Secret string `protobuf:"bytes,7,opt,name=secret,proto3" json:"secret,omitempty"`
|
||||
AuthorizedKeys []string `protobuf:"bytes,10,rep,name=authorized_keys,json=authorizedKeys,proto3" json:"authorized_keys,omitempty"`
|
||||
Flow string `protobuf:"bytes,8,opt,name=flow,proto3" json:"flow,omitempty"`
|
||||
AlterId int32 `protobuf:"varint,9,opt,name=alter_id,json=alterId,proto3" json:"alter_id,omitempty"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
||||
func (x *UserCreate) Reset() {
|
||||
@@ -775,6 +784,13 @@ func (x *UserCreate) GetSecret() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *UserCreate) GetAuthorizedKeys() []string {
|
||||
if x != nil {
|
||||
return x.AuthorizedKeys
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *UserCreate) GetFlow() string {
|
||||
if x != nil {
|
||||
return x.Flow
|
||||
@@ -790,14 +806,15 @@ func (x *UserCreate) GetAlterId() int32 {
|
||||
}
|
||||
|
||||
type UserUpdate struct {
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
Uuid string `protobuf:"bytes,1,opt,name=uuid,proto3" json:"uuid,omitempty"`
|
||||
Password string `protobuf:"bytes,2,opt,name=password,proto3" json:"password,omitempty"`
|
||||
Secret string `protobuf:"bytes,3,opt,name=secret,proto3" json:"secret,omitempty"`
|
||||
Flow string `protobuf:"bytes,4,opt,name=flow,proto3" json:"flow,omitempty"`
|
||||
AlterId int32 `protobuf:"varint,5,opt,name=alter_id,json=alterId,proto3" json:"alter_id,omitempty"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
Uuid string `protobuf:"bytes,1,opt,name=uuid,proto3" json:"uuid,omitempty"`
|
||||
Password string `protobuf:"bytes,2,opt,name=password,proto3" json:"password,omitempty"`
|
||||
Secret string `protobuf:"bytes,3,opt,name=secret,proto3" json:"secret,omitempty"`
|
||||
AuthorizedKeys []string `protobuf:"bytes,6,rep,name=authorized_keys,json=authorizedKeys,proto3" json:"authorized_keys,omitempty"`
|
||||
Flow string `protobuf:"bytes,4,opt,name=flow,proto3" json:"flow,omitempty"`
|
||||
AlterId int32 `protobuf:"varint,5,opt,name=alter_id,json=alterId,proto3" json:"alter_id,omitempty"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
||||
func (x *UserUpdate) Reset() {
|
||||
@@ -851,6 +868,13 @@ func (x *UserUpdate) GetSecret() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *UserUpdate) GetAuthorizedKeys() []string {
|
||||
if x != nil {
|
||||
return x.AuthorizedKeys
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *UserUpdate) GetFlow() string {
|
||||
if x != nil {
|
||||
return x.Flow
|
||||
@@ -2878,7 +2902,7 @@ const file_service_manager_api_grpc_manager_manager_proto_rawDesc = "" +
|
||||
"\x06values\x18\x01 \x03(\v2\x14.manager_api.v1.NodeR\x06values\"[\n" +
|
||||
"\x11NodeUpdateRequest\x12\x12\n" +
|
||||
"\x04uuid\x18\x01 \x01(\tR\x04uuid\x122\n" +
|
||||
"\x06update\x18\x02 \x01(\v2\x1a.manager_api.v1.NodeUpdateR\x06update\"\xb2\x02\n" +
|
||||
"\x06update\x18\x02 \x01(\v2\x1a.manager_api.v1.NodeUpdateR\x06update\"\xdb\x02\n" +
|
||||
"\x04User\x12\x0e\n" +
|
||||
"\x02id\x18\x01 \x01(\x05R\x02id\x12\x1b\n" +
|
||||
"\tsquad_ids\x18\x02 \x03(\x05R\bsquadIds\x12\x1a\n" +
|
||||
@@ -2887,14 +2911,15 @@ const file_service_manager_api_grpc_manager_manager_proto_rawDesc = "" +
|
||||
"\x04type\x18\x05 \x01(\tR\x04type\x12\x12\n" +
|
||||
"\x04uuid\x18\x06 \x01(\tR\x04uuid\x12\x1a\n" +
|
||||
"\bpassword\x18\a \x01(\tR\bpassword\x12\x16\n" +
|
||||
"\x06secret\x18\b \x01(\tR\x06secret\x12\x12\n" +
|
||||
"\x06secret\x18\b \x01(\tR\x06secret\x12'\n" +
|
||||
"\x0fauthorized_keys\x18\r \x03(\tR\x0eauthorizedKeys\x12\x12\n" +
|
||||
"\x04flow\x18\t \x01(\tR\x04flow\x12\x19\n" +
|
||||
"\balter_id\x18\n" +
|
||||
" \x01(\x05R\aalterId\x12\x1d\n" +
|
||||
"\n" +
|
||||
"created_at\x18\v \x01(\x03R\tcreatedAt\x12\x1d\n" +
|
||||
"\n" +
|
||||
"updated_at\x18\f \x01(\x03R\tupdatedAt\"\xea\x01\n" +
|
||||
"updated_at\x18\f \x01(\x03R\tupdatedAt\"\x93\x02\n" +
|
||||
"\n" +
|
||||
"UserCreate\x12\x1b\n" +
|
||||
"\tsquad_ids\x18\x01 \x03(\x05R\bsquadIds\x12\x1a\n" +
|
||||
@@ -2903,14 +2928,17 @@ const file_service_manager_api_grpc_manager_manager_proto_rawDesc = "" +
|
||||
"\x04type\x18\x04 \x01(\tR\x04type\x12\x12\n" +
|
||||
"\x04uuid\x18\x05 \x01(\tR\x04uuid\x12\x1a\n" +
|
||||
"\bpassword\x18\x06 \x01(\tR\bpassword\x12\x16\n" +
|
||||
"\x06secret\x18\a \x01(\tR\x06secret\x12\x12\n" +
|
||||
"\x06secret\x18\a \x01(\tR\x06secret\x12'\n" +
|
||||
"\x0fauthorized_keys\x18\n" +
|
||||
" \x03(\tR\x0eauthorizedKeys\x12\x12\n" +
|
||||
"\x04flow\x18\b \x01(\tR\x04flow\x12\x19\n" +
|
||||
"\balter_id\x18\t \x01(\x05R\aalterId\"\x83\x01\n" +
|
||||
"\balter_id\x18\t \x01(\x05R\aalterId\"\xac\x01\n" +
|
||||
"\n" +
|
||||
"UserUpdate\x12\x12\n" +
|
||||
"\x04uuid\x18\x01 \x01(\tR\x04uuid\x12\x1a\n" +
|
||||
"\bpassword\x18\x02 \x01(\tR\bpassword\x12\x16\n" +
|
||||
"\x06secret\x18\x03 \x01(\tR\x06secret\x12\x12\n" +
|
||||
"\x06secret\x18\x03 \x01(\tR\x06secret\x12'\n" +
|
||||
"\x0fauthorized_keys\x18\x06 \x03(\tR\x0eauthorizedKeys\x12\x12\n" +
|
||||
"\x04flow\x18\x04 \x01(\tR\x04flow\x12\x19\n" +
|
||||
"\balter_id\x18\x05 \x01(\x05R\aalterId\"8\n" +
|
||||
"\bUserList\x12,\n" +
|
||||
|
||||
@@ -116,6 +116,7 @@ message User {
|
||||
string uuid = 6;
|
||||
string password = 7;
|
||||
string secret = 8;
|
||||
repeated string authorized_keys = 13;
|
||||
string flow = 9;
|
||||
int32 alter_id = 10;
|
||||
int64 created_at = 11;
|
||||
@@ -130,6 +131,7 @@ message UserCreate {
|
||||
string uuid = 5;
|
||||
string password = 6;
|
||||
string secret = 7;
|
||||
repeated string authorized_keys = 10;
|
||||
string flow = 8;
|
||||
int32 alter_id = 9;
|
||||
}
|
||||
@@ -138,6 +140,7 @@ message UserUpdate {
|
||||
string uuid = 1;
|
||||
string password = 2;
|
||||
string secret = 3;
|
||||
repeated string authorized_keys = 6;
|
||||
string flow = 4;
|
||||
int32 alter_id = 5;
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// Code generated by protoc-gen-go-grpc. DO NOT EDIT.
|
||||
// versions:
|
||||
// - protoc-gen-go-grpc v1.6.1
|
||||
// - protoc v6.31.1
|
||||
// - protoc-gen-go-grpc v1.6.2
|
||||
// - protoc v7.34.1
|
||||
// source: service/manager_api/grpc/manager/manager.proto
|
||||
|
||||
package manager
|
||||
|
||||
@@ -42,18 +42,19 @@ func convertNode(v CM.Node) *pb.Node {
|
||||
|
||||
func convertUser(v CM.User) *pb.User {
|
||||
return &pb.User{
|
||||
Id: int32(v.ID),
|
||||
SquadIds: toInt32Slice(v.SquadIDs),
|
||||
Username: v.Username,
|
||||
Inbound: v.Inbound,
|
||||
Type: v.Type,
|
||||
Uuid: v.UUID,
|
||||
Password: v.Password,
|
||||
Secret: v.Secret,
|
||||
Flow: v.Flow,
|
||||
AlterId: int32(v.AlterID),
|
||||
CreatedAt: v.CreatedAt.UnixNano(),
|
||||
UpdatedAt: v.UpdatedAt.UnixNano(),
|
||||
Id: int32(v.ID),
|
||||
SquadIds: toInt32Slice(v.SquadIDs),
|
||||
Username: v.Username,
|
||||
Inbound: v.Inbound,
|
||||
Type: v.Type,
|
||||
Uuid: v.UUID,
|
||||
Password: v.Password,
|
||||
Secret: v.Secret,
|
||||
AuthorizedKeys: v.AuthorizedKeys,
|
||||
Flow: v.Flow,
|
||||
AlterId: int32(v.AlterID),
|
||||
CreatedAt: v.CreatedAt.UnixNano(),
|
||||
UpdatedAt: v.UpdatedAt.UnixNano(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -66,7 +67,7 @@ func convertBandwidthLimiter(v CM.BandwidthLimiter) *pb.BandwidthLimiter {
|
||||
Strategy: v.Strategy,
|
||||
ConnectionType: v.ConnectionType,
|
||||
Mode: v.Mode,
|
||||
FlowKeys: v.FlowKeys,
|
||||
FlowKeys: v.FlowKeys,
|
||||
Speed: v.Speed,
|
||||
RawSpeed: v.RawSpeed,
|
||||
CreatedAt: v.CreatedAt.UnixNano(),
|
||||
|
||||
@@ -125,15 +125,16 @@ func (s *Server) DeleteNode(_ context.Context, req *pb.UuidRequest) (*pb.Node, e
|
||||
|
||||
func (s *Server) CreateUser(_ context.Context, req *pb.UserCreate) (*pb.User, error) {
|
||||
v, err := s.manager.CreateUser(CM.UserCreate{
|
||||
SquadIDs: toIntSlice(req.GetSquadIds()),
|
||||
Username: req.GetUsername(),
|
||||
Inbound: req.GetInbound(),
|
||||
Type: req.GetType(),
|
||||
UUID: req.GetUuid(),
|
||||
Password: req.GetPassword(),
|
||||
Secret: req.GetSecret(),
|
||||
Flow: req.GetFlow(),
|
||||
AlterID: int(req.GetAlterId()),
|
||||
SquadIDs: toIntSlice(req.GetSquadIds()),
|
||||
Username: req.GetUsername(),
|
||||
Inbound: req.GetInbound(),
|
||||
Type: req.GetType(),
|
||||
UUID: req.GetUuid(),
|
||||
Password: req.GetPassword(),
|
||||
Secret: req.GetSecret(),
|
||||
AuthorizedKeys: req.GetAuthorizedKeys(),
|
||||
Flow: req.GetFlow(),
|
||||
AlterID: int(req.GetAlterId()),
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -172,11 +173,12 @@ func (s *Server) GetUser(_ context.Context, req *pb.IdRequest) (*pb.User, error)
|
||||
func (s *Server) UpdateUser(_ context.Context, req *pb.UserUpdateRequest) (*pb.User, error) {
|
||||
u := req.GetUpdate()
|
||||
v, err := s.manager.UpdateUser(int(req.GetId()), CM.UserUpdate{
|
||||
UUID: u.GetUuid(),
|
||||
Password: u.GetPassword(),
|
||||
Secret: u.GetSecret(),
|
||||
Flow: u.GetFlow(),
|
||||
AlterID: int(u.GetAlterId()),
|
||||
UUID: u.GetUuid(),
|
||||
Password: u.GetPassword(),
|
||||
Secret: u.GetSecret(),
|
||||
AuthorizedKeys: u.GetAuthorizedKeys(),
|
||||
Flow: u.GetFlow(),
|
||||
AlterID: int(u.GetAlterId()),
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -200,7 +202,7 @@ func (s *Server) CreateBandwidthLimiter(_ context.Context, req *pb.BandwidthLimi
|
||||
Strategy: req.GetStrategy(),
|
||||
ConnectionType: req.GetConnectionType(),
|
||||
Mode: req.GetMode(),
|
||||
FlowKeys: req.GetFlowKeys(),
|
||||
FlowKeys: req.GetFlowKeys(),
|
||||
Speed: req.GetSpeed(),
|
||||
})
|
||||
if err != nil {
|
||||
@@ -245,7 +247,7 @@ func (s *Server) UpdateBandwidthLimiter(_ context.Context, req *pb.BandwidthLimi
|
||||
Strategy: u.GetStrategy(),
|
||||
ConnectionType: u.GetConnectionType(),
|
||||
Mode: u.GetMode(),
|
||||
FlowKeys: u.GetFlowKeys(),
|
||||
FlowKeys: u.GetFlowKeys(),
|
||||
Speed: u.GetSpeed(),
|
||||
})
|
||||
if err != nil {
|
||||
|
||||
@@ -194,7 +194,7 @@ paths:
|
||||
- {in: query, name: id, schema: {type: integer, format: int32}}
|
||||
- {in: query, name: username, schema: {type: string}}
|
||||
- {in: query, name: inbound, schema: {type: string}}
|
||||
- {in: query, name: type, schema: {type: string, enum: [hysteria, hysteria2, mtproxy, trojan, tuic, vless, vmess]}}
|
||||
- {in: query, name: type, schema: {type: string, enum: [anytls, http, hysteria, hysteria2, mixed, mtproxy, naive, socks, ssh, trojan, trusttunnel, tuic, vless, vmess]}}
|
||||
- {$ref: "#/components/parameters/FilterSquadIdIn"}
|
||||
- {$ref: "#/components/parameters/FilterCreatedAtStart"}
|
||||
- {$ref: "#/components/parameters/FilterCreatedAtEnd"}
|
||||
@@ -210,6 +210,15 @@ paths:
|
||||
post:
|
||||
tags: [Users]
|
||||
summary: Create user
|
||||
description: |
|
||||
Required fields depend on `type`:
|
||||
- **vless**: uuid (flow optional)
|
||||
- **vmess**: uuid, alter_id
|
||||
- **trojan, hysteria, hysteria2**: password
|
||||
- **tuic**: uuid, password
|
||||
- **mtproxy**: secret
|
||||
- **ssh**: password OR authorized_keys (at least one)
|
||||
- **anytls, http, mixed, naive, socks, trusttunnel**: password
|
||||
requestBody: {required: true, content: {application/json: {schema: {$ref: "#/components/schemas/UserCreate"}}}}
|
||||
responses:
|
||||
"201": {content: {application/json: {schema: {$ref: "#/components/schemas/User"}}}}
|
||||
@@ -222,7 +231,7 @@ paths:
|
||||
- {in: query, name: id, schema: {type: integer, format: int32}}
|
||||
- {in: query, name: username, schema: {type: string}}
|
||||
- {in: query, name: inbound, schema: {type: string}}
|
||||
- {in: query, name: type, schema: {type: string, enum: [hysteria, hysteria2, mtproxy, trojan, tuic, vless, vmess]}}
|
||||
- {in: query, name: type, schema: {type: string, enum: [anytls, http, hysteria, hysteria2, mixed, mtproxy, naive, socks, ssh, trojan, trusttunnel, tuic, vless, vmess]}}
|
||||
- {$ref: "#/components/parameters/FilterSquadIdIn"}
|
||||
- {$ref: "#/components/parameters/FilterCreatedAtStart"}
|
||||
- {$ref: "#/components/parameters/FilterCreatedAtEnd"}
|
||||
@@ -247,6 +256,15 @@ paths:
|
||||
put:
|
||||
tags: [Users]
|
||||
summary: Update user
|
||||
description: |
|
||||
Required fields depend on user `type`:
|
||||
- **vless**: uuid (flow optional)
|
||||
- **vmess**: uuid, alter_id
|
||||
- **trojan, hysteria, hysteria2**: password
|
||||
- **tuic**: uuid, password
|
||||
- **mtproxy**: secret
|
||||
- **ssh**: password OR authorized_keys (at least one)
|
||||
- **anytls, http, mixed, naive, socks, trusttunnel**: password
|
||||
requestBody: {required: true, content: {application/json: {schema: {$ref: "#/components/schemas/UserUpdate"}}}}
|
||||
responses:
|
||||
"200": {content: {application/json: {schema: {$ref: "#/components/schemas/User"}}}}
|
||||
@@ -266,7 +284,7 @@ paths:
|
||||
summary: List bandwidth limiters
|
||||
parameters:
|
||||
- {in: query, name: id, schema: {type: integer, format: int32}}
|
||||
- {in: query, name: strategy, schema: {type: string, enum: [global, connection]}}
|
||||
- {in: query, name: strategy, schema: {type: string, enum: [global, connection, bypass]}}
|
||||
- {in: query, name: mode, schema: {type: string, enum: [upload, download, bidirectional]}}
|
||||
- {in: query, name: type, schema: {type: string}}
|
||||
- {in: query, name: username, schema: {type: string}}
|
||||
@@ -299,7 +317,7 @@ paths:
|
||||
summary: Count bandwidth limiters
|
||||
parameters:
|
||||
- {in: query, name: id, schema: {type: integer, format: int32}}
|
||||
- {in: query, name: strategy, schema: {type: string, enum: [global, connection]}}
|
||||
- {in: query, name: strategy, schema: {type: string, enum: [global, connection, bypass]}}
|
||||
- {in: query, name: mode, schema: {type: string, enum: [upload, download, bidirectional]}}
|
||||
- {in: query, name: type, schema: {type: string}}
|
||||
- {in: query, name: username, schema: {type: string}}
|
||||
@@ -430,7 +448,7 @@ paths:
|
||||
summary: List connection limiters
|
||||
parameters:
|
||||
- {in: query, name: id, schema: {type: integer, format: int32}}
|
||||
- {in: query, name: strategy, schema: {type: string, enum: [connection]}}
|
||||
- {in: query, name: strategy, schema: {type: string, enum: [connection, bypass]}}
|
||||
- {in: query, name: username, schema: {type: string}}
|
||||
- {in: query, name: outbound, schema: {type: string}}
|
||||
- {in: query, name: connection_type, schema: {type: string, enum: [default, hwid, mux, ip]}}
|
||||
@@ -460,7 +478,7 @@ paths:
|
||||
summary: Count connection limiters
|
||||
parameters:
|
||||
- {in: query, name: id, schema: {type: integer, format: int32}}
|
||||
- {in: query, name: strategy, schema: {type: string, enum: [connection]}}
|
||||
- {in: query, name: strategy, schema: {type: string, enum: [connection, bypass]}}
|
||||
- {in: query, name: username, schema: {type: string}}
|
||||
- {in: query, name: outbound, schema: {type: string}}
|
||||
- {in: query, name: connection_type, schema: {type: string, enum: [default, hwid, mux, ip]}}
|
||||
@@ -507,7 +525,7 @@ paths:
|
||||
summary: List rate limiters
|
||||
parameters:
|
||||
- {in: query, name: id, schema: {type: integer, format: int32}}
|
||||
- {in: query, name: strategy, schema: {type: string, enum: [fixed_window, sliding_window, token_bucket, leaky_bucket]}}
|
||||
- {in: query, name: strategy, schema: {type: string, enum: [fixed_window, sliding_window, token_bucket, leaky_bucket, bypass]}}
|
||||
- {in: query, name: username, schema: {type: string}}
|
||||
- {in: query, name: outbound, schema: {type: string}}
|
||||
- {in: query, name: connection_type, schema: {type: string, enum: [hwid, mux, ip, default]}}
|
||||
@@ -539,7 +557,7 @@ paths:
|
||||
summary: Count rate limiters
|
||||
parameters:
|
||||
- {in: query, name: id, schema: {type: integer, format: int32}}
|
||||
- {in: query, name: strategy, schema: {type: string, enum: [fixed_window, sliding_window, token_bucket, leaky_bucket]}}
|
||||
- {in: query, name: strategy, schema: {type: string, enum: [fixed_window, sliding_window, token_bucket, leaky_bucket, bypass]}}
|
||||
- {in: query, name: username, schema: {type: string}}
|
||||
- {in: query, name: outbound, schema: {type: string}}
|
||||
- {in: query, name: connection_type, schema: {type: string, enum: [hwid, mux, ip, default]}}
|
||||
@@ -686,16 +704,17 @@ components:
|
||||
|
||||
User:
|
||||
type: object
|
||||
required: [id, squad_ids, username, inbound, type, uuid, password, secret, flow, alter_id, created_at, updated_at]
|
||||
required: [id, squad_ids, username, inbound, type, uuid, password, secret, authorized_keys, flow, alter_id, created_at, updated_at]
|
||||
properties:
|
||||
id: {type: integer, format: int32}
|
||||
squad_ids: {$ref: "#/components/schemas/SquadIDs"}
|
||||
username: {type: string, example: "alice"}
|
||||
inbound: {type: string, example: "vless-in"}
|
||||
type: {type: string, enum: [hysteria, hysteria2, mtproxy, trojan, tuic, vless, vmess]}
|
||||
type: {type: string, enum: [anytls, http, hysteria, hysteria2, mixed, mtproxy, naive, socks, ssh, trojan, trusttunnel, tuic, vless, vmess]}
|
||||
uuid: {type: string}
|
||||
password: {type: string}
|
||||
secret: {type: string}
|
||||
authorized_keys: {type: array, items: {type: string}}
|
||||
flow: {type: string}
|
||||
alter_id: {type: integer, format: int32}
|
||||
created_at: {type: string, format: date-time}
|
||||
@@ -703,24 +722,44 @@ components:
|
||||
UserCreate:
|
||||
type: object
|
||||
required: [squad_ids, username, inbound, type]
|
||||
description: |
|
||||
Required fields depend on `type`:
|
||||
- vless: uuid (flow optional)
|
||||
- vmess: uuid, alter_id
|
||||
- trojan, shadowsocks, hysteria, hysteria2: password
|
||||
- tuic: uuid, password
|
||||
- mtproxy: secret
|
||||
- ssh: password OR authorized_keys (at least one)
|
||||
- anytls, http, mixed, naive, socks, trusttunnel: password
|
||||
properties:
|
||||
squad_ids: {$ref: "#/components/schemas/SquadIDs"}
|
||||
username: {type: string, example: "alice"}
|
||||
inbound: {type: string, example: "vless-in"}
|
||||
type:
|
||||
type: string
|
||||
enum: [hysteria, hysteria2, mtproxy, trojan, tuic, vless, vmess]
|
||||
enum: [anytls, http, hysteria, hysteria2, mixed, mtproxy, naive, socks, ssh, trojan, trusttunnel, tuic, vless, vmess]
|
||||
uuid: {type: string, format: uuid}
|
||||
password: {type: string}
|
||||
secret: {type: string}
|
||||
authorized_keys: {type: array, items: {type: string}}
|
||||
flow: {type: string}
|
||||
alter_id: {type: integer, format: int32}
|
||||
UserUpdate:
|
||||
type: object
|
||||
description: |
|
||||
All fields are optional. Validation rules match UserCreate by type:
|
||||
- vless: uuid (flow optional)
|
||||
- vmess: uuid, alter_id
|
||||
- trojan, shadowsocks, hysteria, hysteria2: password
|
||||
- tuic: uuid, password
|
||||
- mtproxy: secret
|
||||
- ssh: password OR authorized_keys (at least one)
|
||||
- anytls, http, mixed, naive, socks, trusttunnel: password
|
||||
properties:
|
||||
uuid: {type: string, format: uuid}
|
||||
password: {type: string}
|
||||
secret: {type: string}
|
||||
authorized_keys: {type: array, items: {type: string}}
|
||||
flow: {type: string}
|
||||
alter_id: {type: integer, format: int32}
|
||||
|
||||
@@ -732,41 +771,43 @@ components:
|
||||
squad_ids: {$ref: "#/components/schemas/SquadIDs"}
|
||||
username: {type: string}
|
||||
outbound: {type: string, example: "direct"}
|
||||
strategy: {type: string, enum: [global, connection]}
|
||||
connection_type: {type: string, enum: [default, hwid, mux, ip]}
|
||||
strategy: {type: string, enum: [global, connection, bypass]}
|
||||
connection_type: {type: string}
|
||||
mode: {type: string, enum: [upload, download, bidirectional]}
|
||||
flow_keys: {type: array, items: {type: string, enum: [user, destination, ip, hwid, mux]}}
|
||||
flow_keys: {type: array, items: {type: string, enum: [user, source_ip, hwid, mux, protocol, destination]}}
|
||||
speed: {type: string, example: "10mbit"}
|
||||
raw_speed: {type: integer, format: int64}
|
||||
created_at: {type: string, format: date-time}
|
||||
updated_at: {type: string, format: date-time}
|
||||
BandwidthLimiterCreate:
|
||||
type: object
|
||||
required: [squad_ids, outbound, strategy, mode, speed]
|
||||
required: [squad_ids, outbound, strategy]
|
||||
description: "mode, speed, flow_keys, connection_type are required/relevant unless strategy=bypass"
|
||||
properties:
|
||||
squad_ids: {$ref: "#/components/schemas/SquadIDs"}
|
||||
username: {type: string}
|
||||
outbound: {type: string, example: "direct"}
|
||||
strategy: {type: string, enum: [global, connection]}
|
||||
connection_type: {type: string, enum: [default, hwid, mux, ip]}
|
||||
strategy: {type: string, enum: [global, connection, bypass]}
|
||||
connection_type: {type: string}
|
||||
mode: {type: string, enum: [upload, download, bidirectional]}
|
||||
flow_keys: {type: array, items: {type: string, enum: [user, destination, ip, hwid, mux]}}
|
||||
flow_keys: {type: array, items: {type: string, enum: [user, source_ip, hwid, mux, protocol, destination]}}
|
||||
speed: {type: string, example: "10mbit"}
|
||||
BandwidthLimiterUpdate:
|
||||
type: object
|
||||
required: [outbound, strategy, mode, speed]
|
||||
required: [outbound, strategy]
|
||||
description: "mode, speed, flow_keys, connection_type are required/relevant unless strategy=bypass"
|
||||
properties:
|
||||
username: {type: string}
|
||||
outbound: {type: string}
|
||||
strategy: {type: string, enum: [global, connection]}
|
||||
connection_type: {type: string, enum: [default, hwid, mux, ip]}
|
||||
strategy: {type: string, enum: [global, connection, bypass]}
|
||||
connection_type: {type: string}
|
||||
mode: {type: string, enum: [upload, download, bidirectional]}
|
||||
flow_keys: {type: array, items: {type: string, enum: [user, destination, ip, hwid, mux]}}
|
||||
flow_keys: {type: array, items: {type: string, enum: [user, source_ip, hwid, mux, protocol, destination]}}
|
||||
speed: {type: string}
|
||||
|
||||
TrafficLimiter:
|
||||
type: object
|
||||
required: [id, squad_ids, outbound, strategy, mode, raw_used, quota, raw_quota, created_at, updated_at]
|
||||
required: [id, squad_ids, outbound, strategy, mode, raw_used, quota, raw_quota, usage, created_at, updated_at]
|
||||
properties:
|
||||
id: {type: integer, format: int32}
|
||||
squad_ids: {$ref: "#/components/schemas/SquadIDs"}
|
||||
@@ -777,11 +818,13 @@ components:
|
||||
raw_used: {type: integer, format: int64}
|
||||
quota: {type: string, example: "10gb"}
|
||||
raw_quota: {type: integer, format: int64}
|
||||
usage: {type: integer, format: int32, description: "Usage percentage 0-100"}
|
||||
created_at: {type: string, format: date-time}
|
||||
updated_at: {type: string, format: date-time}
|
||||
TrafficLimiterCreate:
|
||||
type: object
|
||||
required: [squad_ids, outbound, strategy, mode, quota]
|
||||
required: [squad_ids, outbound, strategy]
|
||||
description: "mode, quota are required unless strategy=bypass"
|
||||
properties:
|
||||
squad_ids: {$ref: "#/components/schemas/SquadIDs"}
|
||||
username: {type: string}
|
||||
@@ -791,7 +834,8 @@ components:
|
||||
quota: {type: string, example: "10gb"}
|
||||
TrafficLimiterUpdate:
|
||||
type: object
|
||||
required: [outbound, strategy, mode, quota]
|
||||
required: [outbound, strategy]
|
||||
description: "mode, quota are required unless strategy=bypass"
|
||||
properties:
|
||||
username: {type: string}
|
||||
outbound: {type: string}
|
||||
@@ -807,7 +851,7 @@ components:
|
||||
squad_ids: {$ref: "#/components/schemas/SquadIDs"}
|
||||
username: {type: string}
|
||||
outbound: {type: string, example: "direct"}
|
||||
strategy: {type: string, enum: [connection]}
|
||||
strategy: {type: string, enum: [connection, bypass]}
|
||||
connection_type: {type: string, enum: [default, hwid, mux, ip]}
|
||||
lock_type: {type: string, enum: [manager, default]}
|
||||
count: {type: integer, format: int64}
|
||||
@@ -815,22 +859,24 @@ components:
|
||||
updated_at: {type: string, format: date-time}
|
||||
ConnectionLimiterCreate:
|
||||
type: object
|
||||
required: [squad_ids, outbound, strategy, lock_type, count]
|
||||
required: [squad_ids, outbound, strategy]
|
||||
description: "lock_type, connection_type, count are required unless strategy=bypass"
|
||||
properties:
|
||||
squad_ids: {$ref: "#/components/schemas/SquadIDs"}
|
||||
username: {type: string}
|
||||
outbound: {type: string, example: "direct"}
|
||||
strategy: {type: string, enum: [connection]}
|
||||
strategy: {type: string, enum: [connection, bypass]}
|
||||
connection_type: {type: string, enum: [default, hwid, mux, ip]}
|
||||
lock_type: {type: string, enum: [manager, default]}
|
||||
count: {type: integer, format: int64}
|
||||
ConnectionLimiterUpdate:
|
||||
type: object
|
||||
required: [outbound, strategy, lock_type, count]
|
||||
required: [outbound, strategy]
|
||||
description: "lock_type, connection_type, count are required unless strategy=bypass"
|
||||
properties:
|
||||
username: {type: string}
|
||||
outbound: {type: string}
|
||||
strategy: {type: string, enum: [connection]}
|
||||
strategy: {type: string, enum: [connection, bypass]}
|
||||
connection_type: {type: string, enum: [default, hwid, mux, ip]}
|
||||
lock_type: {type: string, enum: [manager, default]}
|
||||
count: {type: integer, format: int64}
|
||||
@@ -843,7 +889,7 @@ components:
|
||||
squad_ids: {$ref: "#/components/schemas/SquadIDs"}
|
||||
username: {type: string}
|
||||
outbound: {type: string, example: "direct"}
|
||||
strategy: {type: string, enum: [fixed_window, sliding_window, token_bucket, leaky_bucket]}
|
||||
strategy: {type: string, enum: [fixed_window, sliding_window, token_bucket, leaky_bucket, bypass]}
|
||||
connection_type: {type: string, enum: [hwid, mux, ip, default]}
|
||||
count: {type: integer, format: int64}
|
||||
interval: {type: string, example: "1s"}
|
||||
@@ -851,22 +897,24 @@ components:
|
||||
updated_at: {type: string, format: date-time}
|
||||
RateLimiterCreate:
|
||||
type: object
|
||||
required: [squad_ids, outbound, strategy, connection_type, count, interval]
|
||||
required: [squad_ids, outbound, strategy]
|
||||
description: "connection_type, count, interval are required unless strategy=bypass"
|
||||
properties:
|
||||
squad_ids: {$ref: "#/components/schemas/SquadIDs"}
|
||||
username: {type: string}
|
||||
outbound: {type: string, example: "direct"}
|
||||
strategy: {type: string, enum: [fixed_window, sliding_window, token_bucket, leaky_bucket]}
|
||||
strategy: {type: string, enum: [fixed_window, sliding_window, token_bucket, leaky_bucket, bypass]}
|
||||
connection_type: {type: string, enum: [hwid, mux, ip, default]}
|
||||
count: {type: integer, format: int64}
|
||||
interval: {type: string, example: "1s"}
|
||||
RateLimiterUpdate:
|
||||
type: object
|
||||
required: [outbound, strategy, connection_type, count, interval]
|
||||
required: [outbound, strategy]
|
||||
description: "connection_type, count, interval are required unless strategy=bypass"
|
||||
properties:
|
||||
username: {type: string}
|
||||
outbound: {type: string}
|
||||
strategy: {type: string, enum: [fixed_window, sliding_window, token_bucket, leaky_bucket]}
|
||||
strategy: {type: string, enum: [fixed_window, sliding_window, token_bucket, leaky_bucket, bypass]}
|
||||
connection_type: {type: string, enum: [hwid, mux, ip, default]}
|
||||
count: {type: integer, format: int64}
|
||||
interval: {type: string}
|
||||
|
||||
97
service/node/inbound/ssh.go
Normal file
97
service/node/inbound/ssh.go
Normal file
@@ -0,0 +1,97 @@
|
||||
package inbound
|
||||
|
||||
import (
|
||||
"sync"
|
||||
|
||||
"github.com/sagernet/sing-box/adapter"
|
||||
"github.com/sagernet/sing-box/option"
|
||||
"github.com/sagernet/sing-box/protocol/ssh"
|
||||
CM "github.com/sagernet/sing-box/service/manager/constant"
|
||||
"github.com/sagernet/sing-box/service/node/constant"
|
||||
)
|
||||
|
||||
type SSHManager struct {
|
||||
inbounds map[string]*SSHUserManager
|
||||
|
||||
mtx sync.Mutex
|
||||
}
|
||||
|
||||
func NewSSHManager() *SSHManager {
|
||||
return &SSHManager{
|
||||
inbounds: make(map[string]*SSHUserManager),
|
||||
}
|
||||
}
|
||||
|
||||
func (m *SSHManager) AddUserManager(inbound adapter.Inbound) error {
|
||||
m.mtx.Lock()
|
||||
defer m.mtx.Unlock()
|
||||
m.inbounds[inbound.Tag()] = &SSHUserManager{
|
||||
inbound: inbound.(*ssh.Inbound),
|
||||
usersMap: make(map[string]option.SSHUser),
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *SSHManager) GetUserManager(tag string) (constant.UserManager, bool) {
|
||||
m.mtx.Lock()
|
||||
defer m.mtx.Unlock()
|
||||
inbound, ok := m.inbounds[tag]
|
||||
return inbound, ok
|
||||
}
|
||||
|
||||
func (m *SSHManager) GetUserManagerTags() []string {
|
||||
m.mtx.Lock()
|
||||
defer m.mtx.Unlock()
|
||||
tags := make([]string, 0, len(m.inbounds))
|
||||
for tag := range m.inbounds {
|
||||
tags = append(tags, tag)
|
||||
}
|
||||
return tags
|
||||
}
|
||||
|
||||
type SSHUserManager struct {
|
||||
inbound *ssh.Inbound
|
||||
usersMap map[string]option.SSHUser
|
||||
|
||||
mtx sync.Mutex
|
||||
}
|
||||
|
||||
func (i *SSHUserManager) postUpdate() {
|
||||
users := make([]option.SSHUser, 0, len(i.usersMap))
|
||||
for _, user := range i.usersMap {
|
||||
users = append(users, user)
|
||||
}
|
||||
i.inbound.UpdateUsers(users)
|
||||
}
|
||||
|
||||
func convertSSHUser(user CM.User) option.SSHUser {
|
||||
return option.SSHUser{
|
||||
Name: user.Username,
|
||||
Password: user.Password,
|
||||
AuthorizedKeys: user.AuthorizedKeys,
|
||||
}
|
||||
}
|
||||
|
||||
func (i *SSHUserManager) UpdateUser(user CM.User) {
|
||||
i.mtx.Lock()
|
||||
defer i.mtx.Unlock()
|
||||
i.usersMap[user.Username] = convertSSHUser(user)
|
||||
i.postUpdate()
|
||||
}
|
||||
|
||||
func (i *SSHUserManager) UpdateUsers(users []CM.User) {
|
||||
i.mtx.Lock()
|
||||
defer i.mtx.Unlock()
|
||||
clear(i.usersMap)
|
||||
for _, user := range users {
|
||||
i.usersMap[user.Username] = convertSSHUser(user)
|
||||
}
|
||||
i.postUpdate()
|
||||
}
|
||||
|
||||
func (i *SSHUserManager) DeleteUser(username string) {
|
||||
i.mtx.Lock()
|
||||
defer i.mtx.Unlock()
|
||||
delete(i.usersMap, username)
|
||||
i.postUpdate()
|
||||
}
|
||||
@@ -33,7 +33,7 @@ type Service struct {
|
||||
rateManager constant.RateLimiterManager
|
||||
options option.NodeServiceOptions
|
||||
|
||||
nodeManager CM.NodeManager
|
||||
nodeManager CM.NodeManager
|
||||
|
||||
mtx sync.Mutex
|
||||
}
|
||||
@@ -79,6 +79,7 @@ func (s *Service) Start(stage adapter.StartStage) error {
|
||||
"tuic": inbound.NewTUICManager(),
|
||||
"vless": inbound.NewVLESSManager(),
|
||||
"vmess": inbound.NewVMessManager(),
|
||||
"ssh": inbound.NewSSHManager(),
|
||||
}
|
||||
s.connectionManager = limiter.NewConnectionLimiterManager(s.ctx, nodeManager, s.logger)
|
||||
s.bandwidthManager = limiter.NewBandwidthLimiterManager(s.ctx, nodeManager, s.logger)
|
||||
|
||||
@@ -281,15 +281,16 @@ func (s *APIClient) handler(node CM.ConnectedNode, stream grpc.ServerStreamingCl
|
||||
|
||||
func (s *APIClient) convertUser(user *pb.User) CM.User {
|
||||
return CM.User{
|
||||
ID: int(user.Id),
|
||||
Username: user.Username,
|
||||
Inbound: user.Inbound,
|
||||
Type: user.Type,
|
||||
UUID: user.Uuid,
|
||||
Password: user.Password,
|
||||
Secret: user.Secret,
|
||||
Flow: user.Flow,
|
||||
AlterID: int(user.AlterId),
|
||||
ID: int(user.Id),
|
||||
Username: user.Username,
|
||||
Inbound: user.Inbound,
|
||||
Type: user.Type,
|
||||
UUID: user.Uuid,
|
||||
Password: user.Password,
|
||||
Secret: user.Secret,
|
||||
AuthorizedKeys: user.AuthorizedKeys,
|
||||
Flow: user.Flow,
|
||||
AlterID: int(user.AlterId),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -301,7 +302,7 @@ func (s *APIClient) convertBandwidthLimiter(limiter *pb.BandwidthLimiter) CM.Ban
|
||||
Strategy: limiter.Strategy,
|
||||
ConnectionType: limiter.ConnectionType,
|
||||
Mode: limiter.Mode,
|
||||
FlowKeys: limiter.FlowKeys,
|
||||
FlowKeys: limiter.FlowKeys,
|
||||
Speed: limiter.Speed,
|
||||
RawSpeed: limiter.RawSpeed,
|
||||
}
|
||||
|
||||
@@ -151,18 +151,19 @@ func (x *Node) GetUuid() string {
|
||||
}
|
||||
|
||||
type User struct {
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
Id int32 `protobuf:"varint,1,opt,name=id,proto3" json:"id,omitempty"`
|
||||
Username string `protobuf:"bytes,2,opt,name=username,proto3" json:"username,omitempty"`
|
||||
Inbound string `protobuf:"bytes,3,opt,name=inbound,proto3" json:"inbound,omitempty"`
|
||||
Type string `protobuf:"bytes,4,opt,name=type,proto3" json:"type,omitempty"`
|
||||
Uuid string `protobuf:"bytes,5,opt,name=uuid,proto3" json:"uuid,omitempty"`
|
||||
Password string `protobuf:"bytes,6,opt,name=password,proto3" json:"password,omitempty"`
|
||||
Secret string `protobuf:"bytes,7,opt,name=secret,proto3" json:"secret,omitempty"`
|
||||
Flow string `protobuf:"bytes,8,opt,name=flow,proto3" json:"flow,omitempty"`
|
||||
AlterId int32 `protobuf:"varint,9,opt,name=alter_id,json=alterId,proto3" json:"alter_id,omitempty"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
Id int32 `protobuf:"varint,1,opt,name=id,proto3" json:"id,omitempty"`
|
||||
Username string `protobuf:"bytes,2,opt,name=username,proto3" json:"username,omitempty"`
|
||||
Inbound string `protobuf:"bytes,3,opt,name=inbound,proto3" json:"inbound,omitempty"`
|
||||
Type string `protobuf:"bytes,4,opt,name=type,proto3" json:"type,omitempty"`
|
||||
Uuid string `protobuf:"bytes,5,opt,name=uuid,proto3" json:"uuid,omitempty"`
|
||||
Password string `protobuf:"bytes,6,opt,name=password,proto3" json:"password,omitempty"`
|
||||
Secret string `protobuf:"bytes,7,opt,name=secret,proto3" json:"secret,omitempty"`
|
||||
Flow string `protobuf:"bytes,8,opt,name=flow,proto3" json:"flow,omitempty"`
|
||||
AlterId int32 `protobuf:"varint,9,opt,name=alter_id,json=alterId,proto3" json:"alter_id,omitempty"`
|
||||
AuthorizedKeys []string `protobuf:"bytes,10,rep,name=authorized_keys,json=authorizedKeys,proto3" json:"authorized_keys,omitempty"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
||||
func (x *User) Reset() {
|
||||
@@ -258,6 +259,13 @@ func (x *User) GetAlterId() int32 {
|
||||
return 0
|
||||
}
|
||||
|
||||
func (x *User) GetAuthorizedKeys() []string {
|
||||
if x != nil {
|
||||
return x.AuthorizedKeys
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type UserList struct {
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
Values []*User `protobuf:"bytes,1,rep,name=values,proto3" json:"values,omitempty"`
|
||||
@@ -1338,7 +1346,7 @@ const file_service_node_manager_api_manager_manager_proto_rawDesc = "" +
|
||||
"\n" +
|
||||
".service/node_manager_api/manager/manager.proto\x12\x13node_manager_api.v1\"\x1a\n" +
|
||||
"\x04Node\x12\x12\n" +
|
||||
"\x04uuid\x18\x01 \x01(\tR\x04uuid\"\xd7\x01\n" +
|
||||
"\x04uuid\x18\x01 \x01(\tR\x04uuid\"\x80\x02\n" +
|
||||
"\x04User\x12\x0e\n" +
|
||||
"\x02id\x18\x01 \x01(\x05R\x02id\x12\x1a\n" +
|
||||
"\busername\x18\x02 \x01(\tR\busername\x12\x18\n" +
|
||||
@@ -1348,7 +1356,9 @@ const file_service_node_manager_api_manager_manager_proto_rawDesc = "" +
|
||||
"\bpassword\x18\x06 \x01(\tR\bpassword\x12\x16\n" +
|
||||
"\x06secret\x18\a \x01(\tR\x06secret\x12\x12\n" +
|
||||
"\x04flow\x18\b \x01(\tR\x04flow\x12\x19\n" +
|
||||
"\balter_id\x18\t \x01(\x05R\aalterId\"=\n" +
|
||||
"\balter_id\x18\t \x01(\x05R\aalterId\x12'\n" +
|
||||
"\x0fauthorized_keys\x18\n" +
|
||||
" \x03(\tR\x0eauthorizedKeys\"=\n" +
|
||||
"\bUserList\x121\n" +
|
||||
"\x06values\x18\x01 \x03(\v2\x19.node_manager_api.v1.UserR\x06values\"\x83\x02\n" +
|
||||
"\x10BandwidthLimiter\x12\x0e\n" +
|
||||
|
||||
@@ -48,6 +48,7 @@ message User {
|
||||
string secret = 7;
|
||||
string flow = 8;
|
||||
int32 alter_id = 9;
|
||||
repeated string authorized_keys = 10;
|
||||
}
|
||||
|
||||
message UserList {
|
||||
|
||||
@@ -239,15 +239,16 @@ func (s *RemoteNode) close(err error) {
|
||||
|
||||
func (s *RemoteNode) convertUser(user CS.User) *pb.User {
|
||||
return &pb.User{
|
||||
Id: int32(user.ID),
|
||||
Username: user.Username,
|
||||
Inbound: user.Inbound,
|
||||
Type: user.Type,
|
||||
Uuid: user.UUID,
|
||||
Password: user.Password,
|
||||
Secret: user.Secret,
|
||||
Flow: user.Flow,
|
||||
AlterId: int32(user.AlterID),
|
||||
Id: int32(user.ID),
|
||||
Username: user.Username,
|
||||
Inbound: user.Inbound,
|
||||
Type: user.Type,
|
||||
Uuid: user.UUID,
|
||||
Password: user.Password,
|
||||
Secret: user.Secret,
|
||||
AuthorizedKeys: user.AuthorizedKeys,
|
||||
Flow: user.Flow,
|
||||
AlterId: int32(user.AlterID),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -98,12 +98,12 @@ func (s *Service) Start(stage adapter.StartStage) error {
|
||||
s.adaptiveTimer = newAdaptiveTimer(s.logger, s.router, s.timerConfig)
|
||||
s.adaptiveTimer.start(false)
|
||||
if s.memoryLimit > 0 {
|
||||
s.logger.Info("started memory monitor with limit: ", s.memoryLimit/(1024*1024), " MiB")
|
||||
s.logger.Notice("started memory monitor with limit: ", s.memoryLimit/(1024*1024), " MiB")
|
||||
} else {
|
||||
s.logger.Info("started memory monitor with available memory detection")
|
||||
s.logger.Notice("started memory monitor with available memory detection")
|
||||
}
|
||||
} else {
|
||||
s.logger.Info("started memory pressure monitor")
|
||||
s.logger.Notice("started memory pressure monitor")
|
||||
}
|
||||
|
||||
globalAccess.Lock()
|
||||
|
||||
@@ -66,9 +66,9 @@ func (s *Service) Start(stage adapter.StartStage) error {
|
||||
s.adaptiveTimer = newAdaptiveTimer(s.logger, s.router, s.timerConfig)
|
||||
s.adaptiveTimer.start(false)
|
||||
if s.useAvailable {
|
||||
s.logger.Info("started memory monitor with available memory detection")
|
||||
s.logger.Notice("started memory monitor with available memory detection")
|
||||
} else {
|
||||
s.logger.Info("started memory monitor with limit: ", s.memoryLimit/(1024*1024), " MiB")
|
||||
s.logger.Notice("started memory monitor with limit: ", s.memoryLimit/(1024*1024), " MiB")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user