Nexom 0.1.3__py3-none-any.whl → 1.0.1__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.
- nexom/__init__.py +2 -2
- nexom/__main__.py +111 -17
- nexom/app/__init__.py +62 -0
- nexom/app/auth.py +322 -0
- nexom/{web → app}/cookie.py +4 -2
- nexom/app/db.py +88 -0
- nexom/app/path.py +195 -0
- nexom/app/request.py +267 -0
- nexom/{web → app}/response.py +13 -3
- nexom/{web → app}/template.py +1 -1
- nexom/app/user.py +31 -0
- nexom/assets/app/__init__.py +0 -0
- nexom/assets/app/__pycache__/__init__.cpython-313.pyc +0 -0
- nexom/assets/app/config.py +28 -0
- nexom/assets/app/gunicorn.conf.py +5 -0
- nexom/assets/app/pages/__pycache__/__init__.cpython-313.pyc +0 -0
- nexom/assets/app/pages/_templates.py +7 -0
- nexom/assets/{server → app}/pages/default.py +2 -2
- nexom/assets/{server → app}/pages/document.py +2 -2
- nexom/assets/app/router.py +12 -0
- nexom/assets/app/wsgi.py +64 -0
- nexom/assets/auth/__init__.py +0 -0
- nexom/assets/auth/__pycache__/__init__.cpython-313.pyc +0 -0
- nexom/assets/auth/config.py +27 -0
- nexom/assets/auth/gunicorn.conf.py +5 -0
- nexom/assets/auth/wsgi.py +62 -0
- nexom/assets/auth_page/login.html +95 -0
- nexom/assets/auth_page/signup.html +106 -0
- nexom/assets/error_page/error.html +3 -3
- nexom/assets/gateway/apache_app.conf +16 -0
- nexom/assets/gateway/nginx_app.conf +21 -0
- nexom/buildTools/__init__.py +1 -0
- nexom/buildTools/build.py +274 -54
- nexom/buildTools/run.py +185 -0
- nexom/core/__init__.py +2 -1
- nexom/core/error.py +81 -3
- nexom/core/log.py +111 -0
- nexom/{engine → core}/object_html_render.py +4 -1
- nexom/templates/__init__.py +0 -0
- nexom/templates/auth.py +72 -0
- {nexom-0.1.3.dist-info → nexom-1.0.1.dist-info}/METADATA +75 -50
- nexom-1.0.1.dist-info/RECORD +56 -0
- {nexom-0.1.3.dist-info → nexom-1.0.1.dist-info}/WHEEL +1 -1
- nexom/assets/server/config.py +0 -27
- nexom/assets/server/gunicorn.conf.py +0 -16
- nexom/assets/server/pages/__pycache__/__init__.cpython-313.pyc +0 -0
- nexom/assets/server/pages/_templates.py +0 -11
- nexom/assets/server/router.py +0 -18
- nexom/assets/server/wsgi.py +0 -30
- nexom/engine/__init__.py +0 -1
- nexom/web/__init__.py +0 -5
- nexom/web/path.py +0 -125
- nexom/web/request.py +0 -62
- nexom-0.1.3.dist-info/RECORD +0 -39
- /nexom/{web → app}/http_status_codes.py +0 -0
- /nexom/{web → app}/middleware.py +0 -0
- /nexom/assets/{server → app}/pages/__init__.py +0 -0
- /nexom/assets/{server → app}/static/dog.jpeg +0 -0
- /nexom/assets/{server → app}/static/style.css +0 -0
- /nexom/assets/{server → app}/templates/base.html +0 -0
- /nexom/assets/{server → app}/templates/default.html +0 -0
- /nexom/assets/{server → app}/templates/document.html +0 -0
- /nexom/assets/{server → app}/templates/footer.html +0 -0
- /nexom/assets/{server → app}/templates/header.html +0 -0
- {nexom-0.1.3.dist-info → nexom-1.0.1.dist-info}/entry_points.txt +0 -0
- {nexom-0.1.3.dist-info → nexom-1.0.1.dist-info}/licenses/LICENSE +0 -0
- {nexom-0.1.3.dist-info → nexom-1.0.1.dist-info}/top_level.txt +0 -0
|
Binary file
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Nexom auth server settings.
|
|
3
|
+
|
|
4
|
+
This file is generated by Nexom buildTools.
|
|
5
|
+
"""
|
|
6
|
+
# ======== project ========
|
|
7
|
+
PROJECT_DIR: str = "__prj_dir__"
|
|
8
|
+
APP_DIR: str = "__app_dir__"
|
|
9
|
+
TEMPLATES_DIR: str = "__app_dir__/templates"
|
|
10
|
+
|
|
11
|
+
DATA_DIR: str = "__prj_dir__/data"
|
|
12
|
+
|
|
13
|
+
# ======== gunicorn ========
|
|
14
|
+
ADDRESS: str = "__g_address__"
|
|
15
|
+
PORT: int = __g_port__
|
|
16
|
+
WORKERS: int = __g_workers__
|
|
17
|
+
RELOAD: bool = __g_reload__
|
|
18
|
+
|
|
19
|
+
# ======== auth service ========
|
|
20
|
+
AUTH_DB: str = DATA_DIR + "/db/auth.db"
|
|
21
|
+
|
|
22
|
+
# ======== logger ========
|
|
23
|
+
INFO_LOG: str = "__prj_dir__/data/log/__app_name__/info.log"
|
|
24
|
+
WARN_LOG: str = "__prj_dir__/data/log/__app_name__/warning.log"
|
|
25
|
+
ERR_LOG: str = "__prj_dir__/data/log/__app_name__/error.log"
|
|
26
|
+
ACES_LOG: str = "__prj_dir__/data/log/__app_name__/access.log"
|
|
27
|
+
AUTH_LOG: str = "__prj_dir__/data/log/__app_name__/auth.log"
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import Callable, Iterable
|
|
4
|
+
import time
|
|
5
|
+
|
|
6
|
+
from nexom.app.request import Request
|
|
7
|
+
from nexom.app.response import JsonResponse
|
|
8
|
+
from nexom.core.error import NexomError
|
|
9
|
+
from nexom.core.log import AppLogger, AuthLogger
|
|
10
|
+
from nexom.app.auth import AuthService
|
|
11
|
+
|
|
12
|
+
from auth.config import AUTH_DB, INFO_LOG, WARN_LOG, ERR_LOG, ACES_LOG, AUTH_LOG
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
# Logger
|
|
16
|
+
logger = AppLogger(
|
|
17
|
+
info=INFO_LOG,
|
|
18
|
+
warn=WARN_LOG,
|
|
19
|
+
error=ERR_LOG,
|
|
20
|
+
access=ACES_LOG,
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
# AuthService
|
|
24
|
+
service = AuthService(AUTH_DB, AUTH_LOG)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def _ip(environ: dict) -> str:
|
|
28
|
+
xff = environ.get("HTTP_X_FORWARDED_FOR")
|
|
29
|
+
if isinstance(xff, str) and xff.strip():
|
|
30
|
+
return xff.split(",")[0].strip()
|
|
31
|
+
return str(environ.get("REMOTE_ADDR") or "-")
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def app(environ: dict, start_response: Callable) -> Iterable[bytes]:
|
|
35
|
+
"""
|
|
36
|
+
WSGI application entrypoint.
|
|
37
|
+
"""
|
|
38
|
+
try:
|
|
39
|
+
t0 = time.time()
|
|
40
|
+
|
|
41
|
+
res = service.handler(environ)
|
|
42
|
+
|
|
43
|
+
except NexomError as e:
|
|
44
|
+
logger.error(e)
|
|
45
|
+
return JsonResponse({"error": e.code})
|
|
46
|
+
except Exception:
|
|
47
|
+
logger.error(e)
|
|
48
|
+
return JsonResponse({"error": "Internal Server Error"})
|
|
49
|
+
|
|
50
|
+
try:
|
|
51
|
+
req = Request(environ)
|
|
52
|
+
dt_ms = int((time.time() - t0) * 1000)
|
|
53
|
+
method = req.method if req else str(environ.get("REQUEST_METHOD") or "-")
|
|
54
|
+
path = (req.path if req else str(environ.get("PATH_INFO") or "")).lstrip("/")
|
|
55
|
+
ip = _ip(environ)
|
|
56
|
+
ua = str(environ.get("HTTP_USER_AGENT") or "-")
|
|
57
|
+
logger.access(f'{ip} "{method} /{path}" {res.status_code} {dt_ms}ms "{ua}"')
|
|
58
|
+
except Exception:
|
|
59
|
+
pass
|
|
60
|
+
|
|
61
|
+
start_response(res.status_text, res.headers)
|
|
62
|
+
return [res.body]
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
<!-- assets/auth_page/login.html -->
|
|
2
|
+
<!doctype html>
|
|
3
|
+
<html lang="ja">
|
|
4
|
+
<head>
|
|
5
|
+
<meta charset="utf-8" />
|
|
6
|
+
<meta name="viewport" content="width=device-width,initial-scale=1" />
|
|
7
|
+
<title>Nexom Login</title>
|
|
8
|
+
<style>
|
|
9
|
+
body { font-family: system-ui, -apple-system, Segoe UI, Roboto, sans-serif; margin: 24px; }
|
|
10
|
+
.wrap { max-width: 720px; margin: 0 auto; }
|
|
11
|
+
.card { border: 1px solid #eee; border-radius: 14px; padding: 16px; background: #fff; }
|
|
12
|
+
label { display:block; font-size:12px; margin: 12px 0 6px; color:#333; }
|
|
13
|
+
input { width:100%; padding:10px 12px; border:1px solid #ddd; border-radius:10px; font-size:14px; }
|
|
14
|
+
button { margin-top: 14px; padding:10px 12px; border:1px solid #333; border-radius:10px; background:#333; color:#fff; cursor:pointer; }
|
|
15
|
+
.row { display:flex; gap:10px; align-items:center; flex-wrap:wrap; margin-top: 10px; }
|
|
16
|
+
a { color:#333; }
|
|
17
|
+
.help { color:#666; font-size:12px; }
|
|
18
|
+
.log { margin-top: 14px; padding: 12px; border-radius: 12px; background:#f7f7f7; border:1px solid #eee; white-space: pre-wrap;
|
|
19
|
+
font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 12px; }
|
|
20
|
+
.ok { color:#0a7; }
|
|
21
|
+
.ng { color:#c33; }
|
|
22
|
+
</style>
|
|
23
|
+
</head>
|
|
24
|
+
<body>
|
|
25
|
+
<div class="wrap">
|
|
26
|
+
<h1 style="margin:0 0 12px;font-size:20px;">Login</h1>
|
|
27
|
+
|
|
28
|
+
<section class="card">
|
|
29
|
+
<form id="formLogin">
|
|
30
|
+
<label for="user_id">user_id</label>
|
|
31
|
+
<input id="user_id" name="user_id" autocomplete="username" required />
|
|
32
|
+
|
|
33
|
+
<label for="password">password</label>
|
|
34
|
+
<input id="password" name="password" type="password" autocomplete="current-password" required />
|
|
35
|
+
|
|
36
|
+
<button type="submit">login</button>
|
|
37
|
+
|
|
38
|
+
<div class="row">
|
|
39
|
+
<span class="help">送信先はこのアプリの <code>/login/</code>(アプリ → AuthClient → AuthService)</span>
|
|
40
|
+
<a class="help" href="/signup/">signupへ</a>
|
|
41
|
+
</div>
|
|
42
|
+
</form>
|
|
43
|
+
|
|
44
|
+
<div id="log" class="log">ready</div>
|
|
45
|
+
</section>
|
|
46
|
+
</div>
|
|
47
|
+
|
|
48
|
+
<script>
|
|
49
|
+
const logEl = document.getElementById("log");
|
|
50
|
+
|
|
51
|
+
function pretty(v) {
|
|
52
|
+
try { return JSON.stringify(v, null, 2); } catch { return String(v); }
|
|
53
|
+
}
|
|
54
|
+
function writeLog(ok, title, data) {
|
|
55
|
+
const cls = ok ? "ok" : "ng";
|
|
56
|
+
logEl.innerHTML = `<span class="${cls}">${title}</span>\n${pretty(data)}`;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
async function postJSON(url, bodyObj) {
|
|
60
|
+
const res = await fetch(url, {
|
|
61
|
+
method: "POST",
|
|
62
|
+
headers: {
|
|
63
|
+
"Content-Type": "application/json; charset=utf-8",
|
|
64
|
+
"Accept": "application/json",
|
|
65
|
+
},
|
|
66
|
+
body: JSON.stringify(bodyObj ?? {}),
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
const text = await res.text();
|
|
70
|
+
let data = {};
|
|
71
|
+
try { data = text ? JSON.parse(text) : {}; } catch { data = { raw: text }; }
|
|
72
|
+
|
|
73
|
+
return { ok: res.ok, status: res.status, data };
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
document.getElementById("formLogin").addEventListener("submit", async (e) => {
|
|
77
|
+
e.preventDefault();
|
|
78
|
+
|
|
79
|
+
const user_id = document.getElementById("user_id").value.trim();
|
|
80
|
+
const password = document.getElementById("password").value;
|
|
81
|
+
|
|
82
|
+
if (!user_id || !password) {
|
|
83
|
+
writeLog(false, "login: missing fields", { user_id });
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const r = await postJSON("/login/", { user_id, password });
|
|
88
|
+
writeLog(r.ok, `login: ${r.status}`, r.data);
|
|
89
|
+
|
|
90
|
+
// 例: token を localStorage に保存したいならここ(仕様次第)
|
|
91
|
+
// if (r.ok && r.data && r.data.token) localStorage.setItem("nexom_token", r.data.token);
|
|
92
|
+
});
|
|
93
|
+
</script>
|
|
94
|
+
</body>
|
|
95
|
+
</html>
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
<!-- assets/auth_page/signup.html -->
|
|
2
|
+
<!doctype html>
|
|
3
|
+
<html lang="ja">
|
|
4
|
+
<head>
|
|
5
|
+
<meta charset="utf-8" />
|
|
6
|
+
<meta name="viewport" content="width=device-width,initial-scale=1" />
|
|
7
|
+
<title>Nexom Signup</title>
|
|
8
|
+
<style>
|
|
9
|
+
body { font-family: system-ui, -apple-system, Segoe UI, Roboto, sans-serif; margin: 24px; }
|
|
10
|
+
.wrap { max-width: 720px; margin: 0 auto; }
|
|
11
|
+
.card { border: 1px solid #eee; border-radius: 14px; padding: 16px; background: #fff; }
|
|
12
|
+
label { display:block; font-size:12px; margin: 12px 0 6px; color:#333; }
|
|
13
|
+
input { width:100%; padding:10px 12px; border:1px solid #ddd; border-radius:10px; font-size:14px; }
|
|
14
|
+
button { margin-top: 14px; padding:10px 12px; border:1px solid #333; border-radius:10px; background:#333; color:#fff; cursor:pointer; }
|
|
15
|
+
.row { display:flex; gap:10px; align-items:center; flex-wrap:wrap; margin-top: 10px; }
|
|
16
|
+
a { color:#333; }
|
|
17
|
+
.help { color:#666; font-size:12px; }
|
|
18
|
+
.log { margin-top: 14px; padding: 12px; border-radius: 12px; background:#f7f7f7; border:1px solid #eee; white-space: pre-wrap;
|
|
19
|
+
font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 12px; }
|
|
20
|
+
.ok { color:#0a7; }
|
|
21
|
+
.ng { color:#c33; }
|
|
22
|
+
</style>
|
|
23
|
+
</head>
|
|
24
|
+
<body>
|
|
25
|
+
<div class="wrap">
|
|
26
|
+
<h1 style="margin:0 0 12px;font-size:20px;">Signup</h1>
|
|
27
|
+
|
|
28
|
+
<section class="card">
|
|
29
|
+
<form id="formSignup">
|
|
30
|
+
<label for="user_id">user_id</label>
|
|
31
|
+
<input id="user_id" name="user_id" autocomplete="username" required />
|
|
32
|
+
|
|
33
|
+
<label for="public_name">public_name</label>
|
|
34
|
+
<input id="public_name" name="public_name" autocomplete="nickname" required />
|
|
35
|
+
|
|
36
|
+
<label for="password">password</label>
|
|
37
|
+
<input id="password" name="password" type="password" autocomplete="new-password" required />
|
|
38
|
+
|
|
39
|
+
<label for="password2">password (confirm)</label>
|
|
40
|
+
<input id="password2" name="password2" type="password" autocomplete="new-password" required />
|
|
41
|
+
|
|
42
|
+
<button type="submit">signup</button>
|
|
43
|
+
|
|
44
|
+
<div class="row">
|
|
45
|
+
<span class="help">送信先はこのアプリの <code>/signup/</code>(アプリ → AuthClient → AuthService)</span>
|
|
46
|
+
<a class="help" href="/login/">loginへ</a>
|
|
47
|
+
</div>
|
|
48
|
+
</form>
|
|
49
|
+
|
|
50
|
+
<div id="log" class="log">ready</div>
|
|
51
|
+
</section>
|
|
52
|
+
</div>
|
|
53
|
+
|
|
54
|
+
<script>
|
|
55
|
+
const logEl = document.getElementById("log");
|
|
56
|
+
|
|
57
|
+
function pretty(v) {
|
|
58
|
+
try { return JSON.stringify(v, null, 2); } catch { return String(v); }
|
|
59
|
+
}
|
|
60
|
+
function writeLog(ok, title, data) {
|
|
61
|
+
const cls = ok ? "ok" : "ng";
|
|
62
|
+
logEl.innerHTML = `<span class="${cls}">${title}</span>\n${pretty(data)}`;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
async function postJSON(url, bodyObj) {
|
|
66
|
+
const res = await fetch(url, {
|
|
67
|
+
method: "POST",
|
|
68
|
+
headers: {
|
|
69
|
+
"Content-Type": "application/json; charset=utf-8",
|
|
70
|
+
"Accept": "application/json",
|
|
71
|
+
},
|
|
72
|
+
body: JSON.stringify(bodyObj ?? {}),
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
const text = await res.text();
|
|
76
|
+
let data = {};
|
|
77
|
+
try { data = text ? JSON.parse(text) : {}; } catch { data = { raw: text }; }
|
|
78
|
+
|
|
79
|
+
return { ok: res.ok, status: res.status, data };
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
document.getElementById("formSignup").addEventListener("submit", async (e) => {
|
|
83
|
+
e.preventDefault();
|
|
84
|
+
|
|
85
|
+
const user_id = document.getElementById("user_id").value.trim();
|
|
86
|
+
const public_name = document.getElementById("public_name").value.trim();
|
|
87
|
+
const password = document.getElementById("password").value;
|
|
88
|
+
const password2 = document.getElementById("password2").value;
|
|
89
|
+
|
|
90
|
+
if (!user_id || !public_name || !password) {
|
|
91
|
+
writeLog(false, "signup: missing fields", { user_id, public_name });
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
if (password !== password2) {
|
|
95
|
+
writeLog(false, "signup: password mismatch", {});
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const r = await postJSON("/signup/", { user_id, public_name, password });
|
|
100
|
+
writeLog(r.ok, `signup: ${r.status}`, r.data);
|
|
101
|
+
|
|
102
|
+
// if (r.ok) location.href = "/login/";
|
|
103
|
+
});
|
|
104
|
+
</script>
|
|
105
|
+
</body>
|
|
106
|
+
</html>
|
|
@@ -3,12 +3,12 @@
|
|
|
3
3
|
<head>
|
|
4
4
|
<meta charset="UTF-8">
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
|
-
<title>
|
|
6
|
+
<title>{{ status }}</title>
|
|
7
7
|
</head>
|
|
8
8
|
<body>
|
|
9
9
|
<div class="error_container">
|
|
10
|
-
<div class="error_name">
|
|
11
|
-
<div class="error_message">
|
|
10
|
+
<div class="error_name">{{ status }}</div>
|
|
11
|
+
<div class="error_message">{{ message }}</div>
|
|
12
12
|
</div>
|
|
13
13
|
</body>
|
|
14
14
|
</html>
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# __APP_NAME__ gateway (apache)
|
|
2
|
+
# Domain: __DOMAIN__
|
|
3
|
+
#
|
|
4
|
+
# NOTE:
|
|
5
|
+
# - Replace __DOMAIN__ with your real domain (or pass --domain).
|
|
6
|
+
# - This file is a template.
|
|
7
|
+
|
|
8
|
+
<VirtualHost *:80>
|
|
9
|
+
ServerName __DOMAIN__
|
|
10
|
+
|
|
11
|
+
ProxyPreserveHost On
|
|
12
|
+
ProxyPass / http://127.0.0.1:__APP_PORT__/
|
|
13
|
+
ProxyPassReverse / http://127.0.0.1:__APP_PORT__/
|
|
14
|
+
|
|
15
|
+
# WSGI: __APP_WSGI__
|
|
16
|
+
</VirtualHost>
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# __APP_NAME__ gateway (nginx)
|
|
2
|
+
# Domain: __DOMAIN__
|
|
3
|
+
#
|
|
4
|
+
# NOTE:
|
|
5
|
+
# - Replace __DOMAIN__ with your real domain (or pass --domain).
|
|
6
|
+
# - This file is a template.
|
|
7
|
+
|
|
8
|
+
server {
|
|
9
|
+
listen 80;
|
|
10
|
+
server_name __DOMAIN__;
|
|
11
|
+
|
|
12
|
+
location / {
|
|
13
|
+
proxy_set_header Host $host;
|
|
14
|
+
proxy_set_header X-Real-IP $remote_addr;
|
|
15
|
+
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
|
16
|
+
proxy_set_header X-Forwarded-Proto $scheme;
|
|
17
|
+
|
|
18
|
+
# WSGI: __APP_WSGI__
|
|
19
|
+
proxy_pass http://127.0.0.1:__APP_PORT__;
|
|
20
|
+
}
|
|
21
|
+
}
|
nexom/buildTools/__init__.py
CHANGED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
from . import build
|