From d2e8fcd2b5949c7836feaf559cd65c5062a7748c Mon Sep 17 00:00:00 2001 From: Georg Date: Wed, 1 Sep 2021 12:55:44 +0200 Subject: Init + SSO registration Signed-off-by: Georg --- .gitignore | 1 + README.md | 1 + README.old.md | 25 ++++++++++++ flaskapp.py | 64 +++++++++++++++++++++++++++++ forms.py | 16 ++++++++ irc_register.py | 106 ++++++++++++++++++++++++++++++++++++++++++++++++ irc_verify.py | 45 ++++++++++++++++++++ requirements.old.txt | 13 ++++++ requirements.txt | 17 ++++++++ templates/register.html | 44 ++++++++++++++++++++ 10 files changed, 332 insertions(+) create mode 100644 .gitignore create mode 100644 README.md create mode 100644 README.old.md create mode 100644 flaskapp.py create mode 100644 forms.py create mode 100644 irc_register.py create mode 100644 irc_verify.py create mode 100644 requirements.old.txt create mode 100644 requirements.txt create mode 100644 templates/register.html diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..c18dd8d --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +__pycache__/ diff --git a/README.md b/README.md new file mode 100644 index 0000000..d0cd2e4 --- /dev/null +++ b/README.md @@ -0,0 +1 @@ +This is a prototype allowing users to register with all of our services in a single place. diff --git a/README.old.md b/README.old.md new file mode 100644 index 0000000..512403e --- /dev/null +++ b/README.old.md @@ -0,0 +1,25 @@ +# Liberta Casa + +This has a series of little enhancements I have taken up as pet projects. + +### Registration Page + +##### Theoreticals + +It includes the following technologies: oragono IRCd, flask, a python bot using irctokens + +It consists of the following flow. + +1. A user shall go on to the [registration](https://liberta.casa/register.html) (placeholder). They will enter the details and click on Register. + * The Website is generated using `flask` and the form is generated using `wtforms, flask_wtf`. + * It shall capture the username and password entered by the user and POST it to the same route. + * The username and password already have validators to ensure they fit within the parameters if the oragono ircd services. eg. NICKLEN 32 +2. The bot will be triggered and it shall carry the information provided as arguments by connecting to the IRCd. +3. It will use the `USER` ,`NICK ` commands to register the connection on the IRCd then assign the nickname same as that passed on by the flask route. + * If no lines are recieved it shall throw a server error. + * If the nickname is already in use then the received the `433` code will be captured and translated back to the user as suggestion to retry with a different username + * If the `NICK` command is successful it shall proceed to the next step +4. Using the `PRIVMSG` command the bot shall register for the user and it shall read for `NOTICE` indicating successful account creation and carry that back to the flask app and be shown to the user. +5. TODO: If this fails add and unconditional which exits or it will be an infinite loop. +6. After this success the bot shall Die and the user will be redirected to the page which contains Rules and FAQs about login and features. + diff --git a/flaskapp.py b/flaskapp.py new file mode 100644 index 0000000..cfa21e8 --- /dev/null +++ b/flaskapp.py @@ -0,0 +1,64 @@ +from flask import Flask, render_template, url_for, request, redirect, flash +from forms import RegistrationForm +from irc_register import ircregister +#from irc_verify import ircverify + + +app = Flask(__name__) +app.config['SECRET_KEY'] = '$secret' #remove later + +@app.route('/') +def hello(): + return render_template('home.html') + +@app.route('/kiwi') +def kiwi(): + return redirect("https://liberta.casa/kiwi/") +@app.route('/kiwi/') +def kiwinick(nick, show_password_box): + nick = request.args.get('nick', None) + show_password_box = requests.args.get('show_password_box', None) + return redirect("https://liberta.casa/kiwi/") + +@app.route('/register', methods=['GET', 'POST']) +def register(): + form = RegistrationForm() + if request.method == 'POST': + + username = request.form.get('username') + email = request.form.get('email') + password = request.form.get('password') +# email = request.form.get('email') add password arg to ircregisterfunction + response = ircregister(username, password, email) + if response == "server failure": + flash("Server Unavailable") + elif response == "433": + flash("Username already taken. Please select a different username") + elif response == "success": + return redirect(url_for('kiwinick', nick=username, show_password_box='true')) + elif response == "failure": + flash("Failure! Please try after some time or use NickServ.") + + return render_template('register.html', title='Register', form=form) + +#@app.route('/verify', methods=['GET', 'POST']) +#def verify(): +# form = VerificationForm() +# if request.method == 'POST': +# +# username = request.form.get('username') +# verif_code = request.form.get('verif_code') +# response = ircverify(username, verif_code) +# if response == "server failure": +# flash("Server Unavailable") +# elif response == "433": +# flash("Username under use. Please check your username or visit us for help") +# elif response == "success": +# return redirect(url_for('kiwi')) +# elif response == "failure": +# flash("Failure! Please try after some time or use NickServ.") +# return render_template('verify.html', title='Verify', form=form) + + +if __name__ == '__main__': + app.run(debug=True) diff --git a/forms.py b/forms.py new file mode 100644 index 0000000..6e55f64 --- /dev/null +++ b/forms.py @@ -0,0 +1,16 @@ +from flask_wtf import FlaskForm +from wtforms import StringField, PasswordField, SubmitField +from wtforms.validators import DataRequired, Length, EqualTo, Email + + +class RegistrationForm(FlaskForm): + username = StringField('Username', validators=[DataRequired(), Length(min=1, max=32)]) + email = StringField('Email', validators=[DataRequired(), Email()]) + password = PasswordField('Password', validators=[DataRequired()]) + confirm_password = PasswordField('Confirm Password', validators=[DataRequired(), EqualTo('password')]) + submit = SubmitField('Register') + +#class VerificationForm(FlaskForm): +# username = StringField('Username', validators=[DataRequired(), Length(min=1, max=32)]) +# verif_code = StringField('Verification Code', validators=[DataRequired()]) +# submit = SubmitField('Verify') diff --git a/irc_register.py b/irc_register.py new file mode 100644 index 0000000..fcad711 --- /dev/null +++ b/irc_register.py @@ -0,0 +1,106 @@ +import socket, irctokens +import requests +import re + +def ircregister(username, password, email): + # define the variables + d = irctokens.StatefulDecoder() + e = irctokens.StatefulEncoder() + s = socket.socket() + + #connecting to the server + s.connect(("127.0.0.1", 6667)) + + #defining the send function with proper formatting + def _send(line): + print(f"> {line.format()}") + e.push(line) + while e.pending(): + e.pop(s.send(e.pending())) + + # registering the connection to the server + + _send(irctokens.build("USER", [username, "0", "*", username])) + _send(irctokens.build("NICK", [username])) + + server = 'http://192.168.0.115:8880' + realm = 'devel' + tokenurl = 'http://localhost/kctoken' + usererr = 'An error occured.' + emailverified = False + firstname = 'Foo' + lastname = 'Bar' + + # go through the cases + + while True: + lines = d.push(s.recv(1024)) + + if lines == None: #if nothing is received from server + return "server error" + break + + for line in lines: + print(f"< {line.format()}") + + if line.command == "433": # if nickname already in use + return "433" + + elif line.command == "005": # when 005 is received pass the nickserv register command command + _send(irctokens.build("PRIVMSG", ["NickServ", f"REGISTER {password}"])) + if line.command == 'NOTICE' and line.params == [username, f"Account created"]: + _send(irctokens.build("QUIT")) + try: + tokendl = requests.get(tokenurl) + tokendata = tokendl.json() + token = tokendata['access_token'] + url = server + '/auth/admin/realms/' + realm + '/users' + except: + print("ERROR: Keycloak token could not be installed.") + if re.match(r"[^@]+@[^@]+\.[^@]+", email): + payload = { + "firstName": firstname, + "lastName": lastname, + "email": email, + "enabled": "true", + "username": username, + "credentials": [{"type": "password", "value": password, "temporary": emailverified,}], + "emailVerified": emailverified + } + response = requests.post( + url, + headers = {'Content-Type': 'application/json', 'Authorization': 'Bearer ' + token}, + json = payload + ) + print("Keycloak: HTTP Status ", response.status_code) + try: + print("Keycloak: Response Text: ", response.text) + except: + print("Keycloak: No or invalid response text. This is not an error.") + try: + print("Keycloak: Response JSON: ", response.json()) + except: + print("Keycloak: No or invalid response JSON. This it not an error.") + status = response.status_code + if status == 201: + print(" SSO User " + username + " created.") + if status == 400: + print("ERROR: Keycloak indicated that the request is invalid.") + if status == 401: + print("ERROR: Fix your Keycloak API credentials and/or client roles, doh.") + if status == 403: + print("ERROR: Keycloak indicated that the authorization provided is not enough to access the resource.") + if status == 404: + print("ERROR: Keycloak indicated that the requested resource does not exist.") + if status == 409: + print("ERROR: Keycloak indicated that the resource already exists or \"some other coonflict when processing the request\" occured.") + if status == 415: + print("ERROR: Keycloak indicated that the requested media type is not supported.") + if status == 500: + print("ERROR: Keycloak indicated that the server could not fullfill the request due to \"some unexpected error \".") + else: + print('Invalid email address supplied.') + + return "success" + +# register("hello", "test") diff --git a/irc_verify.py b/irc_verify.py new file mode 100644 index 0000000..ff15818 --- /dev/null +++ b/irc_verify.py @@ -0,0 +1,45 @@ +import socket, irctokens + + +def ircverify(username, verif_code): + # define the variables + d = irctokens.StatefulDecoder() + e = irctokens.StatefulEncoder() + s = socket.socket() + + #connecting to the server + s.connect(("127.0.0.1", 6667)) + + #defining the send function with proper formatting + def _send(line): + print(f"> {line.format()}") + e.push(line) + while e.pending(): + e.pop(s.send(e.pending())) + # registering the connection to the server + + _send(irctokens.build("USER", [username, "0", "*", username])) + _send(irctokens.build("NICK", [username])) + + # go through the cases + + while True: + lines = d.push(s.recv(1024)) + + if lines == None: #if nothing is received from server + return "server error" + break + + for line in lines: + print(f"< {line.format()}") + + if line.command == "433": # if nickname already in use + return "433" + + elif line.command == "005": # when 005 is received pass the nickserv register command command + _send(irctokens.build("PRIVMSG", ["NickServ", f"VERIFY {username} {verif_code}"])) + + if line.command == "NOTICE" and line.params == [username, "Account created"]: # if Services respond with appropriate notice NOTICE + _send(irctokens.build("QUIT")) + return "success" + diff --git a/requirements.old.txt b/requirements.old.txt new file mode 100644 index 0000000..daa45d8 --- /dev/null +++ b/requirements.old.txt @@ -0,0 +1,13 @@ +click==7.1.2 +dnspython==1.16.0 +email-validator==1.1.1 +Flask==1.1.2 +Flask-WTF==0.14.3 +gunicorn==20.0.4 +idna==2.9 +irctokens>=1.0.1 +itsdangerous==1.1.0 +Jinja2==2.11.2 +MarkupSafe==1.1.1 +Werkzeug==1.0.1 +WTForms==2.3.1 diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..db3653d --- /dev/null +++ b/requirements.txt @@ -0,0 +1,17 @@ +certifi==2021.5.30 +charset-normalizer==2.0.4 +click==7.1.2 +dnspython==1.16.0 +email-validator==1.1.1 +Flask==1.1.2 +Flask-WTF==0.14.3 +gunicorn==20.0.4 +idna==2.9 +irctokens==2.0.0 +itsdangerous==1.1.0 +Jinja2==2.11.2 +MarkupSafe==1.1.1 +requests==2.26.0 +urllib3==1.26.6 +Werkzeug==1.0.1 +WTForms==2.3.1 diff --git a/templates/register.html b/templates/register.html new file mode 100644 index 0000000..9c535e3 --- /dev/null +++ b/templates/register.html @@ -0,0 +1,44 @@ + + + + + + Register + + + + + {% for message in get_flashed_messages() %} + {{ message }} + {% endfor %} +
+┬   ┌┬┐ ┬─┐ ┬─┐ ┬─┐ ┌┐┐ ┬─┐
+│    │  │─│ │─  │┬┘  │  │─┤
+┘─┘ └┴┘ │─┘ ┴─┘ │└┘  ┘  ┘ │
+
+┌─┐ ┬─┐ ┐─┐ ┬─┐
+│   │─┤ └─┐ │─┤
+└─┘ ┘ │ ──┘ ┘ │
+
+
+ {{ form.hidden_tag() }} +
+ Sign Up + {{ form.username.label }} + {{ form.username }} + {{ form.email.label }} + {{ form.email }} + {{ form.password.label }} + {{ form.password }} + {{ form.confirm_password.label }} + {{ form.confirm_password }} +
+ {{ form.submit }} +
+ +If you already have an account please login to the IRC using SASL. -- cgit v1.2.3