mirror of
https://github.com/9001/copyparty.git
synced 2025-08-18 01:22:13 -06:00
make passwords user-changeable; closes #92
This commit is contained in:
parent
5a62cb4869
commit
83fb569d61
24
README.md
24
README.md
|
@ -76,6 +76,7 @@ turn almost any device into a file server with resumable uploads/downloads using
|
||||||
* [upload events](#upload-events) - the older, more powerful approach ([examples](./bin/mtag/))
|
* [upload events](#upload-events) - the older, more powerful approach ([examples](./bin/mtag/))
|
||||||
* [handlers](#handlers) - redefine behavior with plugins ([examples](./bin/handlers/))
|
* [handlers](#handlers) - redefine behavior with plugins ([examples](./bin/handlers/))
|
||||||
* [identity providers](#identity-providers) - replace copyparty passwords with oauth and such
|
* [identity providers](#identity-providers) - replace copyparty passwords with oauth and such
|
||||||
|
* [user-changeable passwords](#user-changeable-passwords) - if permitted, users can change their own passwords
|
||||||
* [using the cloud as storage](#using-the-cloud-as-storage) - connecting to an aws s3 bucket and similar
|
* [using the cloud as storage](#using-the-cloud-as-storage) - connecting to an aws s3 bucket and similar
|
||||||
* [hiding from google](#hiding-from-google) - tell search engines you dont wanna be indexed
|
* [hiding from google](#hiding-from-google) - tell search engines you dont wanna be indexed
|
||||||
* [themes](#themes)
|
* [themes](#themes)
|
||||||
|
@ -1355,6 +1356,29 @@ there is a [docker-compose example](./docs/examples/docker/idp-authelia-traefik)
|
||||||
|
|
||||||
a more complete example of the copyparty configuration options [look like this](./docs/examples/docker/idp/copyparty.conf)
|
a more complete example of the copyparty configuration options [look like this](./docs/examples/docker/idp/copyparty.conf)
|
||||||
|
|
||||||
|
but if you just want to let users change their own passwords, then you probably want [user-changeable passwords](#user-changeable-passwords) instead
|
||||||
|
|
||||||
|
|
||||||
|
## user-changeable passwords
|
||||||
|
|
||||||
|
if permitted, users can change their own passwords in the control-panel
|
||||||
|
|
||||||
|
* not compatible with [identity providers](#identity-providers)
|
||||||
|
|
||||||
|
* must be enabled with `--chpw` because account-sharing is a popular usecase
|
||||||
|
|
||||||
|
* if you want to enable the feature but deny password-changing for a specific list of accounts, you can do that with `--chpw-no name1,name2,name3,...`
|
||||||
|
|
||||||
|
* to perform a password reset, edit the server config and give the user another password there, then do a [config reload](#server-config) or server restart
|
||||||
|
|
||||||
|
* the custom passwords are kept in a textfile at filesystem-path `--chpw-db`, by default `chpw.json` in the copyparty config folder
|
||||||
|
|
||||||
|
* if you run multiple copyparty instances with different users you *almost definitely* want to specify separate DBs for each instance
|
||||||
|
|
||||||
|
* if [password hashing](#password-hashing) is enbled, the passwords in the db are also hashed
|
||||||
|
|
||||||
|
* ...which means that all user-defined passwords will be forgotten if you change password-hashing settings
|
||||||
|
|
||||||
|
|
||||||
## using the cloud as storage
|
## using the cloud as storage
|
||||||
|
|
||||||
|
|
|
@ -1065,6 +1065,17 @@ def add_auth(ap):
|
||||||
ap2.add_argument("--bauth-last", action="store_true", help="keeps basic-authentication enabled, but only as a last-resort; if a cookie is also provided then the cookie wins")
|
ap2.add_argument("--bauth-last", action="store_true", help="keeps basic-authentication enabled, but only as a last-resort; if a cookie is also provided then the cookie wins")
|
||||||
|
|
||||||
|
|
||||||
|
def add_chpw(ap):
|
||||||
|
db_path = os.path.join(E.cfg, "chpw.json")
|
||||||
|
ap2 = ap.add_argument_group('user-changeable passwords options')
|
||||||
|
ap2.add_argument("--chpw", action="store_true", help="allow users to change their own passwords")
|
||||||
|
ap2.add_argument("--chpw-no", metavar="U,U,U", type=u, action="append", help="do not allow password-changes for this comma-separated list of usernames")
|
||||||
|
ap2.add_argument("--chpw-db", metavar="PATH", type=u, default=db_path, help="where to store the passwords database (if you run multiple copyparty instances, make sure they use different DBs)")
|
||||||
|
ap2.add_argument("--chpw-len", metavar="N", type=int, default=8, help="minimum password length")
|
||||||
|
ap2.add_argument("--chpw-v", action="store_true", help="verbose (when loading: list status of each user)")
|
||||||
|
ap2.add_argument("--chpw-q", action="store_true", help="quiet (when loading: don't print summary)")
|
||||||
|
|
||||||
|
|
||||||
def add_zeroconf(ap):
|
def add_zeroconf(ap):
|
||||||
ap2 = ap.add_argument_group("Zeroconf options")
|
ap2 = ap.add_argument_group("Zeroconf options")
|
||||||
ap2.add_argument("-z", action="store_true", help="enable all zeroconf backends (mdns, ssdp)")
|
ap2.add_argument("-z", action="store_true", help="enable all zeroconf backends (mdns, ssdp)")
|
||||||
|
@ -1473,6 +1484,7 @@ def run_argparse(
|
||||||
add_tls(ap, cert_path)
|
add_tls(ap, cert_path)
|
||||||
add_cert(ap, cert_path)
|
add_cert(ap, cert_path)
|
||||||
add_auth(ap)
|
add_auth(ap)
|
||||||
|
add_chpw(ap)
|
||||||
add_qr(ap, tty)
|
add_qr(ap, tty)
|
||||||
add_zeroconf(ap)
|
add_zeroconf(ap)
|
||||||
add_zc_mdns(ap)
|
add_zc_mdns(ap)
|
||||||
|
|
|
@ -4,6 +4,7 @@ from __future__ import print_function, unicode_literals
|
||||||
import argparse
|
import argparse
|
||||||
import base64
|
import base64
|
||||||
import hashlib
|
import hashlib
|
||||||
|
import json
|
||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
import stat
|
import stat
|
||||||
|
@ -807,6 +808,7 @@ class AuthSrv(object):
|
||||||
self.vfs = VFS(log_func, "", "", AXS(), {})
|
self.vfs = VFS(log_func, "", "", AXS(), {})
|
||||||
self.acct: dict[str, str] = {}
|
self.acct: dict[str, str] = {}
|
||||||
self.iacct: dict[str, str] = {}
|
self.iacct: dict[str, str] = {}
|
||||||
|
self.defpw: dict[str, str] = {}
|
||||||
self.grps: dict[str, list[str]] = {}
|
self.grps: dict[str, list[str]] = {}
|
||||||
self.re_pwd: Optional[re.Pattern] = None
|
self.re_pwd: Optional[re.Pattern] = None
|
||||||
|
|
||||||
|
@ -1440,6 +1442,8 @@ class AuthSrv(object):
|
||||||
raise
|
raise
|
||||||
|
|
||||||
self.setup_pwhash(acct)
|
self.setup_pwhash(acct)
|
||||||
|
defpw = acct.copy()
|
||||||
|
self.setup_chpw(acct)
|
||||||
|
|
||||||
# case-insensitive; normalize
|
# case-insensitive; normalize
|
||||||
if WINDOWS:
|
if WINDOWS:
|
||||||
|
@ -2069,6 +2073,7 @@ class AuthSrv(object):
|
||||||
|
|
||||||
self.vfs = vfs
|
self.vfs = vfs
|
||||||
self.acct = acct
|
self.acct = acct
|
||||||
|
self.defpw = defpw
|
||||||
self.grps = grps
|
self.grps = grps
|
||||||
self.iacct = {v: k for k, v in acct.items()}
|
self.iacct = {v: k for k, v in acct.items()}
|
||||||
|
|
||||||
|
@ -2089,6 +2094,96 @@ class AuthSrv(object):
|
||||||
MIMES[ext] = mime
|
MIMES[ext] = mime
|
||||||
EXTS.update({v: k for k, v in MIMES.items()})
|
EXTS.update({v: k for k, v in MIMES.items()})
|
||||||
|
|
||||||
|
def chpw(self, broker: Optional["BrokerCli"], uname, pw) -> tuple[bool, str]:
|
||||||
|
if not self.args.chpw:
|
||||||
|
return False, "feature disabled in server config"
|
||||||
|
|
||||||
|
if uname == "*" or uname not in self.defpw:
|
||||||
|
return False, "not logged in"
|
||||||
|
|
||||||
|
if len(pw) < self.args.chpw_len:
|
||||||
|
t = "minimum password length: %d characters"
|
||||||
|
return False, t % (self.args.chpw_len,)
|
||||||
|
|
||||||
|
hpw = self.ah.hash(pw) if self.ah.on else pw
|
||||||
|
if hpw in self.iacct:
|
||||||
|
return False, "password is taken"
|
||||||
|
|
||||||
|
with self.mutex:
|
||||||
|
ap = self.args.chpw_db
|
||||||
|
if not bos.path.exists(ap):
|
||||||
|
pwdb = {}
|
||||||
|
else:
|
||||||
|
with open(ap, "r", encoding="utf-8") as f:
|
||||||
|
pwdb = json.load(f)
|
||||||
|
|
||||||
|
pwdb = [x for x in pwdb if x[0] != uname]
|
||||||
|
pwdb.append((uname, self.defpw[uname], hpw))
|
||||||
|
|
||||||
|
with open(ap, "w", encoding="utf-8") as f:
|
||||||
|
json.dump(pwdb, f, separators=(",\n", ": "))
|
||||||
|
|
||||||
|
self.log("reinitializing due to password-change for user [%s]" % (uname,))
|
||||||
|
|
||||||
|
if not broker:
|
||||||
|
# only true for tests
|
||||||
|
self._reload()
|
||||||
|
return True, "new password OK"
|
||||||
|
|
||||||
|
broker.ask("_reload_blocking", False, False).get()
|
||||||
|
return True, "new password OK"
|
||||||
|
|
||||||
|
def setup_chpw(self, acct: dict[str, str]) -> None:
|
||||||
|
ap = self.args.chpw_db
|
||||||
|
if not self.args.chpw or not bos.path.exists(ap):
|
||||||
|
return
|
||||||
|
|
||||||
|
with open(ap, "r", encoding="utf-8") as f:
|
||||||
|
pwdb = json.load(f)
|
||||||
|
|
||||||
|
u404 = set()
|
||||||
|
urst = set()
|
||||||
|
uok = set()
|
||||||
|
for usr, orig, mod in pwdb:
|
||||||
|
if usr not in acct:
|
||||||
|
u404.add(usr)
|
||||||
|
continue
|
||||||
|
if acct[usr] != orig:
|
||||||
|
urst.add(usr)
|
||||||
|
continue
|
||||||
|
uok.add(usr)
|
||||||
|
acct[usr] = mod
|
||||||
|
|
||||||
|
if self.args.chpw_q:
|
||||||
|
return
|
||||||
|
|
||||||
|
for zs in uok:
|
||||||
|
urst.discard(zs)
|
||||||
|
|
||||||
|
if not self.args.chpw_v:
|
||||||
|
t = "chpw: %d loaded, %d default, %d ignored"
|
||||||
|
self.log(t % (len(uok), len(urst), len(u404)))
|
||||||
|
return
|
||||||
|
|
||||||
|
msg = ""
|
||||||
|
if uok:
|
||||||
|
t = "\033[0mloaded: \033[32m%s"
|
||||||
|
msg += t % (", ".join(list(uok)),)
|
||||||
|
if urst:
|
||||||
|
t = "%s\033[0mdefault: \033[35m%s"
|
||||||
|
msg += t % (
|
||||||
|
", " if msg else "",
|
||||||
|
", ".join(list(urst)),
|
||||||
|
)
|
||||||
|
if u404:
|
||||||
|
t = "%s\033[0mignored: \033[35m%s"
|
||||||
|
msg += t % (
|
||||||
|
", " if msg else "",
|
||||||
|
", ".join(list(u404)),
|
||||||
|
)
|
||||||
|
|
||||||
|
self.log("chpw: " + msg, 6)
|
||||||
|
|
||||||
def setup_pwhash(self, acct: dict[str, str]) -> None:
|
def setup_pwhash(self, acct: dict[str, str]) -> None:
|
||||||
self.ah = PWHash(self.args)
|
self.ah = PWHash(self.args)
|
||||||
if not self.ah.on:
|
if not self.ah.on:
|
||||||
|
|
|
@ -2089,6 +2089,9 @@ class HttpCli(object):
|
||||||
if act == "zip":
|
if act == "zip":
|
||||||
return self.handle_zip_post()
|
return self.handle_zip_post()
|
||||||
|
|
||||||
|
if act == "chpw":
|
||||||
|
return self.handle_chpw()
|
||||||
|
|
||||||
raise Pebkac(422, 'invalid action "{}"'.format(act))
|
raise Pebkac(422, 'invalid action "{}"'.format(act))
|
||||||
|
|
||||||
def handle_zip_post(self) -> bool:
|
def handle_zip_post(self) -> bool:
|
||||||
|
@ -2393,6 +2396,22 @@ class HttpCli(object):
|
||||||
self.reply(b"thank")
|
self.reply(b"thank")
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
def handle_chpw(self) -> bool:
|
||||||
|
assert self.parser
|
||||||
|
pwd = self.parser.require("pw", 64)
|
||||||
|
self.parser.drop()
|
||||||
|
|
||||||
|
ok, msg = self.asrv.chpw(self.conn.hsrv.broker, self.uname, pwd)
|
||||||
|
if ok:
|
||||||
|
ok, msg = self.get_pwd_cookie(pwd)
|
||||||
|
if ok:
|
||||||
|
msg = "new password OK"
|
||||||
|
|
||||||
|
redir = "/?h" if ok else ""
|
||||||
|
html = self.j2s("msg", h1=msg, h2='<a href="/?h">ack</a>', redir=redir)
|
||||||
|
self.reply(html.encode("utf-8"))
|
||||||
|
return True
|
||||||
|
|
||||||
def handle_login(self) -> bool:
|
def handle_login(self) -> bool:
|
||||||
assert self.parser
|
assert self.parser
|
||||||
pwd = self.parser.require("cppwd", 64)
|
pwd = self.parser.require("cppwd", 64)
|
||||||
|
@ -2417,12 +2436,12 @@ class HttpCli(object):
|
||||||
dst += "&" if "?" in dst else "?"
|
dst += "&" if "?" in dst else "?"
|
||||||
dst += "_=1#" + html_escape(uhash, True, True)
|
dst += "_=1#" + html_escape(uhash, True, True)
|
||||||
|
|
||||||
msg = self.get_pwd_cookie(pwd)
|
_, msg = self.get_pwd_cookie(pwd)
|
||||||
html = self.j2s("msg", h1=msg, h2='<a href="' + dst + '">ack</a>', redir=dst)
|
html = self.j2s("msg", h1=msg, h2='<a href="' + dst + '">ack</a>', redir=dst)
|
||||||
self.reply(html.encode("utf-8"))
|
self.reply(html.encode("utf-8"))
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def get_pwd_cookie(self, pwd: str) -> str:
|
def get_pwd_cookie(self, pwd: str) -> tuple[bool, str]:
|
||||||
hpwd = self.asrv.ah.hash(pwd)
|
hpwd = self.asrv.ah.hash(pwd)
|
||||||
uname = self.asrv.iacct.get(hpwd)
|
uname = self.asrv.iacct.get(hpwd)
|
||||||
if uname:
|
if uname:
|
||||||
|
@ -2454,7 +2473,7 @@ class HttpCli(object):
|
||||||
ck = gencookie(k, pwd, self.args.R, self.is_https, dur, "; HttpOnly")
|
ck = gencookie(k, pwd, self.args.R, self.is_https, dur, "; HttpOnly")
|
||||||
self.out_headerlist.append(("Set-Cookie", ck))
|
self.out_headerlist.append(("Set-Cookie", ck))
|
||||||
|
|
||||||
return msg
|
return dur > 0, msg
|
||||||
|
|
||||||
def handle_mkdir(self) -> bool:
|
def handle_mkdir(self) -> bool:
|
||||||
assert self.parser
|
assert self.parser
|
||||||
|
@ -3948,6 +3967,7 @@ class HttpCli(object):
|
||||||
k304=self.k304(),
|
k304=self.k304(),
|
||||||
k304vis=self.args.k304 > 0,
|
k304vis=self.args.k304 > 0,
|
||||||
ver=S_VERSION if self.args.ver else "",
|
ver=S_VERSION if self.args.ver else "",
|
||||||
|
chpw=self.args.chpw and self.uname != "*",
|
||||||
ahttps="" if self.is_https else "https://" + self.host + self.req,
|
ahttps="" if self.is_https else "https://" + self.host + self.req,
|
||||||
)
|
)
|
||||||
self.reply(html.encode("utf-8"))
|
self.reply(html.encode("utf-8"))
|
||||||
|
|
|
@ -208,6 +208,11 @@ class SvcHub(object):
|
||||||
t = "WARNING: --s-rd-sz (%d) is larger than --iobuf (%d); this may lead to reduced performance"
|
t = "WARNING: --s-rd-sz (%d) is larger than --iobuf (%d); this may lead to reduced performance"
|
||||||
self.log("root", t % (args.s_rd_sz, args.iobuf), 3)
|
self.log("root", t % (args.s_rd_sz, args.iobuf), 3)
|
||||||
|
|
||||||
|
if args.chpw and args.idp_h_usr:
|
||||||
|
t = "ERROR: user-changeable passwords is incompatible with IdP/identity-providers; you must disable either --chpw or --idp-h-usr"
|
||||||
|
self.log("root", t, 1)
|
||||||
|
raise Exception(t)
|
||||||
|
|
||||||
bri = "zy"[args.theme % 2 :][:1]
|
bri = "zy"[args.theme % 2 :][:1]
|
||||||
ch = "abcdefghijklmnopqrstuvwx"[int(args.theme / 2)]
|
ch = "abcdefghijklmnopqrstuvwx"[int(args.theme / 2)]
|
||||||
args.theme = "{0}{1} {0} {1}".format(ch, bri)
|
args.theme = "{0}{1} {0} {1}".format(ch, bri)
|
||||||
|
@ -815,18 +820,21 @@ class SvcHub(object):
|
||||||
Daemon(self._reload, "reloading")
|
Daemon(self._reload, "reloading")
|
||||||
return "reload initiated"
|
return "reload initiated"
|
||||||
|
|
||||||
def _reload(self, rescan_all_vols: bool = True) -> None:
|
def _reload(self, rescan_all_vols: bool = True, up2k: bool = True) -> None:
|
||||||
with self.up2k.mutex:
|
with self.up2k.mutex:
|
||||||
if self.reloading != 1:
|
if self.reloading != 1:
|
||||||
return
|
return
|
||||||
self.reloading = 2
|
self.reloading = 2
|
||||||
self.log("root", "reloading config")
|
self.log("root", "reloading config")
|
||||||
self.asrv.reload()
|
self.asrv.reload()
|
||||||
|
if up2k:
|
||||||
self.up2k.reload(rescan_all_vols)
|
self.up2k.reload(rescan_all_vols)
|
||||||
|
else:
|
||||||
|
self.log("root", "reload done")
|
||||||
self.broker.reload()
|
self.broker.reload()
|
||||||
self.reloading = 0
|
self.reloading = 0
|
||||||
|
|
||||||
def _reload_blocking(self, rescan_all_vols: bool = True) -> None:
|
def _reload_blocking(self, rescan_all_vols: bool = True, up2k: bool = True) -> None:
|
||||||
while True:
|
while True:
|
||||||
with self.up2k.mutex:
|
with self.up2k.mutex:
|
||||||
if self.reloading < 2:
|
if self.reloading < 2:
|
||||||
|
@ -837,7 +845,7 @@ class SvcHub(object):
|
||||||
# try to handle multiple pending IdP reloads at once:
|
# try to handle multiple pending IdP reloads at once:
|
||||||
time.sleep(0.2)
|
time.sleep(0.2)
|
||||||
|
|
||||||
self._reload(rescan_all_vols=rescan_all_vols)
|
self._reload(rescan_all_vols=rescan_all_vols, up2k=up2k)
|
||||||
|
|
||||||
def stop_thr(self) -> None:
|
def stop_thr(self) -> None:
|
||||||
while not self.stop_req:
|
while not self.stop_req:
|
||||||
|
|
|
@ -182,13 +182,15 @@ html.z a.g {
|
||||||
border-color: #af4;
|
border-color: #af4;
|
||||||
box-shadow: 0 .3em 1em #7d0;
|
box-shadow: 0 .3em 1em #7d0;
|
||||||
}
|
}
|
||||||
|
#x,
|
||||||
input {
|
input {
|
||||||
color: #a50;
|
color: #a50;
|
||||||
background: #fff;
|
background: #fff;
|
||||||
border: 1px solid #a50;
|
border: 1px solid #a50;
|
||||||
border-radius: .5em;
|
border-radius: .3em;
|
||||||
padding: .5em .7em;
|
padding: .3em .6em;
|
||||||
margin: 0 .5em 0 0;
|
margin: 0 .3em 0 0;
|
||||||
|
font-size: 1em;
|
||||||
}
|
}
|
||||||
input::placeholder {
|
input::placeholder {
|
||||||
font-size: 1.2em;
|
font-size: 1.2em;
|
||||||
|
@ -197,6 +199,7 @@ input::placeholder {
|
||||||
opacity: 0.64;
|
opacity: 0.64;
|
||||||
color: #930;
|
color: #930;
|
||||||
}
|
}
|
||||||
|
#x,
|
||||||
html.z input {
|
html.z input {
|
||||||
color: #fff;
|
color: #fff;
|
||||||
background: #626;
|
background: #626;
|
||||||
|
|
|
@ -92,11 +92,14 @@
|
||||||
|
|
||||||
<h1 id="l">login for more:</h1>
|
<h1 id="l">login for more:</h1>
|
||||||
<div>
|
<div>
|
||||||
<form method="post" enctype="multipart/form-data" action="{{ r }}/{{ qvpath }}">
|
<form id="lf" method="post" enctype="multipart/form-data" action="{{ r }}/{{ qvpath }}">
|
||||||
<input type="hidden" name="act" value="login" />
|
<input type="hidden" id="la" name="act" value="login" />
|
||||||
<input type="password" name="cppwd" placeholder=" password" />
|
<input type="password" id="lp" name="cppwd" placeholder=" password" />
|
||||||
<input type="hidden" name="uhash" id="uhash" value="x" />
|
<input type="hidden" name="uhash" id="uhash" value="x" />
|
||||||
<input type="submit" value="Login" />
|
<input type="submit" id="ls" value="Login" />
|
||||||
|
{% if chpw %}
|
||||||
|
<a id="x" href="#">change password</a>
|
||||||
|
{% endif %}
|
||||||
{% if ahttps %}
|
{% if ahttps %}
|
||||||
<a id="w" href="{{ ahttps }}">switch to https</a>
|
<a id="w" href="{{ ahttps }}">switch to https</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
|
@ -27,12 +27,19 @@ var Ls = {
|
||||||
"v1": "koble til",
|
"v1": "koble til",
|
||||||
"v2": "bruk denne serveren som en lokal harddisk$N$NADVARSEL: kommer til å vise passordet ditt!",
|
"v2": "bruk denne serveren som en lokal harddisk$N$NADVARSEL: kommer til å vise passordet ditt!",
|
||||||
"w1": "bytt til https",
|
"w1": "bytt til https",
|
||||||
|
"x1": "bytt passord",
|
||||||
|
"ta1": "du må skrive et nytt passord først",
|
||||||
|
"ta2": "gjenta for å bekrefte nytt passord:",
|
||||||
|
"ta3": "fant en skrivefeil; vennligst prøv igjen",
|
||||||
},
|
},
|
||||||
"eng": {
|
"eng": {
|
||||||
"d2": "shows the state of all active threads",
|
"d2": "shows the state of all active threads",
|
||||||
"e2": "reload config files (accounts/volumes/volflags),$Nand rescan all e2ds volumes$N$Nnote: any changes to global settings$Nrequire a full restart to take effect",
|
"e2": "reload config files (accounts/volumes/volflags),$Nand rescan all e2ds volumes$N$Nnote: any changes to global settings$Nrequire a full restart to take effect",
|
||||||
"u2": "time since the last server write$N( upload / rename / ... )$N$N17d = 17 days$N1h23 = 1 hour 23 minutes$N4m56 = 4 minutes 56 seconds",
|
"u2": "time since the last server write$N( upload / rename / ... )$N$N17d = 17 days$N1h23 = 1 hour 23 minutes$N4m56 = 4 minutes 56 seconds",
|
||||||
"v2": "use this server as a local HDD$N$NWARNING: this will show your password!",
|
"v2": "use this server as a local HDD$N$NWARNING: this will show your password!",
|
||||||
|
"ta1": "fill in your new password first",
|
||||||
|
"ta2": "repeat to confirm new password:",
|
||||||
|
"ta3": "found a typo; please try again",
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -74,3 +81,42 @@ if (o && /[0-9]+$/.exec(o.innerHTML))
|
||||||
o.innerHTML = shumantime(o.innerHTML);
|
o.innerHTML = shumantime(o.innerHTML);
|
||||||
|
|
||||||
ebi('uhash').value = '' + location.hash;
|
ebi('uhash').value = '' + location.hash;
|
||||||
|
|
||||||
|
(function() {
|
||||||
|
if (!ebi('x'))
|
||||||
|
return;
|
||||||
|
|
||||||
|
var pwi = ebi('lp');
|
||||||
|
|
||||||
|
function redo(msg) {
|
||||||
|
modal.alert(msg, function() {
|
||||||
|
pwi.value = '';
|
||||||
|
pwi.focus();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
function mok(v) {
|
||||||
|
if (v !== pwi.value)
|
||||||
|
return redo(d.ta3);
|
||||||
|
|
||||||
|
pwi.setAttribute('name', 'pw');
|
||||||
|
ebi('la').value = 'chpw';
|
||||||
|
ebi('lf').submit();
|
||||||
|
}
|
||||||
|
function stars() {
|
||||||
|
var m = ebi('modali');
|
||||||
|
function enstars(n) {
|
||||||
|
setTimeout(function() { m.value = ''; }, n);
|
||||||
|
}
|
||||||
|
m.setAttribute('type', 'password');
|
||||||
|
enstars(17);
|
||||||
|
enstars(32);
|
||||||
|
enstars(69);
|
||||||
|
}
|
||||||
|
ebi('x').onclick = function (e) {
|
||||||
|
ev(e);
|
||||||
|
if (!pwi.value)
|
||||||
|
return redo(d.ta1);
|
||||||
|
|
||||||
|
modal.prompt(d.ta2, "y", mok, null, stars);
|
||||||
|
};
|
||||||
|
})();
|
||||||
|
|
|
@ -119,7 +119,7 @@ class Cfg(Namespace):
|
||||||
def __init__(self, a=None, v=None, c=None, **ka0):
|
def __init__(self, a=None, v=None, c=None, **ka0):
|
||||||
ka = {}
|
ka = {}
|
||||||
|
|
||||||
ex = "daw dav_auth dav_inf dav_mac dav_rt e2d e2ds e2dsa e2t e2ts e2tsr e2v e2vu e2vp early_ban ed emp exp force_js getmod grid gsel hardlink ih ihead magic never_symlink nid nih no_acode no_athumb no_dav no_dedup no_del no_dupe no_lifetime no_logues no_mv no_pipe no_poll no_readme no_robots no_sb_md no_sb_lg no_scandir no_tarcmp no_thumb no_vthumb no_zip nrand nw og og_no_head og_s_title q rand smb srch_dbg stats uqe vague_403 vc ver write_uplog xdev xlink xvol"
|
ex = "chpw daw dav_auth dav_inf dav_mac dav_rt e2d e2ds e2dsa e2t e2ts e2tsr e2v e2vu e2vp early_ban ed emp exp force_js getmod grid gsel hardlink ih ihead magic never_symlink nid nih no_acode no_athumb no_dav no_dedup no_del no_dupe no_lifetime no_logues no_mv no_pipe no_poll no_readme no_robots no_sb_md no_sb_lg no_scandir no_tarcmp no_thumb no_vthumb no_zip nrand nw og og_no_head og_s_title q rand smb srch_dbg stats uqe vague_403 vc ver write_uplog xdev xlink xvol"
|
||||||
ka.update(**{k: False for k in ex.split()})
|
ka.update(**{k: False for k in ex.split()})
|
||||||
|
|
||||||
ex = "dotpart dotsrch hook_v no_dhash no_fastboot no_rescan no_sendfile no_snap no_voldump re_dhash plain_ip"
|
ex = "dotpart dotsrch hook_v no_dhash no_fastboot no_rescan no_sendfile no_snap no_voldump re_dhash plain_ip"
|
||||||
|
@ -137,7 +137,7 @@ class Cfg(Namespace):
|
||||||
ex = "db_act k304 loris re_maxage rproxy rsp_jtr rsp_slp s_wr_slp snap_wri theme themes turbo"
|
ex = "db_act k304 loris re_maxage rproxy rsp_jtr rsp_slp s_wr_slp snap_wri theme themes turbo"
|
||||||
ka.update(**{k: 0 for k in ex.split()})
|
ka.update(**{k: 0 for k in ex.split()})
|
||||||
|
|
||||||
ex = "ah_alg bname doctitle df exit favico idp_h_usr html_head lg_sbf log_fk md_sbf name og_desc og_site og_th og_title og_title_a og_title_v og_title_i tcolor textfiles unlist vname R RS SR"
|
ex = "ah_alg bname chpw_db doctitle df exit favico idp_h_usr html_head lg_sbf log_fk md_sbf name og_desc og_site og_th og_title og_title_a og_title_v og_title_i tcolor textfiles unlist vname R RS SR"
|
||||||
ka.update(**{k: "" for k in ex.split()})
|
ka.update(**{k: "" for k in ex.split()})
|
||||||
|
|
||||||
ex = "grp on403 on404 xad xar xau xban xbd xbr xbu xiu xm"
|
ex = "grp on403 on404 xad xar xau xban xbd xbr xbu xiu xm"
|
||||||
|
|
Loading…
Reference in a new issue