Added user management to CLI. Mainly for creating admin user or resetting forgotten passwords.
This commit is contained in:
parent
7484795ce5
commit
c6caf96445
|
@ -4,7 +4,7 @@ from pathlib import Path
|
|||
|
||||
from docopt import docopt
|
||||
|
||||
from . import app, config, droppy, serve, httpredir
|
||||
from . import app, config, droppy, httpredir, pwgen, serve
|
||||
from ._version import version
|
||||
|
||||
app, httpredir.app # Needed for Sanic multiprocessing
|
||||
|
@ -13,6 +13,7 @@ doc = f"""Cista {version} - A file storage for the web.
|
|||
|
||||
Usage:
|
||||
cista [-c <confdir>] [-l <host>] [--import-droppy] [--dev] [<path>]
|
||||
cista [-c <confdir>] --user <name> [--privileged] [--password]
|
||||
|
||||
Options:
|
||||
-c CONFDIR Custom config directory
|
||||
|
@ -26,6 +27,11 @@ Options:
|
|||
|
||||
Listen address, path and imported options are preserved in config, and only
|
||||
custom config dir and dev mode need to be specified on subsequent runs.
|
||||
|
||||
User management:
|
||||
--user NAME Create or modify user
|
||||
--privileged Give the user full admin rights
|
||||
--password Reset password
|
||||
"""
|
||||
|
||||
def main():
|
||||
|
@ -41,6 +47,8 @@ def main():
|
|||
|
||||
def _main():
|
||||
args = docopt(doc)
|
||||
if args["--user"]:
|
||||
return _user(args)
|
||||
listen = args["-l"]
|
||||
# Validate arguments first
|
||||
if args["<path>"]:
|
||||
|
@ -49,15 +57,7 @@ def _main():
|
|||
raise ValueError(f"No such directory: {path}")
|
||||
else:
|
||||
path = None
|
||||
if args["-c"]:
|
||||
# Custom config directory
|
||||
confdir = Path(args["-c"]).resolve()
|
||||
if confdir.exists() and not confdir.is_dir():
|
||||
if confdir.name != config.conffile.name:
|
||||
raise ValueError("Config path is not a directory")
|
||||
# Accidentally pointed to the cista.toml, use parent
|
||||
confdir = confdir.parent
|
||||
config.conffile = config.conffile.with_parent(confdir)
|
||||
_confdir(args)
|
||||
exists = config.conffile.exists()
|
||||
import_droppy = args["--import-droppy"]
|
||||
necessary_opts = exists or import_droppy or path and listen
|
||||
|
@ -88,5 +88,40 @@ def _main():
|
|||
# Run the server
|
||||
serve.run(dev=dev)
|
||||
|
||||
def _confdir(args):
|
||||
if args["-c"]:
|
||||
# Custom config directory
|
||||
confdir = Path(args["-c"]).resolve()
|
||||
if confdir.exists() and not confdir.is_dir():
|
||||
if confdir.name != config.conffile.name:
|
||||
raise ValueError("Config path is not a directory")
|
||||
# Accidentally pointed to the cista.toml, use parent
|
||||
confdir = confdir.parent
|
||||
config.conffile = config.conffile.with_parent(confdir)
|
||||
|
||||
def _user(args):
|
||||
_confdir(args)
|
||||
config.load_config()
|
||||
name = args["--user"]
|
||||
if not name or not name.isidentifier():
|
||||
raise ValueError("Invalid username")
|
||||
config.load_config()
|
||||
u = config.config.users.get(name)
|
||||
info = f"User {name}" if u else f"New user {name}"
|
||||
changes = {}
|
||||
oldadmin = u and u.privileged
|
||||
if args["--privileged"]:
|
||||
changes["privileged"] = True
|
||||
info += " (already admin)" if oldadmin else " (made admin)"
|
||||
else:
|
||||
info += " (admin)" if oldadmin else ""
|
||||
if args["--password"] or not u:
|
||||
changes["password"] = pw = pwgen.generate()
|
||||
info += f"\n Password: {pw}"
|
||||
res = config.update_user(args["--user"], changes)
|
||||
print(info)
|
||||
if res == "read":
|
||||
print(" No changes")
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
|
|
|
@ -109,9 +109,34 @@ def load_config():
|
|||
config = msgspec.toml.decode(conffile.read_bytes(), type=Config, dec_hook=dec_hook)
|
||||
|
||||
@modifies_config
|
||||
def update_config(config: Config, changes: dict) -> Config:
|
||||
def update_config(conf: Config, changes: dict) -> Config:
|
||||
"""Create/update the config with new values, respecting changes done by others."""
|
||||
# Encode into dict, update values with new, convert to Config
|
||||
settings = {} if config is None else msgspec.to_builtins(config, enc_hook=enc_hook)
|
||||
settings = {} if conf is None else msgspec.to_builtins(conf, enc_hook=enc_hook)
|
||||
settings.update(changes)
|
||||
return msgspec.convert(settings, Config, dec_hook=dec_hook)
|
||||
|
||||
@modifies_config
|
||||
def update_user(conf: Config, name: str, changes: dict) -> Config:
|
||||
"""Create/update a user with new values, respecting changes done by others."""
|
||||
# Encode into dict, update values with new, convert to Config
|
||||
try:
|
||||
u = conf.users[name].__copy__()
|
||||
except KeyError:
|
||||
u = User()
|
||||
if "password" in changes:
|
||||
from . import auth
|
||||
auth.set_password(u, changes["password"])
|
||||
del changes["password"]
|
||||
udict = msgspec.to_builtins(u, enc_hook=enc_hook)
|
||||
udict.update(changes)
|
||||
settings = msgspec.to_builtins(conf, enc_hook=enc_hook)
|
||||
settings["users"][name] = msgspec.convert(udict, User, dec_hook=dec_hook)
|
||||
return msgspec.convert(settings, Config, dec_hook=dec_hook)
|
||||
|
||||
@modifies_config
|
||||
def del_user(conf: Config, name: str) -> Config:
|
||||
"""Delete named user account."""
|
||||
ret = conf.__copy__()
|
||||
ret.users.pop(name)
|
||||
return ret
|
||||
|
|
62
cista/pwgen.py
Normal file
62
cista/pwgen.py
Normal file
|
@ -0,0 +1,62 @@
|
|||
import secrets
|
||||
|
||||
|
||||
def generate(n=4):
|
||||
"""Generate a password of random words without repeating any word."""
|
||||
wl = list(words)
|
||||
return ".".join(wl.pop(secrets.randbelow(len(wl))) for i in range(n))
|
||||
|
||||
# A custom list of 1024 common 3-6 letter words, with unique 3-prefixes and no prefix words, entropy 2.1b/letter 10b/word
|
||||
words: list = """
|
||||
able about absent abuse access acid across act adapt add adjust admit adult advice affair afraid again age agree ahead
|
||||
aim air aisle alarm album alert alien all almost alone alpha also alter always amazed among amused anchor angle animal
|
||||
ankle annual answer any apart appear april arch are argue army around array art ascent ash ask aspect assume asthma atom
|
||||
attack audit august aunt author avoid away awful axis baby back bad bag ball bamboo bank bar base battle beach become
|
||||
beef before begin behind below bench best better beyond bid bike bind bio birth bitter black bleak blind blood blue
|
||||
board body boil bomb bone book border boss bottom bounce bowl box boy brain bread bring brown brush bubble buck budget
|
||||
build bulk bundle burden bus but buyer buzz cable cache cage cake call came can car case catch cause cave celery cement
|
||||
census cereal change check child choice chunk cigar circle city civil class clean client close club coast code coffee
|
||||
coil cold come cool copy core cost cotton couch cover coyote craft cream crime cross cruel cry cube cue cult cup curve
|
||||
custom cute cycle dad damage danger daring dash dawn day deal debate decide deer define degree deity delay demand denial
|
||||
depth derive design detail device dial dice die differ dim dinner direct dish divert dizzy doctor dog dollar domain
|
||||
donate door dose double dove draft dream drive drop drum dry duck dumb dune during dust dutch dwarf eager early east
|
||||
echo eco edge edit effort egg eight either elbow elder elite else embark emerge emily employ enable end enemy engine
|
||||
enjoy enlist enough enrich ensure entire envy equal era erode error erupt escape essay estate ethics evil evoke exact
|
||||
excess exist exotic expect extent eye fabric face fade faith fall family fan far father fault feel female fence fetch
|
||||
fever few fiber field figure file find first fish fit fix flat flesh flight float fluid fly foam focus fog foil follow
|
||||
food force fossil found fox frame fresh friend frog fruit fuel fun fury future gadget gain galaxy game gap garden gas
|
||||
gate gauge gaze genius ghost giant gift giggle ginger girl give glass glide globe glue goal god gold good gospel govern
|
||||
gown grant great grid group grunt guard guess guide gulf gun gym habit hair half hammer hand happy hard hat have hawk
|
||||
hay hazard head hedge height help hen hero hidden high hill hint hip hire hobby hockey hold home honey hood hope horse
|
||||
host hotel hour hover how hub huge human hungry hurt hybrid ice icon idea idle ignore ill image immune impact income
|
||||
index infant inhale inject inmate inner input inside into invest iron island issue italy item ivory jacket jaguar james
|
||||
jar jazz jeans jelly jewel job joe joke joy judge juice july jump june just kansas kate keep kernel key kick kid kind
|
||||
kiss kit kiwi knee knife know labor lady lag lake lamp laptop large later laugh lava law layer lazy leader left legal
|
||||
lemon length lesson letter level liar libya lid life light like limit line lion liquid list little live lizard load
|
||||
local logic long loop lost loud love low loyal lucky lumber lunch lust luxury lyrics mad magic main major make male
|
||||
mammal man map market mass matter maze mccoy meadow media meet melt member men mercy mesh method middle milk mimic mind
|
||||
mirror miss mix mobile model mom monkey moon more mother mouse move much muffin mule must mutual myself myth naive name
|
||||
napkin narrow nasty nation near neck need nephew nerve nest net never news next nice night noble noise noodle normal
|
||||
nose note novel now number nurse nut oak obey object oblige obtain occur ocean odor off often oil okay old olive omit
|
||||
once one onion online open opium oppose option orange orbit order organ orient orphan other outer oval oven own oxygen
|
||||
oyster ozone pact paddle page pair palace panel paper parade past path pause pave paw pay peace pen people pepper permit
|
||||
pet philip phone phrase piano pick piece pig pilot pink pipe pistol pitch pizza place please pluck poem point polar pond
|
||||
pool post pot pound powder praise prefer price profit public pull punch pupil purity push put puzzle qatar quasi queen
|
||||
quite quoted rabbit race radio rail rally ramp range rapid rare rather raven raw razor real rebel recall red reform
|
||||
region reject relief remain rent reopen report result return review reward rhythm rib rich ride rifle right ring riot
|
||||
ripple risk ritual river road robot rocket room rose rotate round row royal rubber rude rug rule run rural sad safe sage
|
||||
sail salad same santa sauce save say scale scene school scope screen scuba sea second seed self semi sense series settle
|
||||
seven shadow she ship shock shrimp shy sick side siege sign silver simple since siren sister six size skate sketch ski
|
||||
skull slab sleep slight slogan slush small smile smooth snake sniff snow soap soccer soda soft solid son soon sort south
|
||||
space speak sphere spirit split spoil spring spy square state step still story strong stuff style submit such sudden
|
||||
suffer sugar suit summer sun supply sure swamp sweet switch sword symbol syntax syria system table tackle tag tail talk
|
||||
tank tape target task tattoo taxi team tell ten term test text that theme this three thumb tibet ticket tide tight tilt
|
||||
time tiny tip tired tissue title toast today toe toilet token tomato tone tool top torch toss total toward toy trade
|
||||
tree trial trophy true try tube tumble tunnel turn twenty twice two type ugly unable uncle under unfair unique unlock
|
||||
until unveil update uphold upon upper upset urban urge usage use usual vacuum vague valid van vapor vast vault vein
|
||||
velvet vendor very vessel viable video view villa violin virus visit vital vivid vocal voice volume vote voyage wage
|
||||
wait wall want war wash water wave way wealth web weird were west wet what when whip wide wife will window wire wish
|
||||
wolf woman wonder wood work wrap wreck write wrong xander xbox xerox xray yang yard year yellow yes yin york you zane
|
||||
zara zebra zen zero zippo zone zoo zorro zulu
|
||||
""".split()
|
||||
assert len(words) == 1024 # Exactly 10 bits of entropy per word
|
Loading…
Reference in New Issue
Block a user