Skip to content

Commit 7ea0a1c

Browse files
Merge pull request #24 from piyush-jaiswal/feature/flask-smorest-auth
Feature/flask smorest auth
2 parents fd36afe + 0d16c65 commit 7ea0a1c

File tree

10 files changed

+188
-151
lines changed

10 files changed

+188
-151
lines changed

app/__init__.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,13 +12,15 @@
1212

1313

1414
def register_blueprints():
15-
from app.migrated_routes.category import bp as category_bp
16-
from app.migrated_routes.subcategory import bp as subcategory_bp
17-
from app.migrated_routes.product import bp as product_bp
15+
from app.routes.category import bp as category_bp
16+
from app.routes.subcategory import bp as subcategory_bp
17+
from app.routes.product import bp as product_bp
18+
from app.routes.auth import bp as auth_bp
1819

1920
api.register_blueprint(category_bp, url_prefix="/categories")
2021
api.register_blueprint(subcategory_bp, url_prefix="/subcategories")
2122
api.register_blueprint(product_bp, url_prefix="/products")
23+
api.register_blueprint(auth_bp, url_prefix="/auth")
2224

2325

2426
app = Flask(__name__)
@@ -73,8 +75,6 @@ def missing_token_callback(error):
7375
return jsonify(code="authorization_required", error="JWT needed for this operation. Login, if needed."), 401
7476

7577

76-
from app import routes
77-
7878
swagger_config = {
7979
'openapi': '3.0.0',
8080
'title': 'Ecommerce REST API',

app/models.py

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -48,11 +48,8 @@ def get(email):
4848
return User.query.filter_by(email_normalized=email_normalized).scalar()
4949

5050
def set_email(self, email):
51-
try:
52-
self.email_normalized = self._normalize_email(email)
53-
self.email = email
54-
except EmailNotValidError as e:
55-
raise e
51+
self.email_normalized = self._normalize_email(email)
52+
self.email = email
5653

5754
def set_password(self, password):
5855
# scrypt stores salt with the hash, which it uses to verify the password

app/routes.py

Lines changed: 0 additions & 135 deletions
This file was deleted.
File renamed without changes.

app/routes/auth.py

Lines changed: 154 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,154 @@
1+
from email_validator import EmailNotValidError
2+
from flask import jsonify, make_response
3+
from flask.views import MethodView
4+
from flask_jwt_extended import (
5+
create_access_token,
6+
create_refresh_token,
7+
get_jwt_identity,
8+
jwt_required,
9+
)
10+
from flask_smorest import Blueprint, abort
11+
from sqlalchemy.exc import IntegrityError
12+
13+
from app import db
14+
from app.models import User
15+
from app.schemas import AuthIn, AuthOut
16+
17+
bp = Blueprint("auth", __name__)
18+
19+
20+
@bp.route("/register")
21+
class Register(MethodView):
22+
@bp.arguments(AuthIn)
23+
@bp.response(201)
24+
def post(self, data):
25+
"""
26+
Register a new user.
27+
---
28+
tags:
29+
- User
30+
description: Register a new user.
31+
requestBody:
32+
required: true
33+
description: email - Email id <br> password - Password
34+
content:
35+
application/json:
36+
schema:
37+
type: object
38+
required:
39+
- email
40+
- password
41+
properties:
42+
email:
43+
type: string
44+
password:
45+
type: string
46+
responses:
47+
201:
48+
description: User registered successfully.
49+
400:
50+
description: Invalid input.
51+
409:
52+
description: Email already exists.
53+
500:
54+
description: Internal Server Error.
55+
"""
56+
57+
user = User()
58+
user.set_password(data["password"])
59+
60+
try:
61+
user.set_email(data["email"])
62+
db.session.add(user)
63+
db.session.commit()
64+
except IntegrityError:
65+
db.session.rollback()
66+
abort(make_response(jsonify(error="Email already exists"), 409))
67+
except EmailNotValidError as e:
68+
abort(
69+
make_response(jsonify(code="invalid_email_format", error=str(e)), 422)
70+
)
71+
72+
return {"message": "Registered!"}
73+
74+
75+
@bp.route("/login")
76+
class Login(MethodView):
77+
"""Login a user and return access & refresh tokens."""
78+
79+
@bp.arguments(AuthIn)
80+
@bp.response(200, AuthOut)
81+
def post(self, data):
82+
"""
83+
Login a user.
84+
---
85+
tags:
86+
- User
87+
description: Login a user.
88+
requestBody:
89+
required: true
90+
description: email - Email id <br> password - Password
91+
content:
92+
application/json:
93+
schema:
94+
type: object
95+
required:
96+
- email
97+
- password
98+
properties:
99+
email:
100+
type: string
101+
password:
102+
type: string
103+
responses:
104+
200:
105+
description: User logged in successfully.
106+
400:
107+
description: Invalid input.
108+
401:
109+
description: Invalid email or password.
110+
500:
111+
description: Internal Server Error.
112+
"""
113+
user = User.get(email=data["email"])
114+
if not user or not user.check_password(data["password"]):
115+
return abort(
116+
make_response(
117+
jsonify(
118+
error="Invalid email or password. Check again or register."
119+
),
120+
401,
121+
)
122+
)
123+
124+
return {
125+
"access_token": create_access_token(identity=str(user.id), fresh=True),
126+
"refresh_token": create_refresh_token(identity=str(user.id)),
127+
}
128+
129+
130+
@bp.route("/refresh")
131+
class Refresh(MethodView):
132+
"""Get new access token using your refresh token."""
133+
134+
@jwt_required(refresh=True)
135+
@bp.response(200, AuthOut(only=("access_token",)))
136+
def post(self):
137+
"""
138+
Get new access token using your refresh token
139+
---
140+
tags:
141+
- User
142+
description: Get new access token using your refresh token.
143+
security:
144+
- refresh_token: []
145+
responses:
146+
200:
147+
description: New access token.
148+
401:
149+
description: Token expired, missing or invalid.
150+
500:
151+
description: Internal Server Error.
152+
"""
153+
identity = get_jwt_identity()
154+
return {"access_token": create_access_token(identity=identity, fresh=False)}
File renamed without changes.
File renamed without changes.
File renamed without changes.

app/schemas.py

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
1-
from marshmallow import Schema, ValidationError, fields, pre_load, validates
1+
from marshmallow import Schema, ValidationError, fields, pre_load, validate, validates
22
from marshmallow_sqlalchemy import SQLAlchemyAutoSchema, SQLAlchemySchema, auto_field
33

4-
from app.models import Category, Product, Subcategory
4+
from app.models import Category, Product, Subcategory, User
55

66

77
class CategoryOut(SQLAlchemyAutoSchema):
@@ -101,3 +101,24 @@ class NameArgs(Schema):
101101

102102
class PaginationArgs(Schema):
103103
page = fields.Int(load_default=1)
104+
105+
106+
# class AuthIn(Schema):
107+
class AuthIn(SQLAlchemySchema):
108+
class Meta:
109+
model = User
110+
111+
# email validation handled in User model
112+
email = auto_field()
113+
password = fields.Str(required=True, validate=validate.Length(min=1))
114+
115+
@pre_load
116+
def strip_strings(self, data, **kwargs):
117+
if "email" in data and data["email"] is not None:
118+
data["email"] = data["email"].strip()
119+
return data
120+
121+
122+
class AuthOut(Schema):
123+
access_token = fields.Str()
124+
refresh_token = fields.Str()

0 commit comments

Comments
 (0)