From 69f15f91e791431bf7f8ab18a9825054779a858d Mon Sep 17 00:00:00 2001 From: Eduardo Aguad Date: Tue, 24 Oct 2023 23:37:42 -0300 Subject: [PATCH] implement authentication --- src/collapsar/Select.py | 7 +- .../assets/js/components/user-auth-form.tsx | 67 +++++++++++++++++++ .../assets/js/context/CollapsarProvider.tsx | 6 +- src/collapsar/assets/js/pages/Layout.tsx | 2 +- src/collapsar/assets/js/pages/Login.tsx | 32 +++++++++ src/collapsar/assets/js/services/router.tsx | 5 ++ src/collapsar/controllers/AuthController.py | 24 +++++++ src/collapsar/routes/api.py | 3 + src/collapsar/routes/web.py | 7 +- src/collapsar/templates/admin/base.html | 2 +- src/collapsar/templates/admin/index.html | 4 +- .../app/middlewares/VerifyCsrfToken.py | 2 +- tests/integrations/app/models/User.py | 1 - tests/integrations/config/providers.py | 2 +- 14 files changed, 150 insertions(+), 14 deletions(-) create mode 100644 src/collapsar/assets/js/components/user-auth-form.tsx create mode 100644 src/collapsar/assets/js/pages/Login.tsx create mode 100644 src/collapsar/controllers/AuthController.py diff --git a/src/collapsar/Select.py b/src/collapsar/Select.py index 51edca9..65e5eb9 100644 --- a/src/collapsar/Select.py +++ b/src/collapsar/Select.py @@ -31,10 +31,11 @@ def get_display_value(self): if not (len(self._options) > 0 and isinstance(self._options[0], dict)): return self.value - if option := next(filter(lambda option: str(option["value"]) == self.value, self._options)): - return option["label"] + filter_value = list( + filter(lambda option: str(option["value"]) == self.value, self._options) + ) - return self.value + return filter_value[0]["label"] if filter_value else self.value def json_serialize(self): """ diff --git a/src/collapsar/assets/js/components/user-auth-form.tsx b/src/collapsar/assets/js/components/user-auth-form.tsx new file mode 100644 index 0000000..4ef5f73 --- /dev/null +++ b/src/collapsar/assets/js/components/user-auth-form.tsx @@ -0,0 +1,67 @@ +import * as React from "react" + +import { cn } from "@/lib/utils" +import { Button } from "@/components/ui/button" +import { Input } from "@/components/ui/input" +import { Label } from "@/components/ui/label" + +interface UserAuthFormProps extends React.HTMLAttributes {} + +export function UserAuthForm({ className, ...props }: UserAuthFormProps) { + const [isLoading, setIsLoading] = React.useState(false) + + async function onSubmit(event: React.SyntheticEvent) { + event.preventDefault() + setIsLoading(true) + + setTimeout(() => { + setIsLoading(false) + }, 3000) + } + + return ( +
+
+
+
+ + + +
+
+ + +
+ +
+
+
+ ) +} diff --git a/src/collapsar/assets/js/context/CollapsarProvider.tsx b/src/collapsar/assets/js/context/CollapsarProvider.tsx index 7d0138a..7e8a84b 100644 --- a/src/collapsar/assets/js/context/CollapsarProvider.tsx +++ b/src/collapsar/assets/js/context/CollapsarProvider.tsx @@ -1,5 +1,6 @@ import { createContext, useEffect, useState } from "react"; import * as Fields from "@/components/fields"; +import axios from "axios"; interface renderFormFieldProps { component: string; @@ -8,13 +9,12 @@ interface renderFormFieldProps { } interface CollapsarContextProps { - renderFormField: (options: renderFormFieldProps) => JSX.Element; + renderFormField: (options: renderFormFieldProps) => JSX.Element | null; } export const CollapsarContext = createContext({} as CollapsarContextProps); const CollapsarProvider = ({ children }: any) => { - const renderFormField = ({component, field, renderForDisplay = false}: renderFormFieldProps) => { const FieldComponent = Fields[component as keyof typeof Fields]; @@ -26,10 +26,8 @@ const CollapsarProvider = ({ children }: any) => { return ( ); - } - return ( +
diff --git a/src/collapsar/assets/js/pages/Login.tsx b/src/collapsar/assets/js/pages/Login.tsx new file mode 100644 index 0000000..3c35bb0 --- /dev/null +++ b/src/collapsar/assets/js/pages/Login.tsx @@ -0,0 +1,32 @@ +import { ThemeProvider } from "@/components/theme-provider"; +import { UserAuthForm } from "@/components/user-auth-form"; + +export function Login() { + return ( + +
+
+ + Collapsar +
+ +
+
+

Sign in

+

+ {/* Enter your credentials below to continue. */} +

+
+ +

+ +

+
+
+
+ ); +} diff --git a/src/collapsar/assets/js/services/router.tsx b/src/collapsar/assets/js/services/router.tsx index cda5851..eb0e232 100644 --- a/src/collapsar/assets/js/services/router.tsx +++ b/src/collapsar/assets/js/services/router.tsx @@ -6,6 +6,7 @@ import { } from "react-router-dom"; import { ResourceEdit } from "@/pages/ResourceEdit"; import { ResourceShow } from "@/pages/ResourceShow"; +import { Login } from "@/pages/Login"; import axios from "axios"; // use proxy to remount component on resource change @@ -16,6 +17,10 @@ const ResourceIndexProxy = (props: any) => } const routes = [ + { + path: 'auth/login', + element: , + }, { path: '/', element: , diff --git a/src/collapsar/controllers/AuthController.py b/src/collapsar/controllers/AuthController.py new file mode 100644 index 0000000..ff2f17d --- /dev/null +++ b/src/collapsar/controllers/AuthController.py @@ -0,0 +1,24 @@ +"""A AuthController Module.""" +from masonite.controllers import Controller +from masonite.response import Response +from masonite.request import Request +from masonite.authentication import Auth + + +class AuthController(Controller): + """AuthController Controller Class.""" + + def login(self, request: Request, response: Response, auth: Auth): + """Handle login.""" + + if (auth.attempt(request.input("email"), request.input("password"))): + return response.redirect('/collapsar/') + + return response.redirect('/collapsar/auth/login') + + def logout(self, response: Response, auth: Auth): + """Handle logout.""" + + auth.logout() + + return response.redirect('/collapsar/auth/login') diff --git a/src/collapsar/routes/api.py b/src/collapsar/routes/api.py index 19633ec..22b1471 100644 --- a/src/collapsar/routes/api.py +++ b/src/collapsar/routes/api.py @@ -1,4 +1,5 @@ from masonite.routes import Route + from ..controllers.ResourceStoreController import ResourceStoreController from ..controllers.ResourceIndexController import ResourceIndexController from ..controllers.FieldsController import FieldsController @@ -6,6 +7,7 @@ from ..controllers.ResourceUpdateController import ResourceUpdateController from ..controllers.ResourceDeleteController import ResourceDeleteController + ROUTES = [ Route.group( [ @@ -18,5 +20,6 @@ Route.put("/@resource/", ResourceStoreController.handle), ], prefix="/collapsar-api", + middleware=["auth"] ) ] diff --git a/src/collapsar/routes/web.py b/src/collapsar/routes/web.py index 80414ce..7559263 100644 --- a/src/collapsar/routes/web.py +++ b/src/collapsar/routes/web.py @@ -1,13 +1,18 @@ from masonite.routes import Route from ..controllers.CollapsarController import CollapsarController +from ..controllers.AuthController import AuthController ROUTES = [ Route.group( [ + Route.get("/auth/login", CollapsarController.index).name('login'), + Route.post("/auth/login", AuthController.login), + Route.get("/auth/logout", AuthController.logout), + Route.get("/assets/app.js", CollapsarController.get_js), Route.get("/assets/style.css", CollapsarController.get_css), - Route.get('.*', CollapsarController.index) + Route.get('.*', CollapsarController.index).middleware('auth',) ], prefix="/collapsar", ) diff --git a/src/collapsar/templates/admin/base.html b/src/collapsar/templates/admin/base.html index 4e9fa3c..b8b8721 100644 --- a/src/collapsar/templates/admin/base.html +++ b/src/collapsar/templates/admin/base.html @@ -3,7 +3,7 @@ - + {% block header_title %}{% endblock header_title %} diff --git a/src/collapsar/templates/admin/index.html b/src/collapsar/templates/admin/index.html index f2707e3..3abc7be 100644 --- a/src/collapsar/templates/admin/index.html +++ b/src/collapsar/templates/admin/index.html @@ -7,7 +7,9 @@ window.Collapsar = { sidebar: { items: {{ dashboard_helper.get_resources_navigation() | json_encode() | safe}} - } + }, + token: document.head.querySelector('meta[name="csrf-token"]').content, + user: {% if auth.user %} {{ auth.user | json_encode() | safe }} {% else %} {{ "null" }} {% endif %} } {% endblock %} diff --git a/tests/integrations/app/middlewares/VerifyCsrfToken.py b/tests/integrations/app/middlewares/VerifyCsrfToken.py index 1a8eb61..246e7cc 100644 --- a/tests/integrations/app/middlewares/VerifyCsrfToken.py +++ b/tests/integrations/app/middlewares/VerifyCsrfToken.py @@ -4,5 +4,5 @@ class VerifyCsrfToken(Middleware): exempt = [ - '/collapsar/*', + '/collapsar-api/*', ] diff --git a/tests/integrations/app/models/User.py b/tests/integrations/app/models/User.py index 66e7e2f..54e6876 100644 --- a/tests/integrations/app/models/User.py +++ b/tests/integrations/app/models/User.py @@ -3,7 +3,6 @@ from masoniteorm.scopes import SoftDeletesMixin from masonite.authentication import Authenticates - class User(Model, SoftDeletesMixin, Authenticates): """User Model.""" diff --git a/tests/integrations/config/providers.py b/tests/integrations/config/providers.py index 7992e2f..96b47bf 100644 --- a/tests/integrations/config/providers.py +++ b/tests/integrations/config/providers.py @@ -45,7 +45,7 @@ AuthenticationProvider, ValidationProvider, AuthorizationProvider, - ORMProvider + ORMProvider, ] PROVIDERS += [CollapsarProvider]