]>
git.gir.st - subscriptionfeed.git/blob - app/common/user.py
1 from werkzeug
.security
import generate_password_hash
, check_password_hash
6 from flask_login
import LoginManager
, UserMixin
, login_user
, logout_user
, login_required
, current_user
7 from flask
import Blueprint
, flash
, redirect
, render_template
, url_for
, request
9 class User(UserMixin
): # TODO: to common
10 def __init__(self
, id, name
, passwd
, token
, is_admin
):
18 def set_password(self
, passwd
):
19 self
.passwd
= generate_password_hash(passwd
)
20 with sqlite3
.connect(cf
['global']['database']) as conn
:
22 c
.execute("UPDATE users SET password = ? where id = ?", (self
.passwd
, self
.id,))
23 def check_password(self
, passwd
):
24 return check_password_hash(self
.passwd
, passwd
)
25 def get_settings(self
):
26 settings
= {} # fallback for guest user
27 if self
.is_authenticated
:
28 with sqlite3
.connect(cf
['global']['database']) as conn
:
36 setting
: json
.loads(value
)
37 for setting
, value
in c
.fetchall()
41 def from_id(self
, id):
42 with sqlite3
.connect(cf
['global']['database']) as conn
:
44 c
.execute("SELECT name,password,token,is_admin FROM users WHERE id = ?", (id,))
46 name
, passwd
, token
, admin
= c
.fetchone()
47 except: return None # todo: ugly
48 return User(id, name
, passwd
, token
, admin
)
50 def from_name(self
, name
):
51 with sqlite3
.connect(cf
['global']['database']) as conn
:
53 c
.execute("SELECT id,password,token,is_admin FROM users WHERE name=?", (name
,))
55 id, passwd
, token
, admin
= c
.fetchone()
56 except: return None # todo: ugly
57 return User(id, name
, passwd
, token
, admin
)
59 def from_token(self
, login_token
):
60 # Note: this function reads the revocable token, not the internal one!
61 with sqlite3
.connect(cf
['global']['database']) as conn
:
64 SELECT id, name, password, users.token, is_admin
65 FROM users JOIN user_tokens ON users.id = user_tokens.user_id
66 WHERE user_tokens.token = ?
69 id, name
, passwd
, token
, admin
= c
.fetchone()
70 return User(id, name
, passwd
, token
, admin
)
76 login
= LoginManager()
77 login
.login_view
= 'usermgmt.login_form'
82 # in the future tokens will be invalidable by users. -> https://flask-login.readthedocs.io/en/latest/#alternative-tokens
83 return User
.from_id(id)
86 def querytoken_auth(request
):
87 if request
.args
.get('token'):
88 user
= User
.from_token(request
.args
.get('token'))
94 usermgmt
= Blueprint('usermgmt', __name__
,
95 template_folder
='templates',
96 static_folder
='static',
97 static_url_path
='/static/usermgmt')
99 @usermgmt.route('/login')
101 return render_template('login.html.j2')
103 @usermgmt.route('/login', methods
=['POST'])
105 action
= request
.form
.get('action')
106 if action
== 'login':
107 user
= User
.from_name(request
.form
.get('user'))
108 if user
and user
.check_password(request
.form
.get('password')):
109 login_user(user
, remember
=request
.form
.get('remember'))
110 return redirect(request
.args
.get('next','/')) # xxx: non-exploitable open redirect!
111 flash('wrong username and/or password', 'error')
112 elif action
== 'register':
113 flash("open registration currently closed. ask <i>girst</i> on irc://irc.libera.chat/#invidious if you want an account.", 'info')
114 elif action
== 'logout':
116 return redirect(request
.args
.get('next','/')) # xxx: non-exploitable open redirect!
118 flash('unsupported action', 'error')
119 return redirect(url_for('usermgmt.login_form'))
121 @usermgmt.route('/manage/account')
123 def account_manager():
124 with sqlite3
.connect(cf
['global']['database']) as conn
:
127 SELECT setting, value
130 """, (current_user
.id,))
131 result
= c
.fetchall()
133 setting
: json
.loads(value
)
134 for setting
, value
in result
140 """, (current_user
.id,))
141 result
= c
.fetchone()
143 (login_token
,) = result
146 return render_template('account_mgmt.html.j2', settings
=settings
, login_token
=login_token
, random_pwd
=secrets
.token_hex(16))
148 @usermgmt.route('/manage/account', methods
=['POST'])
150 def manage_account():
151 token
= current_user
.token
152 action
= request
.form
.get('action')
153 if action
== 'chpwd':
154 if not current_user
.check_password(request
.form
.get('oldpasswd')):
155 flash('current password incorrect.', 'error')
157 current_user
.set_password(request
.form
.get('newpasswd'))
158 flash('password updated.', 'info')
159 elif action
== 'chtok':
160 with sqlite3
.connect(cf
['global']['database']) as conn
:
161 new_token
= secrets
.token_urlsafe(16)
164 INSERT OR REPLACE INTO user_tokens (user_id, token)
166 """, (current_user
.id, new_token
))
167 flash('new token generated.', 'info')
168 elif action
== 'chset':
169 with sqlite3
.connect(cf
['global']['database']) as conn
:
170 noshorts
= request
.form
.get('noshorts') == 'yes'
173 INSERT OR REPLACE INTO user_settings (user_id, setting, value)
175 """, (current_user
.id, "noshorts", json
.dumps(noshorts
)))
176 flash('settings saved.', 'info')
177 elif action
== 'addusr':
178 if not current_user
.admin
:
179 return "only admins may do that!", 403
180 with sqlite3
.connect(cf
['global']['database']) as conn
:
181 new_token
= secrets
.token_urlsafe(16)
182 username
= request
.form
.get('user')
183 password
= request
.form
.get('pass')
184 password
= generate_password_hash(password
)
185 is_admin
= request
.form
.get('admin') == 'yes'
189 INSERT INTO users (name, password, is_admin, token)
191 """, (username
, password
, is_admin
, new_token
));
192 flash('new user created.', 'info')
193 except sqlite3
.DatabaseError
as e
:
194 flash('error creating user: {e}', 'error')
196 flash('unsupported action', 'error')
198 return redirect(url_for('usermgmt.account_manager'))
200 # NOTE: only register blueprint _after_ adding routes!
201 app
.register_blueprint(usermgmt
)