shopyo-auth 1.0.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (27) hide show
  1. shopyo_auth-1.0.0/PKG-INFO +13 -0
  2. shopyo_auth-1.0.0/pyproject.toml +36 -0
  3. shopyo_auth-1.0.0/setup.cfg +4 -0
  4. shopyo_auth-1.0.0/shopyo_auth/__init__.py +20 -0
  5. shopyo_auth-1.0.0/shopyo_auth/decorators.py +15 -0
  6. shopyo_auth-1.0.0/shopyo_auth/forms.py +71 -0
  7. shopyo_auth-1.0.0/shopyo_auth/info.json +7 -0
  8. shopyo_auth-1.0.0/shopyo_auth/models.py +150 -0
  9. shopyo_auth-1.0.0/shopyo_auth/static/style.css +7 -0
  10. shopyo_auth-1.0.0/shopyo_auth/templates/shopyo_auth/blocks/login_form.html +36 -0
  11. shopyo_auth-1.0.0/shopyo_auth/templates/shopyo_auth/blocks/register_form.html +44 -0
  12. shopyo_auth-1.0.0/shopyo_auth/templates/shopyo_auth/emails/activate_user.html +16 -0
  13. shopyo_auth-1.0.0/shopyo_auth/templates/shopyo_auth/emails/activate_user.txt +13 -0
  14. shopyo_auth-1.0.0/shopyo_auth/templates/shopyo_auth/login.html +46 -0
  15. shopyo_auth-1.0.0/shopyo_auth/templates/shopyo_auth/register.html +26 -0
  16. shopyo_auth-1.0.0/shopyo_auth/templates/shopyo_auth/shop_login.html +19 -0
  17. shopyo_auth-1.0.0/shopyo_auth/templates/shopyo_auth/unconfirmed.html +19 -0
  18. shopyo_auth-1.0.0/shopyo_auth/upload.py +23 -0
  19. shopyo_auth-1.0.0/shopyo_auth/view.py +142 -0
  20. shopyo_auth-1.0.0/shopyo_auth.egg-info/PKG-INFO +13 -0
  21. shopyo_auth-1.0.0/shopyo_auth.egg-info/SOURCES.txt +25 -0
  22. shopyo_auth-1.0.0/shopyo_auth.egg-info/dependency_links.txt +1 -0
  23. shopyo_auth-1.0.0/shopyo_auth.egg-info/requires.txt +1 -0
  24. shopyo_auth-1.0.0/shopyo_auth.egg-info/top_level.txt +2 -0
  25. shopyo_auth-1.0.0/tests/test_auth_forms.py +1 -0
  26. shopyo_auth-1.0.0/tests/test_auth_functional.py +289 -0
  27. shopyo_auth-1.0.0/tests/test_auth_models.py +276 -0
@@ -0,0 +1,13 @@
1
+ Metadata-Version: 2.1
2
+ Name: shopyo_auth
3
+ Version: 1.0.0
4
+ Summary: Base module containing jinja macros and templates
5
+ Author-email: Abdur-Rahmaan Janhangeer <arj.python@gmail.com>
6
+ Project-URL: Homepage, https://github.com/shopyo/shopyo
7
+ Project-URL: Bug Tracker, https://github.com/shopyo/shopyo/issues
8
+ Keywords: shopyo
9
+ Classifier: Programming Language :: Python :: 3
10
+ Classifier: License :: OSI Approved :: BSD License
11
+ Classifier: Operating System :: OS Independent
12
+ Requires-Python: >=3.8
13
+ Requires-Dist: shopyo
@@ -0,0 +1,36 @@
1
+ [project]
2
+ name = "shopyo_auth"
3
+ authors = [{ name = "Abdur-Rahmaan Janhangeer", email = "arj.python@gmail.com" }]
4
+ description = "Base module containing jinja macros and templates"
5
+ # readme = "README.md"
6
+ requires-python = ">=3.8"
7
+ # license = { file = "LICENSE.txt" }
8
+ keywords = ["shopyo"]
9
+ classifiers = [
10
+ "Programming Language :: Python :: 3",
11
+ "License :: OSI Approved :: BSD License",
12
+ "Operating System :: OS Independent",
13
+ ]
14
+ dependencies = ["shopyo"]
15
+ dynamic = ["version"]
16
+
17
+ [tool.setuptools.dynamic]
18
+ version = { attr = "shopyo_auth.__version__" }
19
+
20
+ [project.urls]
21
+ "Homepage" = "https://github.com/shopyo/shopyo"
22
+ "Bug Tracker" = "https://github.com/shopyo/shopyo/issues"
23
+
24
+ [build-system]
25
+ requires = ["setuptools>=61.0"]
26
+ build-backend = "setuptools.build_meta"
27
+
28
+ [tool.setuptools]
29
+ include-package-data = true
30
+
31
+ [tool.setuptools.packages.find]
32
+ where = ["."]
33
+ exclude = ["tests*", "docs*", "examples*"]
34
+
35
+ [tool.setuptools.package-data]
36
+ shopyo_auth = ["static/**", "templates/**", "*.json"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,20 @@
1
+ from typing import Any
2
+
3
+ from flask import Flask
4
+ from .view import module_blueprint
5
+
6
+ __version__ = "1.0.0"
7
+
8
+
9
+ class ShopyoAuth:
10
+ def __init__(self, app: Any = None) -> None:
11
+ if app is not None:
12
+ self.init_app(app)
13
+
14
+ def init_app(self, app: Flask) -> None:
15
+ if not hasattr(app, "extensions"):
16
+ app.extensions = {}
17
+
18
+ app.extensions["shopyo_auth"] = self
19
+ bp = module_blueprint
20
+ app.register_blueprint(bp)
@@ -0,0 +1,15 @@
1
+ from functools import wraps
2
+
3
+ from flask import redirect
4
+ from flask import url_for
5
+ from flask_login import current_user
6
+
7
+
8
+ def check_confirmed(func):
9
+ @wraps(func)
10
+ def decorated_function(*args, **kwargs):
11
+ if current_user.is_email_confirmed:
12
+ return func(*args, **kwargs)
13
+ return redirect(url_for("auth.unconfirmed"))
14
+
15
+ return decorated_function
@@ -0,0 +1,71 @@
1
+ from flask_wtf import FlaskForm
2
+ from sqlalchemy import func
3
+ from wtforms import PasswordField
4
+ from wtforms.fields import EmailField
5
+ from wtforms.validators import DataRequired
6
+ from wtforms.validators import Email
7
+ from wtforms.validators import EqualTo
8
+ from wtforms.validators import InputRequired
9
+ from wtforms.validators import Length
10
+ from wtforms.validators import ValidationError
11
+
12
+
13
+ class LoginForm(FlaskForm):
14
+ email = EmailField(
15
+ "email",
16
+ [DataRequired(), Email(message="Not a valid email address.")],
17
+ render_kw={"class": "form-control", "autocomplete": "off"},
18
+ )
19
+ password = PasswordField(
20
+ "Password",
21
+ [DataRequired()],
22
+ render_kw={"class": "form-control", "autocomplete": "off"},
23
+ )
24
+
25
+
26
+ class RegistrationForm(FlaskForm):
27
+ """Registration Form"""
28
+
29
+ email = EmailField(
30
+ "email_label",
31
+ [DataRequired(), Email(message="Not a valid email address.")],
32
+ )
33
+
34
+ password = PasswordField(
35
+ "New Password",
36
+ validators=[
37
+ InputRequired("Password is required"),
38
+ Length(
39
+ min=6,
40
+ max=25,
41
+ message="Password must be between 6 and 25 characters",
42
+ ),
43
+ EqualTo("confirm", message="Passwords must match"),
44
+ ],
45
+ )
46
+ confirm = PasswordField(
47
+ "Repeat Password",
48
+ )
49
+
50
+ def validate_email(self, field):
51
+ """
52
+ Inline validator for email. Checks to see if a user object with
53
+ entered email already present in the database
54
+
55
+ Args:
56
+ field : The form field that contains email data.
57
+
58
+ Raises:
59
+ ValidationError: if the username entered in the field is already
60
+ in the database
61
+ """
62
+ try:
63
+ from .models import User
64
+ except Exception as e:
65
+ raise e
66
+ user = User.query.filter(
67
+ func.lower(User.email) == func.lower(field.data)
68
+ ).scalar()
69
+
70
+ if user is not None:
71
+ raise ValidationError(f"email '{field.data}' is already in use.")
@@ -0,0 +1,7 @@
1
+ {
2
+ "display_string": "Login",
3
+ "type": "hidden",
4
+ "fa-icon": "fas fa-clock",
5
+ "module_name": "shopyo_auth",
6
+ "url_prefix": "/shopyo-auth"
7
+ }
@@ -0,0 +1,150 @@
1
+ """
2
+ .. module:: AdminModels
3
+ :synopsis: Contains model of a user Record
4
+
5
+ """
6
+
7
+ import datetime
8
+
9
+ from flask import current_app
10
+ from flask_login import AnonymousUserMixin
11
+ from flask_login import UserMixin
12
+
13
+ from init import db
14
+ from init import login_manager
15
+
16
+ from itsdangerous import URLSafeTimedSerializer
17
+ from sqlalchemy.ext.hybrid import hybrid_property
18
+ from werkzeug.security import check_password_hash
19
+ from werkzeug.security import generate_password_hash
20
+
21
+ from shopyo.api.models import PkModel
22
+
23
+ role_user_bridge = db.Table(
24
+ "role_user_bridge",
25
+ db.Column(
26
+ "user_id",
27
+ db.Integer(),
28
+ db.ForeignKey("users.id", ondelete="CASCADE"),
29
+ primary_key=True,
30
+ ),
31
+ db.Column(
32
+ "role_id",
33
+ db.Integer(),
34
+ db.ForeignKey("roles.id", ondelete="CASCADE"),
35
+ primary_key=True,
36
+ ),
37
+ )
38
+
39
+
40
+ class AnonymousUser(AnonymousUserMixin):
41
+ """Anonymous user class"""
42
+
43
+ def __init__(self):
44
+ self.username = "guest"
45
+ self.email = "<anonymous-user-no-email>"
46
+
47
+ @property
48
+ def is_email_confirmed(self):
49
+ is_disabled = False
50
+
51
+ if "EMAIL_CONFIRMATION_DISABLED" in current_app.config:
52
+ is_disabled = current_app.config["EMAIL_CONFIRMATION_DISABLED"]
53
+
54
+ if is_disabled is not True:
55
+ is_disabled = False
56
+
57
+ return is_disabled
58
+
59
+ @property
60
+ def is_admin(self):
61
+ return False
62
+
63
+ def __repr__(self):
64
+ return f"<AnonymousUser {self.username}>"
65
+
66
+
67
+ login_manager.anonymous_user = AnonymousUser
68
+
69
+
70
+ class User(UserMixin, PkModel):
71
+ """The user of the app"""
72
+
73
+ __tablename__ = "users"
74
+
75
+ username = db.Column(db.String(100), unique=True)
76
+ _password = db.Column(db.String(128), nullable=False)
77
+ first_name = db.Column(db.String(128))
78
+ last_name = db.Column(db.String(128))
79
+ is_admin = db.Column(db.Boolean, default=False)
80
+ email = db.Column(db.String(120), unique=True, nullable=False)
81
+ date_registered = db.Column(
82
+ db.DateTime, nullable=False, default=datetime.datetime.now()
83
+ )
84
+ is_email_confirmed = db.Column(db.Boolean(), nullable=False, default=False)
85
+ email_confirm_date = db.Column(db.DateTime)
86
+
87
+ # A user can have many roles and a role can have many users
88
+ roles = db.relationship(
89
+ "Role",
90
+ secondary=role_user_bridge,
91
+ backref="users",
92
+ )
93
+
94
+ def __repr__(self):
95
+ return f"<User-id: {self.id}, User-email: {self.email}>"
96
+
97
+ @hybrid_property
98
+ def password(self):
99
+ return self._password
100
+
101
+ @password.setter
102
+ def password(self, plaintext):
103
+ # the default hashing method is pbkdf2:sha256
104
+ self._password = generate_password_hash(plaintext)
105
+
106
+ def check_password(self, password):
107
+ return check_password_hash(self._password, password)
108
+
109
+ def generate_confirmation_token(self):
110
+ serializer = URLSafeTimedSerializer(current_app.config["SECRET_KEY"])
111
+ return serializer.dumps(self.email, salt=current_app.config["PASSWORD_SALT"])
112
+
113
+ def confirm_token(self, token, expiration=3600):
114
+ serializer = URLSafeTimedSerializer(current_app.config["SECRET_KEY"])
115
+ email = False
116
+ try:
117
+ email = serializer.loads(
118
+ token,
119
+ salt=current_app.config["PASSWORD_SALT"],
120
+ max_age=expiration,
121
+ )
122
+ except Exception as e:
123
+ print(f"\nShopyo-LOG, Error at confirm_token: {e}")
124
+ return False
125
+
126
+ if email != self.email:
127
+ return False
128
+
129
+ self.is_email_confirmed = True
130
+ self.email_confirm_date = datetime.datetime.now()
131
+ self.update()
132
+ return True
133
+
134
+
135
+ @login_manager.user_loader
136
+ def load_user(user_id):
137
+ return User.query.get(user_id)
138
+
139
+
140
+ login_manager.login_view = "shopyo_auth.login"
141
+
142
+
143
+ class Role(PkModel):
144
+ """A role for a user."""
145
+
146
+ __tablename__ = "roles"
147
+ name = db.Column(db.String(100), nullable=False)
148
+
149
+ def __repr__(self):
150
+ return f"<Role-id: {self.id}, Role-name: {self.name}>"
@@ -0,0 +1,7 @@
1
+ /* for styling WTform errors*/
2
+ .form-errors {
3
+ list-style-type: none;
4
+ padding: 0px;
5
+ margin: 0px;
6
+ color: red;
7
+ }
@@ -0,0 +1,36 @@
1
+
2
+ <p class="h2">Login</p>
3
+ <div class="input-group mb-3">
4
+ <div class="input-group-prepend">
5
+ <span class="input-group-text"><i class="fa fa-id-card"></i></span>
6
+ </div>
7
+ {{ form.email() }}
8
+ {% if form.email.errors %}
9
+ <ul class="errors">
10
+ {% for error in form.user_id.errors %}
11
+ <li>{{ error }}</li>
12
+ {% endfor %}
13
+ </ul>
14
+ {% endif %}
15
+ </div>
16
+ <div class="input-group mb-3">
17
+ <div class="input-group-prepend">
18
+ <span class="input-group-text"><i class="fa fa-key"></i></span>
19
+ </div>
20
+ {{ form.password() }}
21
+ {% if form.password.errors %}
22
+ <ul class="errors">
23
+ {% for error in form.password.errors %}
24
+ <li>{{ error }}</li>
25
+ {% endfor %}
26
+ </ul>
27
+ {% endif %}
28
+ </div>
29
+ <input type="hidden" name="csrf_token" value="{{ csrf_token() }}"/>
30
+
31
+ <input
32
+ type="hidden"
33
+ name="next"
34
+ value="{{ request.args.get('next', '') }}"
35
+ />
36
+ <input type="submit" name="" value="submit" class="btn btn-info">
@@ -0,0 +1,44 @@
1
+ <p class="h2">Register</p>
2
+
3
+ <div class="form-group">
4
+ <div class="input-group mb-3">
5
+ <div class="input-group-prepend">
6
+ <span class="input-group-text"><i class="fa fa-id-card"></i></span>
7
+ </div>
8
+ {{ form.email(class_="form-control", placeholder="Email", autocomplete="off", autofocus=true) }}
9
+ </div>
10
+ <ul class="form-errors">
11
+ {% for error in form.email.errors %}
12
+ <li>{{ error }}</li>
13
+ {% endfor %}
14
+ </ul>
15
+ </div>
16
+ <div class="form-group">
17
+ <div class="input-group mb-3">
18
+ <div class="input-group-prepend">
19
+ <span class="input-group-text"><i class="fa fa-key"></i></span>
20
+ </div>
21
+ {{ form.password(class_="form-control", placeholder="Password") }}
22
+ </div>
23
+ <ul class="form-errors">
24
+ {% for error in form.password.errors %}
25
+ <li>{{ error }}</li>
26
+ {% endfor %}
27
+ </ul>
28
+ </div>
29
+ <div class="form-group">
30
+ <div class="input-group mb-3">
31
+ <div class="input-group-prepend">
32
+ <span class="input-group-text"><i class="fa fa-key"></i></span>
33
+ </div>
34
+ {{ form.confirm(class_="form-control", placeholder="Confirm Password") }}
35
+ </div>
36
+ <ul class="form-errors">
37
+ {% for error in form.confirm.errors %}
38
+ <li>{{ error }}</li>
39
+ {% endfor %}
40
+ </ul>
41
+ </div>
42
+
43
+ <input type="hidden" name="csrf_token" value="{{ csrf_token() }}"/>
44
+ <input type="submit" name="" value="register" class="btn btn-info">
@@ -0,0 +1,16 @@
1
+ <p>Dear {{ user.email }},</p>
2
+
3
+ <p>Welcome to <b>Shopyo</b>!</p>
4
+
5
+ <p>
6
+ To confirm your account please <a href="{{ url_for('auth.confirm', token=token, _external=True) }}">click here</a>.
7
+ </p>
8
+
9
+ <p>Alternatively, you can paste the following link in your browser's address bar:</p>
10
+ <p>{{ url_for('auth.confirm', token=token, _external=True) }}</p>
11
+
12
+ <p>Sincerely,</p>
13
+
14
+ <p>The Shopyo Team</p>
15
+
16
+ <p><small>Note: replies to this email address are not monitored.</small></p>
@@ -0,0 +1,13 @@
1
+ Dear {{ user.email }},
2
+
3
+ Welcome to Shopyo!
4
+
5
+ To confirm your account please click on the following link:
6
+
7
+ {{ url_for('auth.confirm', token=token, _external=True) }}
8
+
9
+ Sincerely,
10
+
11
+ The Shopyo Team
12
+
13
+ Note: replies to this email address are not monitored.
@@ -0,0 +1,46 @@
1
+ {% extends "shopyo_base/main_base.html" %}
2
+
3
+ {% set active_page = "login" %}
4
+ {% block pagehead %}
5
+ <title>{{active_page.capitalize()}}</title>
6
+ <style>
7
+ .hidden {
8
+ display: none;
9
+ }
10
+
11
+ .show {
12
+ display: inline-block;
13
+ }
14
+
15
+ .active {
16
+ width: 6rem;
17
+ color: white;
18
+ background-color: #34ce57;
19
+ }
20
+
21
+ .inactive {
22
+ width: 6rem;
23
+ color: white;
24
+ background-color: #ff253a;
25
+ }
26
+
27
+ .all_inactive {
28
+ display: none;
29
+ }
30
+ </style>
31
+
32
+ {% endblock %}
33
+ {% block content %}
34
+
35
+ <div class="container col-md-6 off-set-2 text-center test">
36
+ <div class="card">
37
+ <div class="card-body">
38
+ <img src="/static/shopyo.svg" width="175" height="150">
39
+ <form action="{{url_for('shopyo_auth.login')}}" method="POST">
40
+ {%include 'shopyo_auth/blocks/login_form.html'%}
41
+ </form>
42
+ </div>
43
+ </div>
44
+ </div>
45
+
46
+ {% endblock %}
@@ -0,0 +1,26 @@
1
+ {% extends "shopyo_base/main_base.html" %}
2
+
3
+ {% set active_page = "register" %}
4
+
5
+ {% block pagehead %}
6
+ <link rel="stylesheet" type="text/css" href="{{ url_for('static', filename='modules/box__default/auth/style.css') }}">
7
+ <title>{{ active_page.capitalize() }}</title>
8
+ {% endblock %}
9
+
10
+ {% block content %}
11
+ <div class="container col-md-6 mb-4 off-set-2 text-center">
12
+ <div class="card">
13
+ <div class="card-body">
14
+ <img src="/static/shopyo.svg" width="175" height="150">
15
+ <form action="{{url_for('shopyo_auth.register')}}" method="post">
16
+ {%include 'auth/blocks/register_form.html'%}
17
+ </form>
18
+ <hr>
19
+ <span class="text-muted">
20
+ Have already registered?
21
+ <a href="{{ url_for('shopyo_auth.login') }}">Sign in here</a>
22
+ </span>
23
+ </div>
24
+ </div>
25
+ </div>
26
+ {% endblock %}
@@ -0,0 +1,19 @@
1
+ {%set active_page = 'shop.html'%}
2
+ {% extends "shop/base.html" %}
3
+ {% block pagehead %}
4
+
5
+ {% endblock %}
6
+ {% block content %}
7
+ <div class="shop-spacer-1">
8
+ </div>
9
+ <div class="container">
10
+ <div class="card">
11
+ <div class="card-body">
12
+ <form action="{{ url_for('auth.shop_login') }}" method="post">
13
+ {%include 'auth/blocks/login_form.html'%}
14
+ </form>
15
+ </div>
16
+ </div>
17
+
18
+ </div>
19
+ {% endblock %}
@@ -0,0 +1,19 @@
1
+ {% extends "shopyo_base/main_base.html" %}
2
+
3
+ {% set active_page = "Email Confirmation" %}
4
+
5
+ {% block pagehead %}
6
+ <title>{{ active_page.capitalize() }}</title>
7
+ {% endblock %}
8
+
9
+ {% block content %}
10
+ <div class="container col-md-6 mb-4 off-set-2 text-center">
11
+ <div class="card">
12
+ <div class="card-body">
13
+ <img src="/static/shopyo.svg" width="175" height="150">
14
+ <p>You have not confirmed your account. Email confirmation link was sent to <span class="mark">{{ current_user.email }}</span>. Please check your inbox and your spam folder.</p>
15
+ <p>Didn't get the email?<a href="{{ url_for('auth.resend',_external=True) }}"> Resend</a></p>
16
+ </div>
17
+ </div>
18
+ </div>
19
+ {% endblock %}
@@ -0,0 +1,23 @@
1
+ import datetime
2
+ import json
3
+
4
+ from .models import User
5
+
6
+
7
+ def add_admin(email, password):
8
+ user = User()
9
+ user.email = email
10
+ user.password = password
11
+ user.is_admin = True
12
+ user.is_email_confirmed = True
13
+ user.email_confirm_date = datetime.datetime.now()
14
+ user.save()
15
+
16
+
17
+ def upload(verbose=False):
18
+ with open("config.json") as config:
19
+ config = json.load(config)
20
+ add_admin(config["admin_user"]["email"], config["admin_user"]["password"])
21
+
22
+ if verbose:
23
+ print("[x] Added Admin User")