hiddifypanel 10.30.7.dev0__py3-none-any.whl → 10.30.8.dev0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (36) hide show
  1. hiddifypanel/VERSION +1 -1
  2. hiddifypanel/VERSION.py +2 -2
  3. hiddifypanel/auth.py +16 -0
  4. hiddifypanel/base.py +8 -1
  5. hiddifypanel/drivers/ssh_liberty_bridge_api.py +4 -0
  6. hiddifypanel/drivers/wireguard_api.py +34 -44
  7. hiddifypanel/drivers/xray_api.py +3 -3
  8. hiddifypanel/hutils/auth.py +1 -1
  9. hiddifypanel/models/admin.py +14 -8
  10. hiddifypanel/models/base_account.py +14 -6
  11. hiddifypanel/models/user.py +38 -25
  12. hiddifypanel/panel/cli.py +1 -27
  13. hiddifypanel/panel/commercial/restapi/v2/admin/__init__.py +3 -1
  14. hiddifypanel/panel/commercial/restapi/v2/admin/admin_info_api.py +1 -0
  15. hiddifypanel/panel/commercial/restapi/v2/admin/admin_log_api.py +1 -0
  16. hiddifypanel/panel/commercial/restapi/v2/admin/admin_user_api.py +9 -20
  17. hiddifypanel/panel/commercial/restapi/v2/admin/admin_users_api.py +14 -0
  18. hiddifypanel/panel/commercial/restapi/v2/admin/schema.py +19 -14
  19. hiddifypanel/panel/commercial/restapi/v2/admin/server_status_api.py +1 -0
  20. hiddifypanel/panel/commercial/restapi/v2/admin/system_actions.py +27 -0
  21. hiddifypanel/panel/commercial/restapi/v2/admin/user_api.py +12 -22
  22. hiddifypanel/panel/commercial/restapi/v2/admin/users_api.py +19 -1
  23. hiddifypanel/panel/commercial/restapi/v2/child/__init__.py +1 -1
  24. hiddifypanel/panel/commercial/restapi/v2/panel/ping_pong.py +12 -0
  25. hiddifypanel/panel/commercial/restapi/v2/panel/schema.py +5 -0
  26. hiddifypanel/panel/commercial/restapi/v2/parent/__init__.py +1 -1
  27. hiddifypanel/panel/commercial/restapi/v2/user/__init__.py +1 -1
  28. hiddifypanel/panel/hiddify.py +31 -0
  29. hiddifypanel/panel/init_db.py +1 -1
  30. hiddifypanel/templates/fake.html +316 -0
  31. {hiddifypanel-10.30.7.dev0.dist-info → hiddifypanel-10.30.8.dev0.dist-info}/METADATA +1 -1
  32. {hiddifypanel-10.30.7.dev0.dist-info → hiddifypanel-10.30.8.dev0.dist-info}/RECORD +36 -35
  33. {hiddifypanel-10.30.7.dev0.dist-info → hiddifypanel-10.30.8.dev0.dist-info}/WHEEL +1 -1
  34. {hiddifypanel-10.30.7.dev0.dist-info → hiddifypanel-10.30.8.dev0.dist-info}/LICENSE.md +0 -0
  35. {hiddifypanel-10.30.7.dev0.dist-info → hiddifypanel-10.30.8.dev0.dist-info}/entry_points.txt +0 -0
  36. {hiddifypanel-10.30.7.dev0.dist-info → hiddifypanel-10.30.8.dev0.dist-info}/top_level.txt +0 -0
@@ -1,8 +1,10 @@
1
1
  import uuid
2
- from apiflask.fields import String, Float, Enum, Date, Integer, Boolean
2
+ from apiflask.fields import String, Float, Enum, Date, Integer, Boolean, DateTime
3
3
  from apiflask import Schema, fields
4
4
  from typing import Any, Mapping
5
5
 
6
+ from marshmallow import ValidationError
7
+
6
8
  from hiddifypanel.models import UserMode, Lang, AdminMode
7
9
  from hiddifypanel import hutils
8
10
 
@@ -18,19 +20,24 @@ class FriendlyDateTime(fields.Field):
18
20
 
19
21
 
20
22
  class FriendlyUUID(fields.Field):
23
+
21
24
  def _serialize(self, value: Any, attr: str | None, obj: Any, **kwargs):
22
- if value is None:
25
+ if value is None or not hutils.auth.is_uuid_valid(value):
23
26
  return None
24
27
  return str(value)
25
28
 
26
29
  def _deserialize(self, value: Any, attr: str | None, data: Mapping[str, Any] | None, **kwargs):
27
- if value is None:
30
+ if value is None or not hutils.auth.is_uuid_valid(value):
28
31
  return None
29
32
  try:
30
33
  return str(uuid.UUID(value))
31
34
  except ValueError:
32
35
  self.fail('Invalid uuid')
33
36
 
37
+ def _validated(self, value):
38
+ if not hutils.auth.is_uuid_valid(value):
39
+ raise ValidationError('Invalid UUID')
40
+
34
41
 
35
42
  class UserSchema(Schema):
36
43
  uuid = FriendlyUUID(required=True, description="Unique identifier for the user")
@@ -66,8 +73,8 @@ class UserSchema(Schema):
66
73
  allow_none=True,
67
74
  description="The current data usage of the user in gigabytes"
68
75
  )
69
- last_reset_time = Date(
70
- format='%Y-%m-%d',
76
+ last_reset_time = FriendlyDateTime(
77
+ format='%Y-%m-%d %H:%M:%S',
71
78
  description="The last time the user's data usage was reset, in a JSON-friendly format",
72
79
  allow_none=True
73
80
  )
@@ -121,20 +128,25 @@ class UserSchema(Schema):
121
128
 
122
129
  lang = Enum(Lang, required=False, allow_none=True, description="The language of the user")
123
130
  enable = Boolean(required=False, description="Whether the user is enabled or not")
131
+ is_active = Boolean(required=False, description="Whether the user is active for using hiddify")
124
132
 
125
133
 
126
- class PutUserSchema(UserSchema):
134
+ class PostUserSchema(UserSchema):
127
135
  def __init__(self, *args, **kwargs):
128
136
  super().__init__(*args, **kwargs)
129
137
  # the uuid is sent in the url path
130
138
  self.fields['uuid'].required = False
139
+ self.fields['uuid'].allow_none = True
131
140
 
132
141
 
133
142
  class PatchUserSchema(UserSchema):
134
143
  def __init__(self, *args, **kwargs):
135
144
  super().__init__(*args, **kwargs)
136
145
  self.fields['uuid'].required = False
146
+ self.fields['uuid'].allow_none = True,
137
147
  self.fields['name'].required = False
148
+ self.fields['name'].allow_none = True,
149
+
138
150
 
139
151
  # endregion
140
152
 
@@ -144,7 +156,7 @@ class PatchUserSchema(UserSchema):
144
156
  class AdminSchema(Schema):
145
157
  name = String(required=True, description='The name of the admin')
146
158
  comment = String(required=False, description='A comment related to the admin', allow_none=True)
147
- uuid = FriendlyUUID(required=True, description='The unique identifier for the admin')
159
+ uuid = FriendlyUUID(required=False, allow_none=True, description='The unique identifier for the admin')
148
160
  mode = Enum(AdminMode, required=True, description='The mode for the admin')
149
161
  can_add_admin = Boolean(required=True, description='Whether the admin can add other admins')
150
162
  parent_admin_uuid = FriendlyUUID(required=False, description='The unique identifier for the parent admin', allow_none=True,
@@ -156,13 +168,6 @@ class AdminSchema(Schema):
156
168
  max_active_users = Integer(required=False, description='The maximum number of active users allowed', allow_none=True)
157
169
 
158
170
 
159
- class PutAdminSchema(AdminSchema):
160
- def __init__(self, *args, **kwargs):
161
- super().__init__(*args, **kwargs)
162
- # the uuid is sent in the url path
163
- self.fields['uuid'].required = False
164
-
165
-
166
171
  class PatchAdminSchema(AdminSchema):
167
172
  def __init__(self, *args, **kwargs):
168
173
  super().__init__(*args, **kwargs)
@@ -20,6 +20,7 @@ class AdminServerStatusApi(MethodView):
20
20
 
21
21
  @app.output(ServerStatusOutputSchema) # type: ignore
22
22
  def get(self):
23
+ """System: ServerStatus"""
23
24
  dto = ServerStatusOutputSchema()
24
25
  dto.stats = { # type: ignore
25
26
  'system': hutils.system.system_stats(),
@@ -0,0 +1,27 @@
1
+ from flask import current_app as app, request
2
+ from flask import g
3
+ from flask.views import MethodView
4
+ from apiflask.fields import Dict
5
+ from apiflask import Schema
6
+ from hiddifypanel.models.usage import DailyUsage
7
+ from hiddifypanel.auth import login_required
8
+ from hiddifypanel.models import Role, DailyUsage
9
+ from hiddifypanel.panel import hiddify, usage
10
+ from hiddifypanel import hutils
11
+ import json
12
+
13
+
14
+ class UpdateUserUsageApi(MethodView):
15
+ decorators = [login_required({Role.super_admin})]
16
+
17
+ def get(self):
18
+ """System: Update User Usage"""
19
+ return json.dumps(usage.update_local_usage(), indent=2)
20
+
21
+
22
+ class AllConfigsApi(MethodView):
23
+ decorators = [login_required({Role.super_admin})]
24
+
25
+ def get(self):
26
+ """System: All Configs for configuration"""
27
+ return json.dumps(hiddify.all_configs_for_cli(), indent=2)
@@ -9,7 +9,7 @@ from hiddifypanel.drivers import user_driver
9
9
  from hiddifypanel.panel import hiddify
10
10
 
11
11
  from . import has_permission
12
- from .schema import UserSchema, PutUserSchema, PatchUserSchema, SuccessfulSchema
12
+ from .schema import UserSchema, PostUserSchema, PatchUserSchema, SuccessfulSchema
13
13
 
14
14
 
15
15
  class UserApi(MethodView):
@@ -17,30 +17,17 @@ class UserApi(MethodView):
17
17
 
18
18
  @app.output(UserSchema) # type: ignore
19
19
  def get(self, uuid):
20
+ """User: Get details of a user"""
20
21
  user = User.by_uuid(uuid) or abort(404, "User not found")
21
22
  if not has_permission(user):
22
23
  abort(403, "You don't have permission to access this user")
23
24
 
24
25
  return user.to_schema() # type: ignore
25
26
 
26
- @app.input(PutUserSchema, arg_name="data") # type: ignore
27
- @app.output(SuccessfulSchema) # type: ignore
28
- def put(self, uuid, data):
29
- if User.by_uuid(uuid):
30
- abort(400, 'The user exists')
31
- data['uuid'] = uuid
32
-
33
- if not data.get('added_by_uuid'):
34
- data['added_by_uuid'] = g.account.uuid
35
-
36
- dbuser = User.add_or_update(**data) or abort(502, "Unknown issue: User is not added")
37
- user_driver.add_client(dbuser)
38
- hiddify.quick_apply_users()
39
- return {'status': 200, 'msg': 'ok'}
40
-
41
27
  @app.input(PatchUserSchema, arg_name="data") # type: ignore
42
- @app.output(SuccessfulSchema) # type: ignore
28
+ @app.output(UserSchema) # type: ignore
43
29
  def patch(self, uuid, data):
30
+ """User: Update a user"""
44
31
  user = User.by_uuid(uuid) or abort(404, "user not found")
45
32
  if not has_permission(user):
46
33
  abort(403, "You don't have permission to access this user")
@@ -50,17 +37,20 @@ class UserApi(MethodView):
50
37
  continue
51
38
  if field not in data:
52
39
  data[field] = getattr(user, field)
53
-
40
+ data['old_uuid'] = uuid
41
+ user_driver.remove_client(user)
54
42
  dbuser = User.add_or_update(**data) or abort(502, "Unknown issue! User is not patched")
55
- user_driver.add_client(dbuser)
43
+ if dbuser.is_active:
44
+ user_driver.add_client(dbuser)
56
45
  # the add_or_update doesn't update the uuid of User, so for now just delete old user after adding new
57
- if user.uuid != data['uuid']:
58
- user.remove()
46
+ # if user.uuid != data['uuid']:
47
+ # user.remove()
59
48
  hiddify.quick_apply_users()
60
- return {'status': 200, 'msg': 'ok'}
49
+ return dbuser.to_schema()
61
50
 
62
51
  @app.output(SuccessfulSchema) # type: ignore
63
52
  def delete(self, uuid):
53
+ """User: Delete a User"""
64
54
  user = User.by_uuid(uuid) or abort(404, "user not found")
65
55
  if not has_permission(user):
66
56
  abort(403, "You don't have permission to access this user")
@@ -6,7 +6,7 @@ from hiddifypanel.models.role import Role
6
6
  from hiddifypanel.panel import hiddify
7
7
  from hiddifypanel.drivers import user_driver
8
8
  from hiddifypanel.models import User
9
- from .user_api import UserSchema
9
+ from .user_api import UserSchema, PostUserSchema
10
10
  from . import has_permission
11
11
 
12
12
 
@@ -15,5 +15,23 @@ class UsersApi(MethodView):
15
15
 
16
16
  @app.output(UserSchema(many=True)) # type: ignore
17
17
  def get(self):
18
+ """User: List users of current admin"""
19
+
18
20
  users = User.query.filter(User.added_by.in_(g.account.recursive_sub_admins_ids())).all() or abort(404, "You have no user")
19
21
  return [user.to_schema() for user in users] # type: ignore
22
+
23
+ @app.input(PostUserSchema, arg_name="data") # type: ignore
24
+ @app.output(UserSchema) # type: ignore
25
+ def post(self, data):
26
+ """User: Create a user"""
27
+
28
+ if data.get("uuid") and User.by_uuid(data['uuid']):
29
+ abort(400, 'The user exists')
30
+
31
+ if not data.get('added_by_uuid'):
32
+ data['added_by_uuid'] = g.account.uuid
33
+
34
+ dbuser = User.add_or_update(**data) or abort(502, "Unknown issue: User is not added")
35
+ user_driver.add_client(dbuser)
36
+ hiddify.quick_apply_users()
37
+ return dbuser.to_schema()
@@ -1,6 +1,6 @@
1
1
  from apiflask import APIBlueprint
2
2
 
3
- bp = APIBlueprint("api_child", __name__, url_prefix="/<proxy_path>/api/v2/child/", enable_openapi=True)
3
+ bp = APIBlueprint("api_child", __name__, url_prefix="/<proxy_path>/api/v2/child/", enable_openapi=False)
4
4
 
5
5
 
6
6
  def init_app(app):
@@ -2,22 +2,34 @@ from flask.views import MethodView
2
2
 
3
3
  from hiddifypanel.auth import login_required
4
4
  from hiddifypanel.models import Role
5
+ from .schema import PongOutputSchema
6
+ from flask import current_app as app
5
7
 
6
8
 
7
9
  class PingPongApi(MethodView):
8
10
  decorators = [login_required(roles={Role.super_admin, Role.admin, Role.agent}, node_auth=True)]
9
11
 
12
+ @app.output(PongOutputSchema) # type: ignore
10
13
  def get(self):
14
+ """Ping Pong: Get"""
11
15
  return {'msg': 'GET: PONG'}
12
16
 
17
+ @app.output(PongOutputSchema) # type: ignore
13
18
  def post(self):
19
+ """Ping Pong: Post"""
14
20
  return {'msg': 'POST: PONG'}
15
21
 
22
+ @app.output(PongOutputSchema) # type: ignore
16
23
  def patch(self):
24
+ """Ping Pong: Patch"""
17
25
  return {'msg': 'PATCH: PONG'}
18
26
 
27
+ @app.output(PongOutputSchema) # type: ignore
19
28
  def delete(self):
29
+ """Ping Pong: Delete"""
20
30
  return {'msg': 'DELETE: PONG'}
21
31
 
32
+ @app.output(PongOutputSchema) # type: ignore
22
33
  def put(self):
34
+ """Ping Pong: Put"""
23
35
  return {'msg': 'PUT: PONG'}
@@ -5,3 +5,8 @@ from apiflask import Schema, fields
5
5
  class PanelInfoOutputSchema(Schema):
6
6
  version = fields.String(description="The panel version")
7
7
  # endregion
8
+
9
+
10
+ class PongOutputSchema(Schema):
11
+ msg = fields.String(description="Pong Response")
12
+ # endregion
@@ -1,6 +1,6 @@
1
1
  from apiflask import APIBlueprint
2
2
 
3
- bp = APIBlueprint("api_parent", __name__, url_prefix="/<proxy_path>/api/v2/parent/", enable_openapi=True)
3
+ bp = APIBlueprint("api_parent", __name__, url_prefix="/<proxy_path>/api/v2/parent/", enable_openapi=False)
4
4
 
5
5
 
6
6
  def init_app(app):
@@ -2,7 +2,7 @@ from apiflask import APIBlueprint
2
2
 
3
3
  bp = APIBlueprint("api_user", __name__, url_prefix="/<proxy_path>/api/v2/user/", enable_openapi=True)
4
4
 
5
- bp_uuid = APIBlueprint("api_user_uuid", __name__, url_prefix="/<proxy_path>/<uuid:secret_uuid>/api/v2/user/", enable_openapi=True)
5
+ bp_uuid = APIBlueprint("api_user_by_uuid", __name__, url_prefix="/<proxy_path>/<uuid:secret_uuid>/api/v2/user/", enable_openapi=True)
6
6
 
7
7
 
8
8
  def init_app(app):
@@ -336,3 +336,34 @@ def get_backup_child_unique_id(backupdata: dict) -> str:
336
336
  if len(backupdata.get('childs', [])) == 0:
337
337
  return "self"
338
338
  return backupdata['childs'][0]['unique_id']
339
+
340
+
341
+ def all_configs_for_cli():
342
+ valid_users = [u.to_dict(dump_id=True) for u in User.query.filter((User.usage_limit > User.current_usage)).all() if u.is_active]
343
+ host_child_ids = [c.id for c in Child.query.filter(Child.mode == ChildMode.virtual).all()]
344
+ configs = {
345
+ "users": valid_users,
346
+ "domains": [u.to_dict(dump_ports=True, dump_child_id=True) for u in Domain.query.filter(Domain.child_id.in_(host_child_ids)).all() if "*" not in u.domain],
347
+ # "hconfigs": get_hconfigs(json=True),
348
+ "chconfigs": get_hconfigs_childs(host_child_ids, json=True)
349
+ }
350
+
351
+ def_user = None if len(User.query.all()) > 1 else User.query.filter(User.name == 'default').first()
352
+ domains = Domain.query.all()
353
+ sslip_domains = [d.domain for d in domains if "sslip.io" in d.domain]
354
+
355
+ configs['chconfigs'][0]['first_setup'] = def_user is not None and len(sslip_domains) > 0
356
+ server_ip = hutils.network.get_ip_str(4)
357
+ owner = AdminUser.get_super_admin()
358
+ configs['api_key'] = owner.uuid
359
+ configs['api_path'] = hconfig(ConfigEnum.proxy_path_admin)
360
+ configs['admin_path'] = get_account_panel_link(owner, server_ip, is_https=False, prefere_path_only=True)
361
+ configs['panel_links'] = []
362
+ configs['panel_links'].append(get_account_panel_link(owner, server_ip, is_https=False))
363
+ configs['panel_links'].append(get_account_panel_link(owner, server_ip))
364
+ domains = Domain.get_domains()
365
+
366
+ for d in domains:
367
+ configs['panel_links'].append(get_account_panel_link(owner, d.domain))
368
+
369
+ return configs
@@ -671,7 +671,6 @@ def upgrade_database():
671
671
 
672
672
  def init_db():
673
673
  db.create_all()
674
- cache.invalidate_all_cached_functions()
675
674
 
676
675
  # set_hconfig(ConfigEnum.db_version, 71)
677
676
  # temporary fix
@@ -680,6 +679,7 @@ def init_db():
680
679
  db_version = int(hconfig(ConfigEnum.db_version) or 0)
681
680
  if db_version == latest_db_version():
682
681
  return
682
+ cache.invalidate_all_cached_functions()
683
683
  migrate(db_version)
684
684
  Child.query.filter(Child.id == 0).first().mode = ChildMode.virtual
685
685
  # if db_version < 69: