Implementing a "forgot password" mechanism (paradigm)

222 views
Skip to first unread message

Manos Pappas

unread,
Jul 27, 2021, 3:55:39 AM7/27/21
to Jam.py Users Mailing List
Hello,

I am writing a Jam application using authentication from a custom table (as described in the HOWTO article).

Is there any example on how to implement a "forget password" button and mechanism on resetting the user password?

Many thanks in advance.

Drazen D. Babic

unread,
Jul 27, 2021, 4:13:40 AM7/27/21
to Jam.py Users Mailing List
Hi,
An Admin should be able to reset password for the user, as per howto example.

However, you should really look into having different Auth method, since password expiry etc is reinventing wheels, so something like LDAP for v5 or SAML for v6. Than the password reset is enforced via this methods. As it should be.
Even this, which actually works:

But sure, for small App not worth doing it.

D.

Manos Pappas

unread,
Jul 28, 2021, 1:40:16 AM7/28/21
to Jam.py Users Mailing List
Hi,

Thank you Drazen for you reply.

I was not clear enough: what I meant is that I can already reset the user password (via a Python server function), I was just asking on how to implement on HTML template the "Forget Password" which after the user clicks, forwards her to another page asking for the e-maill address. That's the part I do not understand much, how to "glue" these pieces together.

TIA.

Drazen D. Babic

unread,
Jul 28, 2021, 2:20:18 AM7/28/21
to Jam.py Users Mailing List
Hello, 
I know what the request is :) Being there, done that.

But, here is a thing: Jam v5 is a SPA (Single Page Application). And that is Jam paradigm. 
So only in v6 and onwards, Jam will be able to route the pages, meaning no SPA.

There is a register.html example, however that is an "offline" page, it is not routed by the Jam App. Which basically means that it is possible to have a page to reset the password, but that also means any password can be reset if the username is known ;) Providing the on_ext_request procedure does the password reset in such manner. 
So if this approach is fine, than study the register example:

and accommodate for password reset. In earlier v5 version, regist.html was included in Demo.

What you described is a different logic to reset the App passwords, which probably depends on temporary link, etc. Complex topic.

Cheers
D.

Andrew Yushev

unread,
Jul 28, 2021, 7:20:44 AM7/28/21
to Drazen D. Babic, Jam.py Users Mailing List
Hi,

Manos, I'll think about how to implement it.

 Regards,
Andrew Yushev

ср, 28 июл. 2021 г. в 09:20, Drazen D. Babic <bab...@gmail.com>:
--
You received this message because you are subscribed to the Google Groups "Jam.py Users Mailing List" group.
To unsubscribe from this group and stop receiving emails from it, send an email to jam-py+un...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/jam-py/19b4f3a9-c8df-4f4a-a2b0-5c490ac1d6a8n%40googlegroups.com.

Manos Pappas

unread,
Jul 29, 2021, 4:43:50 AM7/29/21
to Jam.py Users Mailing List
Hi Andrew,

Thank you very much for your help.
The "reset password" Python script all that it does is to create a random temporary password that is sent to the requested e-mail IF the e-mail is present on the database (otherwise, nothing is done).
The user uses this password to log-on to the system and then change the password using the "register.html" type request.

I am using Jam v5.

Andrew Yushev

unread,
Jul 29, 2021, 11:19:39 AM7/29/21
to Manos Pappas, Jam.py Users Mailing List
Yes, I see.
I'll try to do it,

чт, 29 июл. 2021 г. в 11:43, Manos Pappas <front...@gmail.com>:

Drazen D. Babic

unread,
Jul 29, 2021, 8:26:38 PM7/29/21
to Jam.py Users Mailing List
Hi Manos, 
by the sound of it do you already have this working? 

Manos Pappas

unread,
Jul 30, 2021, 2:11:42 AM7/30/21
to Jam.py Users Mailing List
Hi Drazen,

Nothing fancy, just plain Python code using smtp library to send an e-mail containing a randomly generated password string.
I plan to post it here when I have everything working, as a possible example solution.

Andrew Yushev

unread,
Aug 2, 2021, 2:14:39 PM8/2/21
to Manos Pappas, Jam.py Users Mailing List
Hi,

Manos, I added the mail input in the form, please see the video.
If it suits you you should write the on_login event handler in the server module of the task when
the email is passed.

The  link to the project is below:

Regards,
Andrew Yushev


пт, 30 июл. 2021 г. в 09:11, Manos Pappas <front...@gmail.com>:
--
You received this message because you are subscribed to the Google Groups "Jam.py Users Mailing List" group.
To unsubscribe from this group and stop receiving emails from it, send an email to jam-py+un...@googlegroups.com.
auth.mp4

Manos Pappas

unread,
Sep 6, 2021, 8:07:14 AM9/6/21
to Jam.py Users Mailing List
Hello,

Thanks to Andrew, I've managed to complete this task.
As promised, here is the Python server code to implement on the above project the 'forgot password' function.
The new password is generated ONLY if the e-mail provided is matched against the one that the user has registered in the database when the account was created.
If this is true, the application generates a new password, sends it to the user via e-mail and is also stored on the database.

The following Python code should be entered on the server portion of the application task tree

import secrets # for random password generator
import string # for random password string generator
import smtplib  # for sending e-mail
from email.mime.multipart import MIMEMultipart # for composing MIME e-mail
from email.mime.text import MIMEText # for composing MIME text

def on_login(task, form_data, info):
    # user checked the "forgot password" checkbox
    if form_data['email']:
        # create temporary password for user and send it to the specified e-mail
        # ONLY IF the e-mail is found in the database for the specified user
        # try to keep quiet so that bots can crash themselves
        users = task.users.copy(handlers=False)
        users.set_where(login=form_data['login'],email=form_data['email'])
        users.open()
        if users.rec_count == 1:
             # user login and e-mail found, generate secure password
            rndpasswd = ''.join((secrets.choice(string.ascii_letters) for i in range(10)))
            # send the new password to the user via e-mail
            resetpassemail(form_data['email'], rndpasswd)
            # change the user password on the database
            users.edit()
            users.password_hash.value = task.generate_password_hash(rndpasswd)
            users.post()
            users.apply()
    else:
        # normal login
        users = task.users.copy(handlers=False)
        users.set_where(login=form_data['login'])
        users.open()
        if users.rec_count == 1:
            if users.isactive.value == 1:
                if task.check_password_hash(users.password_hash.value, form_data['password']) or users.password_reset.value == 65535:
                    return {
                        'user_id': users.id.value,
                        'user_name': users.name.value,
                        'role_id': users.role.value,
                        'role_name': users.role.display_text,
                   }

def resetpassemail(useremail,password):
    sender_pass = 'your SMTP password'    # password for SMTP server
    sender_smtp = "your SMTP server name"          # SMTP server
    sender_port = 587                       # SMTP port (here is SSL/TLS)
    sender_user = "your e-mail user"     # STMP sender account
    
    # Create message container - the correct MIME type is multipart/alternative.
    msg = MIMEMultipart('alternative')
    msg['Subject'] = "Password Reset"
    msg['From'] = sender_user
    msg['To'] = useremail
    
    # e-mail text (plain)
    mail_text = """Dear user,
    
    Your new login password is
    %s
    """ % password
    
    # e-mail text (html)
    mail_html = """\
    <html>
        <head></head>
        <body>
            <p>Dear user,<br>
                Your new login password is<br>
                <b><h1>%s</h1></b><br
            </p>
        </body>
    </html>""" % password
    
    # Record the MIME types of both parts - text/plain and text/html.
    part1 = MIMEText(mail_text, 'plain')
    part2 = MIMEText(mail_html, 'html')
    
    # Attach parts into message container.
    msg.attach(part1)
    msg.attach(part2)
    
    # Send the message via local SMTP server.
    s = smtplib.SMTP(sender_smtp, sender_port) # SMTP server params
    s.starttls()    # start SMTP session (TLS)
    s.login(sender_user, sender_pass) # login to SMTP server

    # sendmail function takes 3 arguments: sender's address, recipient's address
    # and message to send - here it is sent as one string.
    s.sendmail(sender_user, useremail, msg.as_string())
    s.quit()

Fabio Lenzarini

unread,
Sep 6, 2021, 8:53:05 AM9/6/21
to Jam.py Users Mailing List
tank you very much Manos

ciao
Fabio

Drazen D. Babic

unread,
Sep 7, 2021, 4:44:38 AM9/7/21
to Jam.py Users Mailing List
Hi Manos, 

I think some libraries are missing:
pip install pyOpenSSL
pip install secrets

Also, when you say "user checked the "forgot password" checkbox" - checked where?

When I try to login:

the Traceback says:
.
.
    user_info = task.on_login(task, form_data, {'ip': ip, 'session_uuid': session_uuid})
  File "training", line 9, in on_login
    if form_data['email']:
KeyError: 'email'
.
and I can't login any more....The user IS there:

sqlite> select * from USERS;
0|1|a1|a1||pbkdf2:sha256:150000$sbrekNsO$e2356567d13686cc53e38cc9bb786d55ca26b4fb240f158f81be4fe65a81b98f|1|dbabic@localhost

And table"

CREATE TABLE "USERS"
(
"DELETED" INTEGER,
"ID" INTEGER PRIMARY KEY,
"LOGIN" TEXT,
"NAME" TEXT,
"PASSWORD" TEXT,
"PASSWORD_HASH" TEXT,
"ROLE" INTEGER, "EMAIL" TEXT);


D.

Drazen D. Babic

unread,
Sep 8, 2021, 9:24:30 PM9/8/21
to Jam.py Users Mailing List
It is ok, 

did not see the email from Andrew with App Export. All working now, magnificent improvement Manos and special thanks to Andrew for providing the Login Form!

For the readers, on Linux install Postfix. You'll need above libraries for Python. Than, for testing purposes, change this in Manos code and comment the line below:

    sender_smtp = "127.0.0.1"          # SMTP server
    sender_port = 25                       # SMTP port (here is SSL/TLS)
    sender_user = "root@localhost"     # STMP sender account
.
.
    #s.login(sender_user, sender_pass) # login to SMTP server  #only for testing leave as is for production

Add isactive and email for the Users table. Make isactive = 1 for all users and populate email. That is all. 
On reset password, we get a nice email like this (install mailx):

mailx
"/var/mail/dbabic": 3 messages 1 new 2 unread
 U   1 root@localhost     Thu Sep  9 09:08  42/1180  Password Reset
 U   2 root@localhost     Thu Sep  9 09:10  41/1155  Password Reset
>N   3 root@localhost     Thu Sep  9 09:20  39/1136  Password Reset

Enjoy

D.

Drazen D. Babic

unread,
Oct 1, 2021, 4:35:47 AM10/1/21
to Jam.py Users Mailing List
Update:

missing libraries to build with: pip install secrets 

apt-get install libsasl2-dev python-dev libldap2-dev libssl-dev

Drazen D. Babic

unread,
Oct 1, 2021, 11:53:03 PM10/1/21
to Jam.py Users Mailing List
Hey guys, 

I'm trying to do the same setup in production, but have an issue. Basically, looks like the set_where is NOT working but below is:

        #users.set_where(login=form_data['login'],email=form_data['email'])
        users.set_where(login=form_data['login'])
        users.open()
        print(users.login.value)

Any ideas why would this not work? The above means everyone can reset password for anyone :(

Also worth mentioning:
Message has been deleted

Drazen D. Babic

unread,
Oct 2, 2021, 8:31:02 AM10/2/21
to Jam.py Users Mailing List
Found the issue! The above set_where will fail with below when there are users with the same email:

    raise DatasetEmpty(consts.language('value_in_empty_dataset') % self.owner.item_name)
jam.dataset.DatasetEmpty: Item users - an attempt to get or set a field value in the empty dataset

Hence, the email must be UNIQUE for all users by my testing. This is not great since there might be a different username with the same email.

What is the solution for above?

Thanks

Manos Pappas

unread,
Oct 4, 2021, 6:28:48 AM10/4/21
to Jam.py Users Mailing List
Hi Drazen,

On my system, there is the requirement that each user has a unique e-mail (this is currently checked manually but I am investigating ways to automate this); this e-mail is checked on initial user registration.
Normally, each user must have a different e-mail otherwise we would need another "differential" field (i.e secret answer) that must be checked together with the e-mail, in order to issue a new password.
Reply all
Reply to author
Forward
0 new messages