Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement session authentication (removing uuid from url) #72

Merged
merged 114 commits into from
Dec 30, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
114 commits
Select commit Hold shift + click to select a range
b55c8a2
add: auth to api (incomplete)
Dec 10, 2023
e313142
add: username & password to user model and admin model
Dec 10, 2023
38ad0de
add: auth to admin apis
Dec 10, 2023
8780435
add: get_user_roles to authentication.py
Dec 10, 2023
f86d9e3
refactor: user apis
Dec 10, 2023
5a9c06f
fix: rename
Dec 10, 2023
10b81d7
add: auth to admin panel endpoints
Dec 10, 2023
1587cf1
using non-assci name in building username
Dec 11, 2023
f14f51b
fix: basic authentication verify password function
Dec 11, 2023
0a17700
add: athentication with session
Dec 11, 2023
53f6397
fix: add authentication(role-based) for ModelView classes
Dec 11, 2023
27d4518
add: admin route backward compatibility
Dec 13, 2023
9c3dd7e
chg: if client sent user/pass we try to authenticate with the user/pass,
Dec 13, 2023
2a1febf
chg: authentication
Dec 16, 2023
07a2d36
chg: remove <user_secret> from routing in blueprint
Dec 16, 2023
d9c63e0
add: some functions in utils
Dec 16, 2023
b9e03bb
add: api auth, basic auth, old url backward compatibility middlewares
Dec 16, 2023
f736c1a
fix: using g.account instead of g.user/g.admin
Dec 16, 2023
5d42c91
add: role authentication for views
Dec 16, 2023
47e4793
add: redirect_to_user.html
Dec 16, 2023
6c0344f
fix: setup session data for admin
Dec 17, 2023
67c3dd6
chg: rename
Dec 17, 2023
89fbee5
add: server-side(redis) session instead of client-side
Dec 17, 2023
f0dc241
fix: backward compatibility
Dec 17, 2023
4ca8129
fix: admin link in invalid proxy
Dec 17, 2023
3008b26
fix: remove links with uuid
Dec 17, 2023
7875a47
Merge branch 'auth' of github.com:Iam54r1n4/HiddifyPanel into auth
Dec 17, 2023
fb453fb
add: auth for apps api
Dec 17, 2023
b840da4
fix: creating empty session(in redis) for every request
Dec 18, 2023
2867a1a
fix: bug
Dec 18, 2023
6833842
add: admin log api
Dec 18, 2023
d8af20f
add: CORS for javascript calls
Dec 18, 2023
00ffece
fix: status js log file call to use api
Dec 18, 2023
9f32621
chg: new route page
Dec 19, 2023
3727e51
chg: init
Dec 19, 2023
76c1d81
chg: backward compatibility
Dec 19, 2023
9dd19fb
new: better handling user change
Dec 19, 2023
35c6933
support app changes
Dec 19, 2023
3a67c71
chg: apiflask authentication to auth_back.py file & remove unused
Dec 19, 2023
e011503
add: flask_login & add flask_login.UserMixin to User and AdminUser
Dec 19, 2023
fa2d4ab
using flask_login instead of old authenticator
Dec 19, 2023
f9fd1aa
add: role property to user and admin models & implement Role enum
Dec 19, 2023
8a6f2b9
new: auth.login_required (supports roles)
Dec 19, 2023
26b0713
using: new login_required
Dec 19, 2023
8e4f270
Merge commit '26b0713eba52700902e5b530b62a276e3d7dae0a' into auth
Dec 20, 2023
d07f294
fix: merging conflicts
Dec 20, 2023
4aa7e34
add: get current logged account apikey (draft)
Iam54r1n4 Dec 30, 2023
3bfa5cd
new: JS to get logs files in result.html
Iam54r1n4 Dec 30, 2023
3b7ba2e
new: use log api for getting logs
Iam54r1n4 Dec 30, 2023
8c4a6f6
add: new format of domain showing (https://user:pass@domain.com)
Iam54r1n4 Dec 30, 2023
80fa30e
fix: no need to send g.proxy_path to templates (jinja has it)
Iam54r1n4 Dec 30, 2023
b1b88d5
add: user:pass to /admin/adminuser/ links
Iam54r1n4 Dec 30, 2023
08d200f
fix: accept authentication link to login(eg.
Iam54r1n4 Dec 30, 2023
de0ccdc
fix: /admin/adminuser/ links username password bug
Iam54r1n4 Dec 30, 2023
40bf37d
fix: first try to authenticate with HTTP Authorization header value
Iam54r1n4 Dec 30, 2023
818058c
fix: bug
Iam54r1n4 Dec 30, 2023
cb27616
add: user:pass to non-secure link too
Iam54r1n4 Dec 30, 2023
22ca465
add: authentication for user panel
Iam54r1n4 Dec 30, 2023
334939a
fix: backward compatibility
Iam54r1n4 Dec 30, 2023
1fa739e
fix: new.html assests link
Iam54r1n4 Dec 30, 2023
e9a079d
new: redirect old apis url to new url
Iam54r1n4 Dec 30, 2023
f06fccd
fix: bug & authenticate api calls with session cookie
Iam54r1n4 Dec 30, 2023
c2d05f2
new: add login_required to user's apis
Iam54r1n4 Dec 30, 2023
997e431
add: login_required to quicksetup
Iam54r1n4 Dec 30, 2023
8e09af0
fix: bug
Iam54r1n4 Dec 30, 2023
46f35d7
fix: fill username & password in user/admin creation
Iam54r1n4 Dec 30, 2023
f83c057
fix: init_db filling user/admin username|passowrd fields
Iam54r1n4 Dec 30, 2023
b9fca95
clean up
Iam54r1n4 Dec 30, 2023
7fdf6ee
chg: better names
Iam54r1n4 Dec 30, 2023
5bc5b2a
fix: typo
Iam54r1n4 Dec 30, 2023
f8232b0
fix: typo & imports
Iam54r1n4 Dec 30, 2023
80453e3
chg: basic athentication realm value
Iam54r1n4 Dec 30, 2023
ff0ed17
fix: better account type selection in parse_auth_id
Iam54r1n4 Dec 30, 2023
6af56ac
fix: domain in api
Iam54r1n4 Dec 30, 2023
a2353b0
fix: disable CORS for all endpoints and enable it just for adminlog api
Iam54r1n4 Dec 30, 2023
de68dad
chg: better readability
Iam54r1n4 Dec 30, 2023
9fbf1f4
chg: send api calls with cookie instead of ApiKey
Iam54r1n4 Dec 30, 2023
2d364f6
chg: using server_status log api
Iam54r1n4 Dec 30, 2023
c19612d
chg: authenticate user panel endponit with Authorization header even
Iam54r1n4 Dec 30, 2023
61b5d25
add: user/pass to user admin links
Iam54r1n4 Dec 30, 2023
5f89280
chg: refactor
Iam54r1n4 Dec 30, 2023
7540c40
add: user/pass to hiddifypanel admin-links command(cli)
Iam54r1n4 Dec 30, 2023
ee36b9e
refactor
Iam54r1n4 Dec 30, 2023
524047d
chg: APIs links to new format
Iam54r1n4 Dec 30, 2023
3c7a320
del: using g.account.uuid instead of g.account_uuid
Iam54r1n4 Dec 30, 2023
5a80ee3
chg: refactor
Iam54r1n4 Dec 30, 2023
c2ba50b
add: new fields to str_config(proxy paths)
Iam54r1n4 Dec 30, 2023
9d143ea
clean: unused var
Iam54r1n4 Dec 30, 2023
0caf45d
fix: add just proxy_path_admin and proxy_path_client
Iam54r1n4 Dec 30, 2023
a2d1f2a
fix: typo in redirect.html file
Iam54r1n4 Dec 30, 2023
9381402
chg: api v1 blueprint name
Iam54r1n4 Dec 30, 2023
ec9462a
new: handle new proxy paths (test)
Iam54r1n4 Dec 30, 2023
54da087
fix: bug
Iam54r1n4 Dec 30, 2023
6463c01
fix: backward compatibility
Iam54r1n4 Dec 30, 2023
a57cf52
using: proper using of proxy path (for panel links)
Iam54r1n4 Dec 30, 2023
0def804
new: implement custom flask-login for
Iam54r1n4 Dec 30, 2023
4436cbb
del: unused var
Iam54r1n4 Dec 30, 2023
2e88920
add: get by username password
Iam54r1n4 Dec 30, 2023
c2a5b89
new: account atuhentication approach
Iam54r1n4 Dec 30, 2023
bd64c04
chg: admin login links
Iam54r1n4 Dec 30, 2023
f132a26
fix: api v1 telegram endpoints
Iam54r1n4 Dec 30, 2023
b82f4a7
fix: remove user/pass from short api link
Iam54r1n4 Dec 30, 2023
0a17460
fix: api v1 routing
Iam54r1n4 Dec 30, 2023
73292a7
fix: short api
Iam54r1n4 Dec 30, 2023
b0cb11f
fix: short api again
Iam54r1n4 Dec 30, 2023
7b71ad6
fix: auto removing UUIDs from api requests
Iam54r1n4 Dec 30, 2023
9624462
del: duplicate function
Iam54r1n4 Dec 30, 2023
c97c076
new: check permission for AdminUsersApi
Iam54r1n4 Dec 30, 2023
1c4fdbf
new: check permission for AdminUserApi
Iam54r1n4 Dec 30, 2023
e112324
fix: bug in QuickSetup
Iam54r1n4 Dec 30, 2023
37d45eb
fix: bug
Iam54r1n4 Dec 30, 2023
956032d
new: base class for admin and user models
Iam54r1n4 Dec 30, 2023
cd2365f
fix: admin me api
Iam54r1n4 Dec 30, 2023
8f7aedd
fix: proxy path validation
Iam54r1n4 Dec 30, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 22 additions & 6 deletions hiddifypanel/base.py
Original file line number Diff line number Diff line change
@@ -1,30 +1,33 @@
import flask_bootstrap
import hiddifypanel
from dynaconf import FlaskDynaconf
from flask import Flask, request, g
from flask import request, g
from flask_babelex import Babel
from hiddifypanel.panel.init_db import init_db
from flask_session import Session
from flask_cors import CORS
import datetime

from hiddifypanel.models import *
from dotenv import dotenv_values
import os
from hiddifypanel.panel import hiddify
from apiflask import APIFlask
from werkzeug.middleware.proxy_fix import ProxyFix
from hiddifypanel.models import *
from hiddifypanel.panel.init_db import init_db
from hiddifypanel.cache import redis_client


def create_app(cli=False, **config):

app = APIFlask(__name__, static_url_path="/<proxy_path>/static/", instance_relative_config=True, version='2.0.0', title="Hiddify API",
openapi_blueprint_url_prefix="/<proxy_path>/<user_secret>/api", docs_ui='elements', json_errors=False, enable_openapi=True)
openapi_blueprint_url_prefix="/<proxy_path>/api", docs_ui='elements', json_errors=False, enable_openapi=True)
# app = Flask(__name__, static_url_path="/<proxy_path>/static/", instance_relative_config=True)
app.wsgi_app = ProxyFix(
app.wsgi_app, x_for=1, x_proto=1, x_host=1, x_prefix=1
)
app.servers = {
'name': 'current',
'url': '',
}
} # type: ignore
app.info = {
'description': 'Hiddify is a free and open source software. It is as it is.',
'termsOfService': 'http://hiddify.com',
Expand All @@ -47,6 +50,15 @@ def create_app(cli=False, **config):

app.config[c] = v

# setup flask server-side session
# app.config['APPLICATION_ROOT'] = './'
# app.config['SESSION_COOKIE_DOMAIN'] = '/'
app.config['SESSION_TYPE'] = 'redis'
app.config['SESSION_REDIS'] = redis_client
app.config['SESSION_PERMANENT'] = False
app.config['PERMANENT_SESSION_LIFETIME'] = datetime.timedelta(days=7)
Session(app)

app.jinja_env.line_statement_prefix = '%'
app.jinja_env.filters['b64encode'] = hiddify.do_base_64
app.view_functions['admin.static'] = {} # fix bug in apiflask
Expand All @@ -57,6 +69,7 @@ def create_app(cli=False, **config):
with app.app_context():
init_db()

hiddifypanel.panel.auth.init_app(app)
hiddifypanel.panel.common.init_app(app)
hiddifypanel.panel.admin.init_app(app)
hiddifypanel.panel.user.init_app(app)
Expand Down Expand Up @@ -99,7 +112,10 @@ def check_csrf():
@app.after_request
def apply_no_robot(response):
response.headers["X-Robots-Tag"] = "noindex, nofollow"
if response.status_code == 401:
response.headers['WWW-Authenticate'] = 'Basic realm="Hiddify"'
return response
Iam54r1n4 marked this conversation as resolved.
Show resolved Hide resolved

app.jinja_env.globals['get_locale'] = get_locale
app.jinja_env.globals['version'] = hiddifypanel.__version__
app.jinja_env.globals['static_url_for'] = hiddify.static_url_for
Expand Down
144 changes: 118 additions & 26 deletions hiddifypanel/hutils/utils.py
Original file line number Diff line number Diff line change
@@ -1,32 +1,17 @@
import socket
import base64
from typing import Any, Tuple
from urllib.parse import urlparse
from uuid import UUID
import user_agents
from sqlalchemy.orm import Load
import glob
import json
from babel.dates import format_timedelta as babel_format_timedelta
from flask_babelex import gettext as __
from flask_babelex import lazy_gettext as _
import datetime
from flask import jsonify, g, url_for, Markup, abort, current_app, request
from datetime import datetime
from flask import url_for, Markup
from flask import flash as flask_flash
import re
from wtforms.validators import ValidationError
import requests

import string
import random
from babel.dates import format_timedelta as babel_format_timedelta
import urllib
import time
import os
import psutil
from urllib.parse import urlparse
import ssl
import h2.connection
import subprocess
import netifaces
import time
import sys

from hiddifypanel.cache import cache
Expand All @@ -37,13 +22,15 @@ def url_encode(strr):
import urllib.parse
return urllib.parse.quote(strr)

def is_uuid_valid(uuid,version):

def is_uuid_valid(uuid, version):
try:
uuid_obj = UUID(uuid, version=version)
except ValueError:
return False
return str(uuid_obj) == uuid


def error(str):

print(str, file=sys.stderr)
Expand All @@ -53,11 +40,14 @@ def static_url_for(**values):
orig = url_for("static", **values)
return orig.split("user_secret")[0]


@cache.cache(ttl=60000)
def get_latest_release_url(repo):
latest_url = requests.get(f'{repo}/releases/latest').url.strip()
version = latest_url.split('tag/')[1].strip()
return (latest_url,version)
latest_url = requests.get(f'{repo}/releases/latest').url.strip()
version = latest_url.split('tag/')[1].strip()
return (latest_url, version)


@cache.cache(ttl=600)
def get_latest_release_version(repo_name):
try:
Expand Down Expand Up @@ -104,13 +94,29 @@ def get_random_string(min_=10, max_=30):
return result_str


def get_random_password(length: int = 16) -> str:
'''Retunrns a random password with fixed length'''
characters = string.ascii_letters + string.digits # + '-'
while True:
passwd = ''.join(random.choice(characters) for i in range(length))
if (any(c.islower() for c in passwd) and any(c.isupper() for c in passwd) and sum(c.isdigit() for c in passwd) > 1):
return passwd


def is_assci_alphanumeric(str):
for c in str:
if c not in string.ascii_letters + string.digits:
return False
return True


def date_to_json(d):
return d.strftime("%Y-%m-%d") if d else None


def json_to_date(date_str):
try:
return datetime.datetime.strptime(date_str, '%Y-%m-%d')
return datetime.strptime(date_str, '%Y-%m-%d')
except:
return date_str

Expand All @@ -122,11 +128,97 @@ def time_to_json(d):

def json_to_time(time_str):
try:
return datetime.datetime.strptime(time_str, "%Y-%m-%d %H:%M:%S")
return datetime.strptime(time_str, "%Y-%m-%d %H:%M:%S")
except:
return time_str


def flash(message, category):
print(message)
return flask_flash(Markup(message), category)


def get_proxy_path_from_url(url: str) -> str | None:
url_path = urlparse(url).path
proxy_path = url_path.lstrip('/').split('/')[0] or None
return proxy_path


def is_uuid_in_url_path(path: str) -> bool:
for section in path.split('/'):
if is_uuid_valid(section, 4):
return True
return False


def get_uuid_from_url_path(path: str, section_index: int = 2) -> str | None:
"""
Takes a URL path and extracts the UUID at the specified section index.

Args:
path (str): The URL path from which to extract the UUID.
section_index (int, optional): The index of the section in the URL path where the UUID is located. Defaults to 2, because in past the UUID was in the second section of path of url.

Returns:
str | None: The extracted UUID as a string if found, or None if not found.
"""
s_index = 1
for section in path.lstrip('/').split('/'):
if is_uuid_valid(section, 4):
if s_index == section_index:
return section
s_index += 1
return None


def get_apikey_from_auth_header(auth_header: str) -> str | None:
if auth_header.startswith('ApiKey'):
return auth_header.split('ApiKey ')[1].strip()
return None


def get_basic_auth_from_auth_header(auth_header: str) -> str | None:
if auth_header.startswith('Basic'):
return auth_header.split('Basic ')[1].strip()
return None


def parse_basic_auth_header(auth_header: str) -> tuple[str, str] | None:
if not auth_header.startswith('Basic'):
return None
header_value = auth_header.split('Basic ')
if len(header_value) < 2:
return None
username, password = map(lambda item: item.strip(), base64.urlsafe_b64decode(header_value[1].strip()).decode('utf-8').split(':'))
return (username, password) if username and password else None


def parse_login_id(raw_id) -> Tuple[Any | None, str | None]:
"""
Parses the given raw ID to extract the account type and ID.
Args:
raw_id (str): The raw ID to be parsed.
Returns:
Tuple[Any | None, str | None]: A tuple containing the account type and ID.
The account type is of type Role (either Role.user or Role.admin)
and the ID is a string. If the raw ID cannot be parsed, None is returned
for both the account type and ID.
"""
splitted = raw_id.split('_')
if len(splitted) < 2:
return None, None
admin_or_user, id = splitted
from hiddifypanel.models.role import Role
Iam54r1n4 marked this conversation as resolved.
Show resolved Hide resolved
account_type = Role.admin if admin_or_user == 'admin' else Role.user
if not id or not account_type:
Iam54r1n4 marked this conversation as resolved.
Show resolved Hide resolved
return None, None
return account_type, id


def add_basic_auth_to_url(url: str, username: str, password: str) -> str:
if 'https://' in url:
return url.replace('https://', f'https://{username}:{password}@')
elif 'http:// ' in url:
return url.replace('http://', f'http://{username}:{password}@')
else:
return url
7 changes: 4 additions & 3 deletions hiddifypanel/models/__init__.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
from .config_enum import ConfigCategory, ConfigEnum,Lang
from .config_enum import ConfigCategory, ConfigEnum, Lang
from .config import StrConfig, BoolConfig, get_hconfigs, hconfig, set_hconfig, add_or_update_config, bulk_register_configs

from .parent_domain import ParentDomain, add_or_update_parent_domains, bulk_register_parent_domains
from .domain import Domain, DomainType, ShowDomain, get_domain, get_current_proxy_domains, get_panel_domains, get_proxy_domains, get_proxy_domains_db, get_hdomains, hdomain, add_or_update_domain, bulk_register_domains
from .proxy import Proxy, ProxyL3, ProxyCDN, ProxyProto, ProxyTransport, add_or_update_proxy, bulk_register_proxies
from .user import User, UserMode, is_user_active, remaining_days, days_to_reset, user_by_id, user_by_uuid, add_or_update_user,remove_user, user_should_reset, add_or_update_user, bulk_register_users, UserDetail, ONE_GIG
from .admin import AdminUser, AdminMode, get_super_admin_secret, get_admin_user_db, get_admin_by_uuid, add_or_update_admin, bulk_register_admins, current_admin_or_owner, get_super_admin
from .user import User, UserMode, is_user_active, remaining_days, days_to_reset, user_by_id, get_user_by_uuid, add_or_update_user, remove_user, user_should_reset, add_or_update_user, bulk_register_users, UserDetail, ONE_GIG, get_user_by_username, get_user_by_username_password
from .admin import AdminUser, AdminMode, get_super_admin_uuid, get_admin_by_uuid, add_or_update_admin, bulk_register_admins, current_admin_or_owner, get_super_admin, get_admin_by_username, get_admin_by_username_password
from .child import Child
from .usage import DailyUsage, get_daily_usage_stats
from .role import Role
Loading