From fc979330dc2c62a77acc1722282b7a5be086bbcb Mon Sep 17 00:00:00 2001 From: Henil Panchal Date: Thu, 11 May 2023 10:42:22 +0530 Subject: [PATCH 01/64] feat: add APIs for email verification --- flask/auth.py | 47 ++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 46 insertions(+), 1 deletion(-) diff --git a/flask/auth.py b/flask/auth.py index 77d26aaa..4aa5e34e 100644 --- a/flask/auth.py +++ b/flask/auth.py @@ -134,7 +134,7 @@ def signup(): if not registry_user: db.users.insert_one(user) - + # send_verify_email(email) #TODO: uncomment this line return ( jsonify( { @@ -266,3 +266,48 @@ def forgot_password(*email): jsonify({"message": "Password reset link sent to your email", "code": 200}), 200, ) + + +def send_verify_email(email): + + user = db.users.find_one({"email": email}) + + if not user: + return jsonify({"message": "User not found", "code": 404}), 404 + + uuid = generate_uuid() + db.users.update_one({"email": email}, {"$set": {"uuid": uuid}}) + + message = f"""\n + Dear {user['username']}, + + We received a request to verify your email. To verify your email, please copy paste the link below in a new browser window: + + {env_var['host']}/account/verify/{uuid} + + Thank you, + The Fortran-lang Team""" + + message = f'Subject: Verify email \nTo: {email}\n{message}' + + # sending the mail + smtp.sendmail(to_addrs=email, msg=message, from_addr=fortran_email) + + return ( + jsonify({"message": "verification link sent to your email", "code": 200}), + 200, + ) + +@app.route("/auth/verify-email", methods=["POST"]) +def verify_email(): + uuid = request.form.get("uuid") + + if not uuid: + return jsonify({"message": "Unauthorized", "code": 401}), 401 + + user = db.users.find_one({"uuid": uuid}) + + if not user: + return jsonify({"message": "User not found", "code": 404}), 404 + + return jsonify({"message": "Successfully Verified Email", "code": 200}), 200 \ No newline at end of file From 7a24e580f8dae56a85bba8cb5647b6ac21a34384 Mon Sep 17 00:00:00 2001 From: Henil Panchal Date: Thu, 11 May 2023 10:43:31 +0530 Subject: [PATCH 02/64] feat: add UI for email verification --- registry/src/App.js | 5 +++++ registry/src/pages/verifyemail.js | 37 +++++++++++++++++++++++++++++++ 2 files changed, 42 insertions(+) create mode 100644 registry/src/pages/verifyemail.js diff --git a/registry/src/App.js b/registry/src/App.js index 059c6f8a..d178802e 100644 --- a/registry/src/App.js +++ b/registry/src/App.js @@ -12,6 +12,7 @@ import NoPage from "./pages/404"; import UserPage from "./pages/user"; import PackagePage from "./pages/package"; import NamespaceForm from "./pages/createNamespace"; +import VerifyEmail from "./pages/verifyemail"; import NamespacePage from "./pages/namespace"; import AdminSection from "./pages/admin"; import ForgotPassword from "./pages/forgotpassword"; @@ -32,6 +33,10 @@ function App() { path="/account/reset-password/:uuid" element={} /> + } + /> } /> } /> } /> diff --git a/registry/src/pages/verifyemail.js b/registry/src/pages/verifyemail.js new file mode 100644 index 00000000..b6328d20 --- /dev/null +++ b/registry/src/pages/verifyemail.js @@ -0,0 +1,37 @@ +import React, { useState } from "react"; +import { useParams } from "react-router-dom"; +import { useSelector } from "react-redux"; +import { verify } from "../store/actions/verifyEmailActions"; +import { Link } from "react-router-dom"; +import Container from "react-bootstrap/Container"; + +const VerifyEmail = () => { + const { uuid } = useParams(); + const message = useSelector((state) => state.verifyemail.message); + const statuscode = useSelector((state) => state.verifyemail.statuscode); + + const handleSubmit = async (e) => { + e.preventDefault(); + dispatch(verify(uuid)); + }; + + return ( + +
+

Welcome to fpm Registry!

+ {message && + (statuscode !== 200 ? ( +

{message}

+ ) : ( +

{message}

+ ))} + +

+ Already have an account? Login +

+
+
+ ); +}; + +export default VerifyEmail; From 47079db621c7027a175f306e0270a598a03b7c77 Mon Sep 17 00:00:00 2001 From: Henil Panchal Date: Thu, 11 May 2023 10:53:08 +0530 Subject: [PATCH 03/64] feat: add reducers and actions for email verification --- registry/src/pages/verifyemail.js | 2 +- .../src/store/actions/verifyEmailActions.js | 52 +++++++++++++++++++ registry/src/store/reducers/rootReducer.js | 2 + .../src/store/reducers/verifyEmailReducer.js | 41 +++++++++++++++ 4 files changed, 96 insertions(+), 1 deletion(-) create mode 100644 registry/src/store/actions/verifyEmailActions.js create mode 100644 registry/src/store/reducers/verifyEmailReducer.js diff --git a/registry/src/pages/verifyemail.js b/registry/src/pages/verifyemail.js index b6328d20..9bc09e8e 100644 --- a/registry/src/pages/verifyemail.js +++ b/registry/src/pages/verifyemail.js @@ -19,13 +19,13 @@ const VerifyEmail = () => {

Welcome to fpm Registry!

+

Verify your email

{message && (statuscode !== 200 ? (

{message}

) : (

{message}

))} -

Already have an account? Login

diff --git a/registry/src/store/actions/verifyEmailActions.js b/registry/src/store/actions/verifyEmailActions.js new file mode 100644 index 00000000..7eac6036 --- /dev/null +++ b/registry/src/store/actions/verifyEmailActions.js @@ -0,0 +1,52 @@ +import axios from "axios"; + +export const VERIFY_REQUEST = "VERIFY_REQUEST"; + +export const VERIFY_REQUEST_SUCCESS = "VERIFY_REQUEST_SUCCESS"; +export const VERIFY_REQUEST_FAILURE = "VERIFY_REQUEST_FAILURE"; + +export const verify = (uuid) => async (dispatch) => { + // Make an api call to request to verify email + let formData = new FormData(); + + formData.append("uuid", uuid); + + try { + let result = await axios({ + method: "post", + url: `${process.env.REACT_APP_REGISTRY_API_URL}/auth/verify-email`, + data: formData, + headers: { + "Content-Type": "multipart/form-data", + }, + }); + + if (result.data.code === 200) { + dispatch({ + type: VERIFY_REQUEST_SUCCESS, + payload: { + statuscode: result.data.code, + message: result.data.message, + }, + }); + } else { + dispatch({ + type: VERIFY_REQUEST_FAILURE, + payload: { + statuscode: result.data.code, + message: result.data.message, + }, + }); + } + } catch (error) { + //on failure + // console.log(error); + dispatch({ + type: VERIFY_REQUEST_FAILURE, + payload: { + statuscode: error.response.data.code, + message: error.response.data.message, + }, + }); + } +}; \ No newline at end of file diff --git a/registry/src/store/reducers/rootReducer.js b/registry/src/store/reducers/rootReducer.js index 41f9d1aa..130e100d 100644 --- a/registry/src/store/reducers/rootReducer.js +++ b/registry/src/store/reducers/rootReducer.js @@ -13,6 +13,7 @@ import addRemoveMaintainerReducer from "./addRemoveMaintainerReducer"; import generateNamespaceTokenReducer from "./generateNamespaceTokenReducer"; import addRemoveNamespaceMaintainerReducer from "./namespaceMaintainersReducer"; import addRemoveNamespaceAdminReducer from "./namespaceAdminReducer"; +import verifyEmailReducer from "./verifyEmailReducer"; const rootReducer = combineReducers({ auth: authReducer, @@ -29,6 +30,7 @@ const rootReducer = combineReducers({ createNamespace: createNamespaceReducer, addRemoveNamespaceMaintainer: addRemoveNamespaceMaintainerReducer, addRemoveNamespaceAdmin: addRemoveNamespaceAdminReducer, + verifyemail: verifyEmailReducer, }); export default rootReducer; diff --git a/registry/src/store/reducers/verifyEmailReducer.js b/registry/src/store/reducers/verifyEmailReducer.js new file mode 100644 index 00000000..d8a208de --- /dev/null +++ b/registry/src/store/reducers/verifyEmailReducer.js @@ -0,0 +1,41 @@ +import { + VERIFY_REQUEST_SUCCESS, + VERIFY_REQUEST_FAILURE, + VERIFY_REQUEST, +} from "../actions/verifyEmailActions"; + +const initialState = { + statuscode: 0, + message: "", + isLoading: false, +}; + +const verifyEmailReducer = (state = initialState, action) => { + switch (action.type) { + case VERIFY_REQUEST_SUCCESS: + return { + ...state, + statuscode: action.payload.statuscode, + message: action.payload.message, + isLoading: false, + }; + + case VERIFY_REQUEST_FAILURE: + return { + ...state, + statuscode: action.payload.statuscode, + message: action.payload.message, + isLoading: false, + }; + + case VERIFY_REQUEST: + return { + ...state, + isLoading: true, + }; + default: + return state; + } +}; + +export default verifyEmailReducer; From de8a8be912e89e3b6a8fd49321b973fe78e4b129 Mon Sep 17 00:00:00 2001 From: Henil Panchal Date: Thu, 11 May 2023 10:58:35 +0530 Subject: [PATCH 04/64] fix: redundant variable --- registry/src/store/reducers/verifyEmailReducer.js | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/registry/src/store/reducers/verifyEmailReducer.js b/registry/src/store/reducers/verifyEmailReducer.js index d8a208de..41b6feff 100644 --- a/registry/src/store/reducers/verifyEmailReducer.js +++ b/registry/src/store/reducers/verifyEmailReducer.js @@ -7,7 +7,6 @@ import { const initialState = { statuscode: 0, message: "", - isLoading: false, }; const verifyEmailReducer = (state = initialState, action) => { @@ -17,7 +16,6 @@ const verifyEmailReducer = (state = initialState, action) => { ...state, statuscode: action.payload.statuscode, message: action.payload.message, - isLoading: false, }; case VERIFY_REQUEST_FAILURE: @@ -25,14 +23,13 @@ const verifyEmailReducer = (state = initialState, action) => { ...state, statuscode: action.payload.statuscode, message: action.payload.message, - isLoading: false, }; case VERIFY_REQUEST: return { ...state, - isLoading: true, }; + default: return state; } From 10931916339de285d87ee27e67b07560551be2b4 Mon Sep 17 00:00:00 2001 From: Henil Panchal Date: Thu, 11 May 2023 11:02:46 +0530 Subject: [PATCH 05/64] feat: add APIs for email change functionality --- flask/auth.py | 31 ++++++++++++++++++++++++++++++- 1 file changed, 30 insertions(+), 1 deletion(-) diff --git a/flask/auth.py b/flask/auth.py index 4aa5e34e..ec1774b7 100644 --- a/flask/auth.py +++ b/flask/auth.py @@ -310,4 +310,33 @@ def verify_email(): if not user: return jsonify({"message": "User not found", "code": 404}), 404 - return jsonify({"message": "Successfully Verified Email", "code": 200}), 200 \ No newline at end of file + return jsonify({"message": "Successfully Verified Email", "code": 200}), 200 + +@app.route("/auth/change-email", methods=["POST"]) +def change_email(): + uuid = request.form.get("uuid") + new_email = request.form.get("new_email") + + if not uuid: + return jsonify({"message": "Unauthorized", "code": 401}), 401 + + user = db.users.find_one({"uuid": uuid}) + + if not user: + return jsonify({"message": "User not found", "code": 404}), 404 + + if not new_email: + return jsonify({"message": "Please enter new email", "code": 400}), 400 + + used_email = db.users.find_one({"email": new_email}) + + if used_email: + return jsonify({"message": "Email already in use", "code": 400}), 400 + + db.users.update_one( + {"uuid": uuid}, + {"$set": {"email": new_email}}, + ) + send_verify_email(new_email) + + return jsonify({"message": "Email Id Successfully changed", "code": 200}), 200 \ No newline at end of file From d3b1b6e157962768c172113fd61a4fde1752cbf8 Mon Sep 17 00:00:00 2001 From: Henil Panchal Date: Thu, 11 May 2023 22:43:45 +0530 Subject: [PATCH 06/64] feat: fix bugs --- flask/auth.py | 2 +- registry/src/pages/admin.js | 2 +- registry/src/pages/verifyemail.js | 3 ++- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/flask/auth.py b/flask/auth.py index ec1774b7..1185b7fe 100644 --- a/flask/auth.py +++ b/flask/auth.py @@ -339,4 +339,4 @@ def change_email(): ) send_verify_email(new_email) - return jsonify({"message": "Email Id Successfully changed", "code": 200}), 200 \ No newline at end of file + return jsonify({"message": "Email id Successfully changed", "code": 200}), 200 \ No newline at end of file diff --git a/registry/src/pages/admin.js b/registry/src/pages/admin.js index f8991df0..8bc12c4a 100644 --- a/registry/src/pages/admin.js +++ b/registry/src/pages/admin.js @@ -118,7 +118,7 @@ const AdminSection = () => { // const [newPassword, setNewPassword] = useState(""); useEffect(() => { - dispatch(adminAuth()); + dispatch(adminAuth(uuid)); if (!isAdmin) { navigate("/404"); } diff --git a/registry/src/pages/verifyemail.js b/registry/src/pages/verifyemail.js index 9bc09e8e..02558e88 100644 --- a/registry/src/pages/verifyemail.js +++ b/registry/src/pages/verifyemail.js @@ -1,12 +1,13 @@ import React, { useState } from "react"; import { useParams } from "react-router-dom"; -import { useSelector } from "react-redux"; +import { useDispatch, useSelector } from "react-redux"; import { verify } from "../store/actions/verifyEmailActions"; import { Link } from "react-router-dom"; import Container from "react-bootstrap/Container"; const VerifyEmail = () => { const { uuid } = useParams(); + const dispatch = useDispatch(); const message = useSelector((state) => state.verifyemail.message); const statuscode = useSelector((state) => state.verifyemail.statuscode); From 815699c3b77cc1c5a894220b3790c5f11ad409ae Mon Sep 17 00:00:00 2001 From: Henil Panchal Date: Tue, 16 May 2023 10:54:05 +0530 Subject: [PATCH 07/64] fix: #34 --- registry/src/pages/login.js | 9 ++++++++- registry/src/pages/register.js | 9 ++++++++- registry/src/store/actions/authActions.js | 8 ++++++++ registry/src/store/reducers/authReducer.js | 13 +++++++++++++ 4 files changed, 37 insertions(+), 2 deletions(-) diff --git a/registry/src/pages/login.js b/registry/src/pages/login.js index 26e1a159..89f85669 100644 --- a/registry/src/pages/login.js +++ b/registry/src/pages/login.js @@ -14,6 +14,7 @@ const Login = () => { const isAuthenticated = useSelector((state) => state.auth.isAuthenticated); const errorMessage = useSelector((state) => state.auth.error); + const isLoading = useSelector((state) => state.auth.isLoading); useEffect(() => { if (isAuthenticated) { @@ -49,7 +50,7 @@ const Login = () => { } }; - return ( + return !isLoading ? (

Welcome to fpm Registry!

@@ -84,6 +85,12 @@ const Login = () => {

+ ) : ( + + + Loading... + + ); }; diff --git a/registry/src/pages/register.js b/registry/src/pages/register.js index 82ce99ac..9bc73502 100644 --- a/registry/src/pages/register.js +++ b/registry/src/pages/register.js @@ -14,6 +14,7 @@ const Register = () => { const dispatch = useDispatch(); const isAuthenticated = useSelector((state) => state.auth.isAuthenticated); + const isLoading = useSelector((state) => state.auth.isLoading); const errorMessage = useSelector((state) => state.auth.error); const handleSubmit = async (e) => { @@ -54,7 +55,7 @@ const Register = () => { return Object.keys(errors).length === 0; }; - return ( + return !isLoading ? (

Welcome to fpm Registry!

@@ -98,8 +99,14 @@ const Register = () => { Forgot password

+
) : ( + + + Loading... + ); + }; export default Register; diff --git a/registry/src/store/actions/authActions.js b/registry/src/store/actions/authActions.js index 91509042..43083b09 100644 --- a/registry/src/store/actions/authActions.js +++ b/registry/src/store/actions/authActions.js @@ -1,12 +1,16 @@ import axios from "axios"; export const RESET_ERROR_MESSAGE = "RESET_ERROR_MESSAGE"; +export const LOGIN_REQUEST = "LOGIN_REQUEST"; export const LOGIN_SUCCESS = "LOGIN_SUCCESS"; export const LOGIN_FAILURE = "LOGIN_FAILURE"; export const login = (user_identifier, password) => async (dispatch) => { // Make an api call to login + dispatch({ + type: LOGIN_REQUEST, + }); let formData = new FormData(); formData.append("user_identifier", user_identifier); @@ -91,8 +95,12 @@ export const logout = (uuid) => async (dispatch) => { export const SIGNUP_SUCCESS = "SIGNUP_SUCCESS"; export const SIGNUP_FAILURE = "SIGNUP_FAILURE"; +export const SIGNUP_REQUEST = "SIGNUP_REQUEST"; export const signup = (username, email, password) => async (dispatch) => { + dispatch({ + type: SIGNUP_REQUEST, + }); let formData = new FormData(); formData.append("username", username); diff --git a/registry/src/store/reducers/authReducer.js b/registry/src/store/reducers/authReducer.js index e5acb069..2b06374a 100644 --- a/registry/src/store/reducers/authReducer.js +++ b/registry/src/store/reducers/authReducer.js @@ -6,6 +6,8 @@ import { LOGOUT_SUCCESS, LOGOUT_FAILURE, RESET_ERROR_MESSAGE, + LOGIN_REQUEST, + SIGNUP_REQUEST } from "../actions/authActions"; const initialState = { @@ -13,10 +15,21 @@ const initialState = { uuid: null, error: null, username: null, + isLoading: false, }; const authReducer = (state = initialState, action) => { switch (action.type) { + case LOGIN_REQUEST: + return { + ...state, + isLoading: True, + }; + case SIGNUP_REQUEST: + return { + ...state, + isLoading: True, + }; case LOGIN_SUCCESS: return { ...state, From 5ba8f059103634fb307643005d24401cdd3f24d1 Mon Sep 17 00:00:00 2001 From: Henil Panchal Date: Tue, 16 May 2023 11:03:21 +0530 Subject: [PATCH 08/64] add readme for the registry --- README.md | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/README.md b/README.md index f9cf96cf..d4256bbe 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,21 @@ # registry Registry for Fortran package manager +Currently for the testing phase : +1. backend APIs are hosted at: http://registry-apis.vercel.app/ +2. frontend is hosted at: https://registry-frontend.vercel.app/ +3. Documentation for the APIs are available at: https://registry-apis.vercel.app/apidocs/ + +*** Please note: the current registry is a playground: its database will be fully deleted once its functionality is established. Please do not use it for production yet! more information will follow then. **** + +The fpm release [0.8.2](https://fortran-lang.discourse.group/t/fpm-version-0-8-2-released-centralized-registry-playground/5792) introduces fpm support for uploading packages to the fpm-registry server directly from the command-line interface, via +``` +fpm publish --token +``` + +fpm will now interact with a web interface that will help to manage the namespaces & packages. + + ## Python Flask app with Nginx and Mongo database Project structure: From f9047313124397a8dc539d0dd4bb9d6dff214008 Mon Sep 17 00:00:00 2001 From: Henil Panchal Date: Tue, 16 May 2023 11:12:26 +0530 Subject: [PATCH 09/64] add: help and fix rreducers --- registry/src/pages/help.js | 14 +++++++++++++- registry/src/pages/login.js | 1 + registry/src/pages/register.js | 1 + registry/src/store/reducers/authReducer.js | 4 ++-- 4 files changed, 17 insertions(+), 3 deletions(-) diff --git a/registry/src/pages/help.js b/registry/src/pages/help.js index b36fddbe..ecd35d9a 100644 --- a/registry/src/pages/help.js +++ b/registry/src/pages/help.js @@ -1,7 +1,19 @@ import React from "react"; const Help = () => { - return

help

; + return (
+

First of all, you will need to register a user account:

+

For uploading your package to the registry, you will have to step by step follow the following points:

+

Register yourself as a user. You will require a unique username, email & password to set up your account.

+

For uploading a package from fpm, you will have to first create a namespace. A namespace represents a collection of packages. Each package is published under a namespace in order to avoid collision of same package names. Namespace names will be unique always. + Now, that you will have created a namespace with a unique name and a nice description. You can go to dashboard by from the dropdown options in the Navigation bar on top. + In the dashboard, you can see the namespace that has been created by you. You can now generate a token for this namespace.

+

Use this token to upload packages from the fpm using the CLI:

+ fpm publish --token token-here +

After completing the above steps, you will receive a response in the fpm command line interface whether your upload was successful or not. + + If your upload was successful, you can now again go to the registry frontend and check the dashboard. It should display the package uploaded by you. + You can now Add/Remove maintainers to your package. Mantainers have the rights to operate on the same package.

); }; export default Help; diff --git a/registry/src/pages/login.js b/registry/src/pages/login.js index 89f85669..0a3e216f 100644 --- a/registry/src/pages/login.js +++ b/registry/src/pages/login.js @@ -3,6 +3,7 @@ import { useNavigate } from "react-router-dom"; import { useDispatch, useSelector } from "react-redux"; import { login, resetErrorMessage } from "../store/actions/authActions"; import { Link } from "react-router-dom"; +import Spinner from "react-bootstrap/Spinner"; import Container from "react-bootstrap/Container"; const Login = () => { diff --git a/registry/src/pages/register.js b/registry/src/pages/register.js index 9bc73502..43bdf510 100644 --- a/registry/src/pages/register.js +++ b/registry/src/pages/register.js @@ -3,6 +3,7 @@ import { useNavigate } from "react-router-dom"; import { useDispatch, useSelector } from "react-redux"; import { signup, resetErrorMessage } from "../store/actions/authActions"; import { Link } from "react-router-dom"; +import Spinner from "react-bootstrap/Spinner"; import Container from "react-bootstrap/Container"; const Register = () => { diff --git a/registry/src/store/reducers/authReducer.js b/registry/src/store/reducers/authReducer.js index 2b06374a..84c423b3 100644 --- a/registry/src/store/reducers/authReducer.js +++ b/registry/src/store/reducers/authReducer.js @@ -23,12 +23,12 @@ const authReducer = (state = initialState, action) => { case LOGIN_REQUEST: return { ...state, - isLoading: True, + isLoading: true, }; case SIGNUP_REQUEST: return { ...state, - isLoading: True, + isLoading: true, }; case LOGIN_SUCCESS: return { From c96154a787502b87a8cdb4178384a2b39d9f187c Mon Sep 17 00:00:00 2001 From: Henil Panchal Date: Tue, 16 May 2023 11:22:02 +0530 Subject: [PATCH 10/64] fix: #35 --- registry/src/pages/dashboard.js | 3 ++- registry/src/store/reducers/authReducer.js | 4 ++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/registry/src/pages/dashboard.js b/registry/src/pages/dashboard.js index 9e050d39..4961dff8 100644 --- a/registry/src/pages/dashboard.js +++ b/registry/src/pages/dashboard.js @@ -7,6 +7,7 @@ import Col from "react-bootstrap/Col"; import Row from "react-bootstrap/Row"; import Spinner from "react-bootstrap/Spinner"; import Container from "react-bootstrap/Container"; +import { Link } from "react-router-dom"; import AddMaintainerFormDialog from "./addMaintainerDialogForm"; import RemoveMaintainerFormDialog from "./removeMaintainerDialogForm"; import GenerateNamespaceTokenDialogForm from "./generateNamespaceTokenDialogForm"; @@ -155,7 +156,7 @@ const Dashboard = () => { function Namespaces() { return namespaces.length === 0 ? (
- You are not a maintainer of any namespace yet. + You are not a maintainer of any namespace yet. Add a namespace here
) : ( diff --git a/registry/src/store/reducers/authReducer.js b/registry/src/store/reducers/authReducer.js index 84c423b3..f89aab1f 100644 --- a/registry/src/store/reducers/authReducer.js +++ b/registry/src/store/reducers/authReducer.js @@ -36,6 +36,7 @@ const authReducer = (state = initialState, action) => { isAuthenticated: true, uuid: action.payload.uuid, username: action.payload.username, + isLoading: false, }; case LOGIN_FAILURE: @@ -43,6 +44,7 @@ const authReducer = (state = initialState, action) => { ...state, isAuthenticated: false, error: action.payload.error, + isLoading: false, }; case LOGOUT_SUCCESS: @@ -66,6 +68,7 @@ const authReducer = (state = initialState, action) => { isAuthenticated: true, uuid: action.payload.uuid, username: action.payload.username, + isLoading: false, }; case SIGNUP_FAILURE: @@ -73,6 +76,7 @@ const authReducer = (state = initialState, action) => { ...state, isAuthenticated: false, error: action.payload.error, + isLoading: false, }; case RESET_ERROR_MESSAGE: return { From d6fa8e088e64176836e37b8c97a8698cd0911d60 Mon Sep 17 00:00:00 2001 From: Henil Panchal Date: Tue, 16 May 2023 11:23:23 +0530 Subject: [PATCH 11/64] token duration --- registry/src/pages/help.js | 1 + 1 file changed, 1 insertion(+) diff --git a/registry/src/pages/help.js b/registry/src/pages/help.js index ecd35d9a..8c4eb84d 100644 --- a/registry/src/pages/help.js +++ b/registry/src/pages/help.js @@ -8,6 +8,7 @@ const Help = () => {

For uploading a package from fpm, you will have to first create a namespace. A namespace represents a collection of packages. Each package is published under a namespace in order to avoid collision of same package names. Namespace names will be unique always. Now, that you will have created a namespace with a unique name and a nice description. You can go to dashboard by from the dropdown options in the Navigation bar on top. In the dashboard, you can see the namespace that has been created by you. You can now generate a token for this namespace.

+

This token will be valid for 1 week , but you can always generate a new token.

Use this token to upload packages from the fpm using the CLI:

fpm publish --token token-here

After completing the above steps, you will receive a response in the fpm command line interface whether your upload was successful or not. From 0024731bc27d544938031d391a43cd03a9536b3a Mon Sep 17 00:00:00 2001 From: Henil Panchal Date: Tue, 16 May 2023 12:02:12 +0530 Subject: [PATCH 12/64] add the read description and homepage and repository --- flask/packages.py | 22 ++++++++++++++++++++-- registry/src/pages/package.js | 5 ++--- 2 files changed, 22 insertions(+), 5 deletions(-) diff --git a/flask/packages.py b/flask/packages.py index 67d21b7e..6b7f2089 100644 --- a/flask/packages.py +++ b/flask/packages.py @@ -7,6 +7,8 @@ from datetime import datetime, timedelta from auth import generate_uuid from app import swagger +import zipfile +import toml from flasgger.utils import swag_from from urllib.parse import unquote import math @@ -184,6 +186,10 @@ def upload(): file_object_id = file_storage.put(tarball, content_type="application/gzip", filename=tarball_name) + + # Extract the package metadata from the tarball's fpm.toml file. + package_data = extract_fpm_toml(tarball_name) + # TODO: Uncomment this when the package validation is enabled # validate the package @@ -197,7 +203,9 @@ def upload(): package_obj = { "name": package_name, "namespace": namespace_doc["_id"], - "description": "Sample Test description", + "description": package_data["description"], + "homepage": package_data["homepage"], + "repository": package_data["repository"], "license": package_license, "createdAt": datetime.utcnow(), "updatedAt": datetime.utcnow(), @@ -416,6 +424,8 @@ def get_package(namespace_name, package_name): "name": package["name"], "namespace": namespace["namespace"], "description": package["description"], + "repository": package["repository"], + "homepage": package["homepage"], "latest_version_data": { "dependencies": package["versions"][-1]["dependencies"], "version": package["versions"][-1]["version"], @@ -606,4 +616,12 @@ def checkUserUnauthorized(user_id, package_namespace): admins_id_list = [str(obj_id) for obj_id in package_namespace["admins"]] maintainers_id_list = [str(obj_id) for obj_id in package_namespace["maintainers"]] str_user_id = str(user_id) - return str_user_id not in admins_id_list and str_user_id not in maintainers_id_list \ No newline at end of file + return str_user_id not in admins_id_list and str_user_id not in maintainers_id_list + +def extract_fpm_toml(file_obj): + with zipfile.ZipFile(file_obj, 'r') as zip_ref: + zip_ref.extract("fpm.toml") + with open("fpm.toml", 'r') as file: + data = toml.load(file) + + return data \ No newline at end of file diff --git a/registry/src/pages/package.js b/registry/src/pages/package.js index f446d44b..e410811c 100644 --- a/registry/src/pages/package.js +++ b/registry/src/pages/package.js @@ -166,16 +166,15 @@ export default PackagePage; const sideBar = (data) => { return ( - {/* TODO: update Package API for url,website,maintainers */}

Install

fpm install {data.namespace}/{data.name}

Repository

- {/* { data.url } */} + { data.repository }

Homepage

- {/* { data.website } */} + { data.homepage }

License

{data.license} From f4ddeb8967788acec9e1bef079ddd0ad3b6528c6 Mon Sep 17 00:00:00 2001 From: Henil Panchal Date: Tue, 16 May 2023 12:02:38 +0530 Subject: [PATCH 13/64] nfeat: update packages --- flask/requirements.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/flask/requirements.txt b/flask/requirements.txt index 6a3de376..095b0f95 100755 --- a/flask/requirements.txt +++ b/flask/requirements.txt @@ -8,3 +8,4 @@ flasgger license-expression semantic-version docker +toml \ No newline at end of file From 2cac330516b83b9a5a685950a4606fb4cd667c6b Mon Sep 17 00:00:00 2001 From: Henil Panchal Date: Tue, 16 May 2023 12:32:34 +0530 Subject: [PATCH 14/64] add : css to help page --- registry/src/pages/help.js | 75 +++++++++++++++++++++++++++++++------- 1 file changed, 61 insertions(+), 14 deletions(-) diff --git a/registry/src/pages/help.js b/registry/src/pages/help.js index 8c4eb84d..619422a4 100644 --- a/registry/src/pages/help.js +++ b/registry/src/pages/help.js @@ -1,20 +1,67 @@ import React from "react"; const Help = () => { - return (
-

First of all, you will need to register a user account:

-

For uploading your package to the registry, you will have to step by step follow the following points:

-

Register yourself as a user. You will require a unique username, email & password to set up your account.

-

For uploading a package from fpm, you will have to first create a namespace. A namespace represents a collection of packages. Each package is published under a namespace in order to avoid collision of same package names. Namespace names will be unique always. - Now, that you will have created a namespace with a unique name and a nice description. You can go to dashboard by from the dropdown options in the Navigation bar on top. - In the dashboard, you can see the namespace that has been created by you. You can now generate a token for this namespace.

-

This token will be valid for 1 week , but you can always generate a new token.

-

Use this token to upload packages from the fpm using the CLI:

- fpm publish --token token-here -

After completing the above steps, you will receive a response in the fpm command line interface whether your upload was successful or not. - - If your upload was successful, you can now again go to the registry frontend and check the dashboard. It should display the package uploaded by you. - You can now Add/Remove maintainers to your package. Mantainers have the rights to operate on the same package.

); + const containerStyle = { + textAlign: "left", + fontFamily: "Arial, sans-serif", + fontSize: "18px", + lineHeight: "1.5", + paddingLeft: "20px", + paddingTop: "20px", + paddinBottom: "20px", + color: "#333", + }; + + const codeStyle = { + backgroundColor: "#f5f5f5", + padding: "5px", + fontSize: "18px", + fontFamily: "Courier, monospace", + }; + + return ( +
+

Help

+

+
First of all, you will need to register a user account:
+
+ For uploading your package to the registry, you will have to step by + step follow the following points: +
+
+ Register yourself as a user. You will require a unique username, email & + password to set up your account. +
+

+
+ For uploading a package from fpm, you will have to first create a + namespace. A namespace represents a collection of packages. Each package + is published under a namespace in order to avoid collision of same + package names. Namespace names will be unique always. Now, that you will + have created a namespace with a unique name and a nice description. You + can go to dashboard by from the dropdown options in the Navigation bar + on top.

In the dashboard, you can see the namespace that has + been created by you. You can now generate a token for this namespace. +
+

+
+ This token will be valid for 1 week , but you can always generate a new + token. +
+
Use this token to upload packages from the fpm using the CLI:
+

+ fpm publish --token token-here +

+
+ After completing the above steps, you will receive a response in the fpm + command line interface whether your upload was successful or not. If + your upload was successful, you can now again go to the registry + frontend and check the dashboard. It should display the package uploaded + by you. You can now Add/Remove maintainers to your package. Mantainers + have the rights to operate on the same package. +
+
+ ); }; export default Help; From 7050da29d3e94697b99db417cb048e34881c2b3e Mon Sep 17 00:00:00 2001 From: Henil Panchal Date: Tue, 16 May 2023 12:44:02 +0530 Subject: [PATCH 15/64] update: readme --- README.md | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index d4256bbe..e8ad6c8b 100644 --- a/README.md +++ b/README.md @@ -1,14 +1,14 @@ -# registry -Registry for Fortran package manager +# Registry for Fortran package manager Currently for the testing phase : 1. backend APIs are hosted at: http://registry-apis.vercel.app/ 2. frontend is hosted at: https://registry-frontend.vercel.app/ 3. Documentation for the APIs are available at: https://registry-apis.vercel.app/apidocs/ -*** Please note: the current registry is a playground: its database will be fully deleted once its functionality is established. Please do not use it for production yet! more information will follow then. **** +**Please note: the current registry is a playground: its database will be fully deleted once its functionality is established. Please do not use it for production yet! more information will follow then.** The fpm release [0.8.2](https://fortran-lang.discourse.group/t/fpm-version-0-8-2-released-centralized-registry-playground/5792) introduces fpm support for uploading packages to the fpm-registry server directly from the command-line interface, via + ``` fpm publish --token ``` @@ -16,9 +16,9 @@ fpm publish --token fpm will now interact with a web interface that will help to manage the namespaces & packages. -## Python Flask app with Nginx and Mongo database +## fpm - registry : Python Flask app with Nginx and Mongo database -Project structure: +backend Project structure: ``` . ├── compose.yaml @@ -35,7 +35,7 @@ Project structure: ``` -## Deploy with docker compose +## Instructions for Deploy with docker compose ``` $ sudo chmod 666 /var/run/docker.sock (for root access) @@ -53,9 +53,9 @@ Hello world, Mongo Flask set MONGO_URI=MONGO_DB_ATLAS_URL (in .env file in flask directory) The MONGO_URI must be set in the environment (or, alternatively, in the .env file in the flask directory) to the URL value of the MongoDB to use. For example,If deploying to production, MONGO_URI should be set to mongo container address. set the following env variables in the .env file in the flask folder: - - SALT=MYSALT - - MONGO_URI - - MONGO_DB_NAME + - SALT + - MONGO_URI + - MONGO_DB_NAME - SUDO_PASSWORD - MONGO_USER_NAME - MONGO_PASSWORD From ed56575380e0c799eaadf3bcf6bb943428357a95 Mon Sep 17 00:00:00 2001 From: Henil Panchal Date: Fri, 19 May 2023 21:12:11 +0530 Subject: [PATCH 16/64] fix: fix admin page bugs --- registry/src/pages/admin.js | 677 ++++++++++++++---------------------- 1 file changed, 263 insertions(+), 414 deletions(-) diff --git a/registry/src/pages/admin.js b/registry/src/pages/admin.js index 8bc12c4a..5b91b7a5 100644 --- a/registry/src/pages/admin.js +++ b/registry/src/pages/admin.js @@ -1,15 +1,5 @@ import React, { useEffect, useState } from "react"; import { Container } from "react-bootstrap"; -import { useDispatch, useSelector } from "react-redux"; -import { useNavigate } from "react-router-dom"; -import { - adminAuth, - deleteUser, - deleteNamespace, - deletePackage, - deleteRelease, - deprecatePackage, -} from "../store/actions/adminActions"; import { MDBBtn, MDBModal, @@ -21,536 +11,395 @@ import { MDBModalFooter, MDBIcon, } from "mdb-react-ui-kit"; - -const isEmpty = (...values) => { - return values.some((value) => value === ""); -}; +import { useDispatch, useSelector } from "react-redux"; +import { useNavigate } from "react-router-dom"; +import { + adminAuth, + deleteUser, + deleteNamespace, + deletePackage, + deleteRelease, + deprecatePackage, +} from "../store/actions/adminActions"; const AdminSection = () => { - const [deprecateModal, setdeprecateModal] = useState(false); const uuid = useSelector((state) => state.auth.uuid); const dispatch = useDispatch(); const navigate = useNavigate(); const message = useSelector((state) => state.admin.message); const statuscode = useSelector((state) => state.admin.statuscode); const isAdmin = useSelector((state) => state.admin.isAdmin); - const toggleShowDeprecateModal = () => { - if (!isEmpty(deprecatePackageNamespaceName, deprecatepackageName)) { - setdeprecateModal(!deprecateModal); - } else { - toggleShowemptyModal(); + + useEffect(() => { + dispatch(adminAuth(uuid)); + console.log("adminAuth"); + console.log(isAdmin); + if (!isAdmin) { + navigate("/404"); } - }; - const [deprecatepackageName, setdeprecatepackageName] = useState(""); + }, [isAdmin]); - const [deprecatePackageNamespaceName, setdeprecatePackageNamespaceName] = - useState(""); + useEffect(() => { + if (statuscode !=null) { + toggleShowModal(); + } + }, [statuscode]); - const [emptyModal, setemptyModal] = useState(false); + const [formData, setFormData] = useState({ + namespaceName: "", + packageName: "", + releaseName: "", + userName: "", + newPassword: "", + }); - const [messageModal, setmessageModal] = useState(false); - const toggleShowmessageModal = () => setmessageModal(!messageModal); - const toggleShowemptyModal = () => setemptyModal(!emptyModal); + const [modalData, setModalData] = useState({ + showModal: false, + modalTitle: "", + modalMessage: "", + modalAction: null, + }); - const [deleteNamespaceModal, setdeleteNamespaceModal] = useState(false); - const toggleShowDeleteNamespaceModal = () => { - if (!isEmpty(deletenamespaceName)) { - setdeleteNamespaceModal(!deleteNamespaceModal); - } else { - toggleShowemptyModal(); - } + const handleInputChange = (e) => { + const { name, value } = e.target; + setFormData({ ...formData, [name]: value }); }; - const [deletenamespaceName, setdeletenamespaceName] = useState(""); + const openModal = (title, message, action) => { + setModalData({ + showModal: true, + modalTitle: title, + modalMessage: message, + modalAction: action, + }); + }; - const [deletePackageModal, setdeletePackageModal] = useState(false); - const toggleShowDeletePackageModal = () => { - if (!isEmpty(deletepackagenamespaceName, deletepackageName)) { - setdeletePackageModal(!deletePackageModal); - } else { - toggleShowemptyModal(); - } + const toggleShowModal = () => { + setModalData({ ...modalData, showModal: !modalData.showModal }); }; - const [deletepackageName, setdeletepackageName] = useState(""); - const [deletepackagenamespaceName, setdeletepackagenamespaceName] = - useState(""); - const [deleteUserModal, setdeleteUserModal] = useState(false); - const toggleShowDeleteUserModal = () => { - if (!isEmpty(deleteuserName)) { - setdeleteUserModal(!deleteUserModal); - } else { - toggleShowemptyModal(); + const handleAction = () => { + if (modalData.modalAction) { + modalData.modalAction(); } + toggleShowModal(); }; - const [deleteuserName, setdeleteuserName] = useState(""); - - const [deleteReleaseModal, setdeleteReleaseModal] = useState(false); - const toggleShowDeleteReleaseModal = () => { - if ( - !isEmpty( - deletereleasenamespaceName, - deletereleasepackageName, - deletereleaseName + const handleDeletePackage = () => { + openModal( + "Delete Package", + `You will not be able to recover ${formData.namespaceName}/${formData.packageName} package after you delete it.`, + dispatch( + deletePackage(formData.namespaceName, formData.packageName, uuid) ) - ) { - setdeleteReleaseModal(!deleteReleaseModal); - } else { - toggleShowemptyModal(); - } - }; - const [deletereleasepackageName, setdeletereleasepackageName] = useState(""); - const [deletereleasenamespaceName, setdeletereleasenamespaceName] = - useState(""); - const [deletereleaseName, setdeletereleaseName] = useState(""); + ); - // const [changePasswordModal, setchangePasswordModal] = useState(false); - // const toggleShowChangePasswordModal = () => { - // if (!isEmpty(userName, newPassword)) { - // setchangePasswordModal(!changePasswordModal); - // } else { - // toggleShowemptyModal(); - // } - // }; + console.log( + "Deleting package:", + formData.namespaceName, + formData.packageName + ); - // const [userName, setUserName] = useState(""); - // const [newPassword, setNewPassword] = useState(""); + // clear the form data + setFormData({ + namespaceName: "", + packageName: "", + }); + }; - useEffect(() => { - dispatch(adminAuth(uuid)); - if (!isAdmin) { - navigate("/404"); - } - }, [isAdmin]); + const handleDeleteRelease = () => { + openModal( + "Delete Release", + `You will not be able to recover ${formData.namespaceName}/${formData.packageName}/${formData.releaseName} release after you delete it.`, + deleteRelease + ); - useEffect(() => { - toggleShowmessageModal(); - }, [message]); + console.log( + "Deleting release:", + formData.namespaceName, + formData.packageName, + formData.releaseName + ); - const handleDeprecatePackage = () => { dispatch( - deprecatePackage( - deprecatePackageNamespaceName, - deprecatepackageName, + deleteRelease( + formData.namespaceName, + formData.packageName, + formData.releaseName, uuid ) ); - setdeprecatePackageNamespaceName(""); - setdeprecatepackageName(""); - }; - const handleDeleteNamespace = () => { - dispatch(deleteNamespace(deletenamespaceName, uuid)); - setdeletenamespaceName(""); + // clear the form data + setFormData({ + namespaceName: "", + packageName: "", + releaseName: "", + }); }; - const handleDeletePackage = () => { - dispatch( - deletePackage(deletepackagenamespaceName, deletepackageName, uuid) + const handleDeleteUser = () => { + openModal( + "Delete User", + `You will not be able to recover ${formData.userName} user after you delete it.`, + dispatch(deleteUser(formData.userName, uuid)) ); - setdeletepackagenamespaceName(""); - setdeletepackageName(""); + console.log("Deleting user:", formData.userName); + + // setModalData({ + // showModal: false, + // }); + + setModalData({ + showModal: true, + modalTitle: statuscode + " Status Code", + modalMessage: message, + modalAction: toggleShowModal(), + }); + + // clear the form data + // openModal(statuscode + " Status Code", message, toggleShowModal()); + setFormData({ + userName: "", + }); }; - const handleDeleteUser = () => { - dispatch(deleteUser(deleteuserName, uuid)); - setdeleteuserName(""); + const handleDeleteNamespace = () => { + openModal( + "Delete Namespace", + `You will not be able to recover ${formData.namespaceName} namespace after you delete it.`, + dispatch(deleteNamespace(formData.namespaceName, uuid)) + ); + console.log("Deleting namespace:", formData.namespaceName); + dispatch(deleteNamespace(formData.namespaceName, uuid)); + // clear the form data + setFormData({ + namespaceName: "", + }); }; - const handleDeleteRelease = () => { - dispatch( - deleteRelease( - deletereleasenamespaceName, - deletereleasepackageName, - deletereleaseName, - uuid + const handleDeprecatePackage = () => { + openModal( + "Delete Package", + `You will not be able to recover ${formData.namespaceName}/${formData.packageName} package after you delete it.`, + dispatch( + deprecatePackage(formData.namespaceName, formData.packageName, uuid) ) ); - setdeletereleasenamespaceName(""); - setdeletereleasepackageName(""); - setdeletereleaseName(""); + console.log( + "Deprecating package:", + formData.namespaceName, + formData.packageName + ); + dispatch( + deprecatePackage(formData.namespaceName, formData.packageName, uuid) + ); + // clear the form data + setFormData({ + namespaceName: "", + packageName: "", + }); }; - const handleChangePassword = () => { - // dispatch(adminAuth(userName, newPassword)); - // setUserName(""); - // setNewPassword(""); + const changePassword = () => { + console.log("Changing password for user:", formData.userName); + // Add the logic to change the password + // clear the form data + setFormData({ + userName: "", + newPassword: "", + }); }; return ( - - - - - - Empty Inputs - - - - You must fill all the - fields. - - - - Close - - - - - - - - - - {statuscode} status - - - - - {message} - - - - Close - - - - - + +

Admin Settings

-

Deprecate package release

+

Delete package

setdeprecatePackageNamespaceName(e.target.value)} + name="namespaceName" + value={formData.namespaceName} + onChange={handleInputChange} style={{ width: 300 }} /> setdeprecatepackageName(e.target.value)} + name="packageName" + value={formData.packageName} + onChange={handleInputChange} style={{ width: 300 }} />

- - Deprecate Package + + Delete Package - - - - - Deprecate Package - - - - You will not be able - to recover {deprecatePackageNamespaceName}/ - {deprecatepackageName} package after you deprecate it. - - - - Close - - - Deprecate Package - - - - -
-

Delete namespace

+

+

Delete package version

setdeletenamespaceName(e.target.value)} + name="namespaceName" + value={formData.namespaceName} + onChange={handleInputChange} + style={{ width: 300 }} + /> + +

- - Delete Namespace + + Delete Release - - - - - Delete Namespace - - - - You will not be able - to recover {deletenamespaceName} Namespace after you delete it. - - - - Close - - - Delete Namespace - - - - -
-

Delete package

+

+

Deprecate package

setdeletepackagenamespaceName(e.target.value)} + name="namespaceName" + value={formData.namespaceName} + onChange={handleInputChange} style={{ width: 300 }} /> setdeletepackageName(e.target.value)} + name="packageName" + value={formData.packageName} + onChange={handleInputChange} style={{ width: 300 }} />

- + Deprecate Package - - - - - Delete Package - - - - You will not be able - to recover {deletepackagenamespaceName}/{deletepackageName}{" "} - package after you delete it. - - - - Close - - Delete Package - - - -
-

Delete user

+

+

Delete Namespace

setdeleteuserName(e.target.value)} + placeholder="Namespace Name" + name="namespaceName" + value={formData.namespaceName} + onChange={handleInputChange} style={{ width: 300 }} />

- - Delete User + + Delete Namespace - - - - - Delete User - - - - You will not be able - to recover {deleteuserName} user after you delete it. - - - - Close - - Delete User - - - -
-

Delete release

+

+

Delete user

setdeletereleasepackageName(e.target.value)} - style={{ width: 300 }} - /> - setdeletereleasenamespaceName(e.target.value)} - style={{ width: 300 }} - /> - setdeletereleaseName(e.target.value)} + placeholder="User Name" + name="userName" + value={formData.userName} + onChange={handleInputChange} style={{ width: 300 }} />

- - - Delete Release + + Delete User - - - - - Delete Release - - - - You will not be able - to recover {deletereleasenamespaceName}/ - {deletereleasepackageName}/{deletereleaseName} release after you - delete it. - - - - Close - - Delete Release - - - -
- {/*
+
+

Change password

setUserName(e.target.value)} + name="userName" + value={formData.userName} + onChange={handleInputChange} style={{ width: 300 }} /> -

-

setNewPassword(e.target.value)} + name="newPassword" + value={formData.newPassword} + onChange={handleInputChange} style={{ width: 300 }} />

+ openModal( + "Change Password", + `You will not be able to recover ${formData.userName} user's password after you change password.`, + changePassword + ) + } style={{ fontSize: 16 }} > Change Password - - - - - Change Password - - - - You will not be able - to recover {userName} user's password after you change password. - - - - Close - - Change Password - - - - -
*/} +
+ + + + + {modalData.modalTitle} + + + + {" "} + {modalData.modalMessage} + + + + Close + + Delete + + + +
); }; export default AdminSection; + +// fix workflow for the submit + +// workflow: +// click show modal with data , +// modal with fn and close , +// fn click send and update with status +// show new modal with status, +// close modal From c26dfd5692d64a1824ff72e7280d4e7bf7f8af99 Mon Sep 17 00:00:00 2001 From: Henil Panchal Date: Fri, 19 May 2023 21:52:46 +0530 Subject: [PATCH 17/64] fix: fix admin page bugs --- registry/src/pages/admin.js | 104 ++++++-------------- registry/src/store/actions/adminActions.js | 5 +- registry/src/store/reducers/adminReducer.js | 4 +- 3 files changed, 34 insertions(+), 79 deletions(-) diff --git a/registry/src/pages/admin.js b/registry/src/pages/admin.js index 5b91b7a5..fd2a467d 100644 --- a/registry/src/pages/admin.js +++ b/registry/src/pages/admin.js @@ -1,5 +1,7 @@ import React, { useEffect, useState } from "react"; import { Container } from "react-bootstrap"; +import { useDispatch, useSelector } from "react-redux"; +import { useNavigate } from "react-router-dom"; import { MDBBtn, MDBModal, @@ -11,12 +13,10 @@ import { MDBModalFooter, MDBIcon, } from "mdb-react-ui-kit"; -import { useDispatch, useSelector } from "react-redux"; -import { useNavigate } from "react-router-dom"; import { adminAuth, deleteUser, - deleteNamespace, + deleteNamespace, deletePackage, deleteRelease, deprecatePackage, @@ -32,18 +32,19 @@ const AdminSection = () => { useEffect(() => { dispatch(adminAuth(uuid)); - console.log("adminAuth"); - console.log(isAdmin); if (!isAdmin) { navigate("/404"); } }, [isAdmin]); useEffect(() => { - if (statuscode !=null) { - toggleShowModal(); + if (statuscode != null) { + // toggleShowModal(); // toggleShowModal(); + + openModal(statuscode+" Status Code", message, null); } - }, [statuscode]); + }, [statuscode,message]); + const [formData, setFormData] = useState({ namespaceName: "", @@ -94,12 +95,6 @@ const AdminSection = () => { ) ); - console.log( - "Deleting package:", - formData.namespaceName, - formData.packageName - ); - // clear the form data setFormData({ namespaceName: "", @@ -111,22 +106,13 @@ const AdminSection = () => { openModal( "Delete Release", `You will not be able to recover ${formData.namespaceName}/${formData.packageName}/${formData.releaseName} release after you delete it.`, - deleteRelease - ); - - console.log( - "Deleting release:", - formData.namespaceName, - formData.packageName, - formData.releaseName - ); - - dispatch( - deleteRelease( - formData.namespaceName, - formData.packageName, - formData.releaseName, - uuid + dispatch( + deleteRelease( + formData.namespaceName, + formData.packageName, + formData.releaseName, + uuid + ) ) ); @@ -144,21 +130,8 @@ const AdminSection = () => { `You will not be able to recover ${formData.userName} user after you delete it.`, dispatch(deleteUser(formData.userName, uuid)) ); - console.log("Deleting user:", formData.userName); - - // setModalData({ - // showModal: false, - // }); - - setModalData({ - showModal: true, - modalTitle: statuscode + " Status Code", - modalMessage: message, - modalAction: toggleShowModal(), - }); // clear the form data - // openModal(statuscode + " Status Code", message, toggleShowModal()); setFormData({ userName: "", }); @@ -170,8 +143,7 @@ const AdminSection = () => { `You will not be able to recover ${formData.namespaceName} namespace after you delete it.`, dispatch(deleteNamespace(formData.namespaceName, uuid)) ); - console.log("Deleting namespace:", formData.namespaceName); - dispatch(deleteNamespace(formData.namespaceName, uuid)); + // clear the form data setFormData({ namespaceName: "", @@ -186,14 +158,7 @@ const AdminSection = () => { deprecatePackage(formData.namespaceName, formData.packageName, uuid) ) ); - console.log( - "Deprecating package:", - formData.namespaceName, - formData.packageName - ); - dispatch( - deprecatePackage(formData.namespaceName, formData.packageName, uuid) - ); + // clear the form data setFormData({ namespaceName: "", @@ -201,15 +166,15 @@ const AdminSection = () => { }); }; - const changePassword = () => { - console.log("Changing password for user:", formData.userName); - // Add the logic to change the password - // clear the form data - setFormData({ - userName: "", - newPassword: "", - }); - }; +// const changePassword = () => { // TODO: Enable this feature +// console.log("Changing password for user:", formData.userName); +// // Add the logic to change the password +// // clear the form data +// setFormData({ +// userName: "", +// newPassword: "", +// }); +// }; return ( @@ -331,7 +296,7 @@ const AdminSection = () => { Delete User -
+ {/*
// TODO: Enable this feature

Change password

@@ -364,7 +329,7 @@ const AdminSection = () => { > Change Password -

+
*/} @@ -393,13 +358,4 @@ const AdminSection = () => { ); }; -export default AdminSection; - -// fix workflow for the submit - -// workflow: -// click show modal with data , -// modal with fn and close , -// fn click send and update with status -// show new modal with status, -// close modal +export default AdminSection; \ No newline at end of file diff --git a/registry/src/store/actions/adminActions.js b/registry/src/store/actions/adminActions.js index 55b69743..f95843f6 100644 --- a/registry/src/store/actions/adminActions.js +++ b/registry/src/store/actions/adminActions.js @@ -99,7 +99,6 @@ export const deleteUser = (username, uuid) => async (dispatch) => { } } catch (error) { //on failure - console.log(error); dispatch({ type: DELETE_USER_ERROR, payload: { @@ -119,7 +118,7 @@ export const deleteNamespace = (namespace, uuid) => async (dispatch) => { try { let result = await axios({ method: "post", - url: `${process.env.REACT_APP_REGISTRY_API_URL}/packages/${namespace}/delete`, + url: `${process.env.REACT_APP_REGISTRY_API_URL}/namespace/${namespace}/delete`, data: formData, headers: { "Content-Type": "multipart/form-data", @@ -166,7 +165,7 @@ export const deletePackage = try { let result = await axios({ method: "post", - url: `${process.env.REACT_APP_REGISTRY_API_URL}/packages/${namespacename}/${packagename}/delete"`, + url: `${process.env.REACT_APP_REGISTRY_API_URL}/packages/${namespacename}/${packagename}/delete`, data: formData, headers: { "Content-Type": "multipart/form-data", diff --git a/registry/src/store/reducers/adminReducer.js b/registry/src/store/reducers/adminReducer.js index 54145afb..214448ce 100644 --- a/registry/src/store/reducers/adminReducer.js +++ b/registry/src/store/reducers/adminReducer.js @@ -15,8 +15,8 @@ import { const initialState = { error: null, - isAdmin: false, - message: "", + isAdmin: true, + message: null, statuscode: null, }; From 28a6dff352faec71b23a2942114d4b22329768fc Mon Sep 17 00:00:00 2001 From: Henil Panchal Date: Fri, 19 May 2023 22:10:32 +0530 Subject: [PATCH 18/64] fix: fix admin page bugs --- registry/src/pages/admin.js | 73 ++++++++++++++++++++----------------- 1 file changed, 40 insertions(+), 33 deletions(-) diff --git a/registry/src/pages/admin.js b/registry/src/pages/admin.js index fd2a467d..04ac03f0 100644 --- a/registry/src/pages/admin.js +++ b/registry/src/pages/admin.js @@ -39,12 +39,9 @@ const AdminSection = () => { useEffect(() => { if (statuscode != null) { - // toggleShowModal(); // toggleShowModal(); - - openModal(statuscode+" Status Code", message, null); + openModal(statuscode + " Status Code", message, null); } - }, [statuscode,message]); - + }, [statuscode, message]); const [formData, setFormData] = useState({ namespaceName: "", @@ -90,9 +87,11 @@ const AdminSection = () => { openModal( "Delete Package", `You will not be able to recover ${formData.namespaceName}/${formData.packageName} package after you delete it.`, - dispatch( - deletePackage(formData.namespaceName, formData.packageName, uuid) - ) + () => { + dispatch( + deletePackage(formData.namespaceName, formData.packageName, uuid) + ); + } ); // clear the form data @@ -106,14 +105,16 @@ const AdminSection = () => { openModal( "Delete Release", `You will not be able to recover ${formData.namespaceName}/${formData.packageName}/${formData.releaseName} release after you delete it.`, - dispatch( - deleteRelease( - formData.namespaceName, - formData.packageName, - formData.releaseName, - uuid - ) - ) + () => { + dispatch( + deleteRelease( + formData.namespaceName, + formData.packageName, + formData.releaseName, + uuid + ) + ); + } ); // clear the form data @@ -128,7 +129,9 @@ const AdminSection = () => { openModal( "Delete User", `You will not be able to recover ${formData.userName} user after you delete it.`, - dispatch(deleteUser(formData.userName, uuid)) + () => { + dispatch(deleteUser(formData.userName, uuid)); + } ); // clear the form data @@ -141,9 +144,11 @@ const AdminSection = () => { openModal( "Delete Namespace", `You will not be able to recover ${formData.namespaceName} namespace after you delete it.`, - dispatch(deleteNamespace(formData.namespaceName, uuid)) + () => { + dispatch(deleteNamespace(formData.namespaceName, uuid)); + } ); - + // clear the form data setFormData({ namespaceName: "", @@ -154,11 +159,13 @@ const AdminSection = () => { openModal( "Delete Package", `You will not be able to recover ${formData.namespaceName}/${formData.packageName} package after you delete it.`, - dispatch( - deprecatePackage(formData.namespaceName, formData.packageName, uuid) - ) + () => { + dispatch( + deprecatePackage(formData.namespaceName, formData.packageName, uuid) + ); + } ); - + // clear the form data setFormData({ namespaceName: "", @@ -166,15 +173,15 @@ const AdminSection = () => { }); }; -// const changePassword = () => { // TODO: Enable this feature -// console.log("Changing password for user:", formData.userName); -// // Add the logic to change the password -// // clear the form data -// setFormData({ -// userName: "", -// newPassword: "", -// }); -// }; + // const changePassword = () => { // TODO: Enable this feature + // console.log("Changing password for user:", formData.userName); + // // Add the logic to change the password + // // clear the form data + // setFormData({ + // userName: "", + // newPassword: "", + // }); + // }; return ( @@ -358,4 +365,4 @@ const AdminSection = () => { ); }; -export default AdminSection; \ No newline at end of file +export default AdminSection; From 6abb8232e09be46f8037472fac5a71f74102edc5 Mon Sep 17 00:00:00 2001 From: Henil Panchal Date: Fri, 19 May 2023 23:25:57 +0530 Subject: [PATCH 19/64] fix: only allow the packages and namespaces and tarballs for the clone --- flask/mongo.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/flask/mongo.py b/flask/mongo.py index 4d321d16..9a627351 100644 --- a/flask/mongo.py +++ b/flask/mongo.py @@ -43,7 +43,7 @@ def generate_latest_tarball(): os.mkdir(backup_dir) # Execute the mongodump command - command = f"mongodump --host {mongo_uri} --authenticationDatabase admin --username {mongo_username} --password {mongo_password} --db {database_name} --out {backup_dir}" + command = f"mongodump --host {mongo_uri} --authenticationDatabase admin --username {mongo_username} --password {mongo_password} --db {database_name} --collection namespaces --collection packages --collection tarballs --out {backup_dir}" subprocess.call(command, shell=True) # Create a tar archive of the backup directory From 7e14cb22e97969319b1d48ec0ff0402c1f9a1968 Mon Sep 17 00:00:00 2001 From: Henil Panchal Date: Fri, 19 May 2023 23:30:28 +0530 Subject: [PATCH 20/64] disable account transfer API --- flask/user.py | 79 ++++++++++++++++++++++++++------------------------- 1 file changed, 40 insertions(+), 39 deletions(-) diff --git a/flask/user.py b/flask/user.py index 8329ba37..0539d59e 100644 --- a/flask/user.py +++ b/flask/user.py @@ -213,46 +213,47 @@ def admin(): ) -@app.route("/users/admin/transfer", methods=["POST"]) +@app.route("/users/admin/transfer", methods=["POST"]) # TODO: enable this functionality def transfer_account(): - uuid = request.form.get("uuid") - if not uuid: - return jsonify({"message": "Unauthorized", "code": 401}), 401 - else: - user = db.users.find_one({"uuid": uuid}) - - if not user: - return jsonify({"message": "User not found", "code": 404}), 404 - - if "admin" not in user["roles"]: - return jsonify({"message": "Unauthorized", "code": 401}), 401 - else: - old_user = request.form.get("old_username") - new_user = request.form.get("new_username") - new_email = request.form.get("new_email") - db.users.update_one( - {"username": old_user}, - { - "$set": { - "email": new_email, - "username": new_user, - "uuid": "", - "loggedCount": 0, - "loginAt": None, - "lastLogout": None, - } - }, - ) - forgot_password(new_email) - return ( - jsonify( - { - "message": "Account Transfer Successful and Password reset request sent.", - "code": 200, - } - ), - 200, - ) + return jsonify({"message": "This Functionality has been disabled.", "code": 501}), 501 + # uuid = request.form.get("uuid") + # if not uuid: + # return jsonify({"message": "Unauthorized", "code": 401}), 401 + # else: + # user = db.users.find_one({"uuid": uuid}) + + # if not user: + # return jsonify({"message": "User not found", "code": 404}), 404 + + # if "admin" not in user["roles"]: + # return jsonify({"message": "Unauthorized", "code": 401}), 401 + # else: + # old_user = request.form.get("old_username") + # new_user = request.form.get("new_username") + # new_email = request.form.get("new_email") + # db.users.update_one( + # {"username": old_user}, + # { + # "$set": { + # "email": new_email, + # "username": new_user, + # "uuid": "", + # "loggedCount": 0, + # "loginAt": None, + # "lastLogout": None, + # } + # }, + # ) + # forgot_password(new_email) + # return ( + # jsonify( + # { + # "message": "Account Transfer Successful and Password reset request sent.", + # "code": 200, + # } + # ), + # 200, + # ) @app.route("//maintainer", methods=["POST"]) From 6a7ebfa66149a6986d6b3db99988342ab14c4c79 Mon Sep 17 00:00:00 2001 From: Henil Panchal Date: Sat, 20 May 2023 19:25:05 +0530 Subject: [PATCH 21/64] admin navbar setting --- registry/src/pages/Navbar.js | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/registry/src/pages/Navbar.js b/registry/src/pages/Navbar.js index 2b1a4090..d44dd923 100644 --- a/registry/src/pages/Navbar.js +++ b/registry/src/pages/Navbar.js @@ -1,4 +1,4 @@ -import React, { useState } from "react"; +import React, { useEffect, useState } from "react"; import { useNavigate, useLocation } from "react-router-dom"; import { useDispatch, useSelector } from "react-redux"; import Image from "react-bootstrap/Image"; @@ -8,6 +8,7 @@ import Navbar from "react-bootstrap/Navbar"; import NavDropdown from "react-bootstrap/NavDropdown"; import { logout } from "../store/actions/authActions"; import { searchPackage, setQuery } from "../store/actions/searchActions"; +import { adminAuth } from "../store/actions/adminActions"; const NavbarComponent = () => { const dispatch = useDispatch(); @@ -19,6 +20,10 @@ const NavbarComponent = () => { const username = useSelector((state) => state.auth.username); const uuid = useSelector((state) => state.auth.uuid); + useEffect(() => { + dispatch(adminAuth(uuid)); + }, [isAuthenticated, uuid]); + const signOut = () => { dispatch(logout(uuid)); }; @@ -127,8 +132,8 @@ const SearchBar = () => { } }; - const handleKeyDown = event => { - if (event.key === 'Enter') { + const handleKeyDown = (event) => { + if (event.key === "Enter") { search(); } }; From 91e7f0c9bf31d5f8b4d8e965f66752b87417d387 Mon Sep 17 00:00:00 2001 From: Henil Panchal Date: Sat, 3 Jun 2023 10:30:59 +0530 Subject: [PATCH 22/64] fix: bugs --- registry/src/App.js | 2 +- registry/src/pages/login.js | 11 ++--------- registry/src/pages/register.js | 11 ++--------- registry/src/pages/{verifyemail.js => verifyEmail.js} | 0 4 files changed, 5 insertions(+), 19 deletions(-) rename registry/src/pages/{verifyemail.js => verifyEmail.js} (100%) diff --git a/registry/src/App.js b/registry/src/App.js index d178802e..9b9d24fb 100644 --- a/registry/src/App.js +++ b/registry/src/App.js @@ -12,7 +12,7 @@ import NoPage from "./pages/404"; import UserPage from "./pages/user"; import PackagePage from "./pages/package"; import NamespaceForm from "./pages/createNamespace"; -import VerifyEmail from "./pages/verifyemail"; +import VerifyEmail from "./pages/verifyEmail"; import NamespacePage from "./pages/namespace"; import AdminSection from "./pages/admin"; import ForgotPassword from "./pages/forgotpassword"; diff --git a/registry/src/pages/login.js b/registry/src/pages/login.js index 0a3e216f..b24b114f 100644 --- a/registry/src/pages/login.js +++ b/registry/src/pages/login.js @@ -3,7 +3,6 @@ import { useNavigate } from "react-router-dom"; import { useDispatch, useSelector } from "react-redux"; import { login, resetErrorMessage } from "../store/actions/authActions"; import { Link } from "react-router-dom"; -import Spinner from "react-bootstrap/Spinner"; import Container from "react-bootstrap/Container"; const Login = () => { @@ -51,7 +50,7 @@ const Login = () => { } }; - return !isLoading ? ( + return (

Welcome to fpm Registry!

@@ -77,7 +76,7 @@ const Login = () => {

{fromValidationErrors.password}

)} {errorMessage != null ?

{errorMessage}

: null} - +

Don't have an account? Sign up

@@ -86,12 +85,6 @@ const Login = () => {

- ) : ( - - - Loading... - - ); }; diff --git a/registry/src/pages/register.js b/registry/src/pages/register.js index 43bdf510..a0dce463 100644 --- a/registry/src/pages/register.js +++ b/registry/src/pages/register.js @@ -3,7 +3,6 @@ import { useNavigate } from "react-router-dom"; import { useDispatch, useSelector } from "react-redux"; import { signup, resetErrorMessage } from "../store/actions/authActions"; import { Link } from "react-router-dom"; -import Spinner from "react-bootstrap/Spinner"; import Container from "react-bootstrap/Container"; const Register = () => { @@ -56,7 +55,7 @@ const Register = () => { return Object.keys(errors).length === 0; }; - return !isLoading ? ( + return (

Welcome to fpm Registry!

@@ -92,7 +91,7 @@ const Register = () => {

{fromValidationErrors.password}

)} {errorMessage != null ?

{errorMessage}

: null} - +

Already have an account? Log in

@@ -100,14 +99,8 @@ const Register = () => { Forgot password

-
) : ( - - - Loading... - ); - }; export default Register; diff --git a/registry/src/pages/verifyemail.js b/registry/src/pages/verifyEmail.js similarity index 100% rename from registry/src/pages/verifyemail.js rename to registry/src/pages/verifyEmail.js From 1d017a33726501990d7cbb34af2176b9219620b7 Mon Sep 17 00:00:00 2001 From: Henil Panchal Date: Sat, 3 Jun 2023 10:32:54 +0530 Subject: [PATCH 23/64] fix: verify email reducer --- registry/src/pages/verifyEmail.js | 4 ++-- registry/src/store/reducers/rootReducer.js | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/registry/src/pages/verifyEmail.js b/registry/src/pages/verifyEmail.js index 02558e88..6c1b4fad 100644 --- a/registry/src/pages/verifyEmail.js +++ b/registry/src/pages/verifyEmail.js @@ -8,8 +8,8 @@ import Container from "react-bootstrap/Container"; const VerifyEmail = () => { const { uuid } = useParams(); const dispatch = useDispatch(); - const message = useSelector((state) => state.verifyemail.message); - const statuscode = useSelector((state) => state.verifyemail.statuscode); + const message = useSelector((state) => state.verifyEmail.message); + const statuscode = useSelector((state) => state.verifyEmail.statuscode); const handleSubmit = async (e) => { e.preventDefault(); diff --git a/registry/src/store/reducers/rootReducer.js b/registry/src/store/reducers/rootReducer.js index 130e100d..91236501 100644 --- a/registry/src/store/reducers/rootReducer.js +++ b/registry/src/store/reducers/rootReducer.js @@ -30,7 +30,7 @@ const rootReducer = combineReducers({ createNamespace: createNamespaceReducer, addRemoveNamespaceMaintainer: addRemoveNamespaceMaintainerReducer, addRemoveNamespaceAdmin: addRemoveNamespaceAdminReducer, - verifyemail: verifyEmailReducer, + verifyEmail: verifyEmailReducer, }); export default rootReducer; From 9dd0ebf0ae1d925af5bacf622a55b42e26aa3c58 Mon Sep 17 00:00:00 2001 From: Henil Panchal Date: Sat, 3 Jun 2023 17:34:31 +0530 Subject: [PATCH 24/64] feat: add dry run --- flask/packages.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/flask/packages.py b/flask/packages.py index 6b7f2089..78847288 100644 --- a/flask/packages.py +++ b/flask/packages.py @@ -131,8 +131,11 @@ def upload(): package_name = request.form.get("package_name") package_version = request.form.get("package_version") package_license = request.form.get("package_license") + dry_run = request.form.get("dry_run") tarball = request.files["tarball"] + dry_run = True if dry_run == "true" else False + if not upload_token: return jsonify({"code": 400, "message": "Upload token missing"}) @@ -269,6 +272,10 @@ def upload(): package_doc["versions"].append(new_version) package_doc["versions"] = sorted(package_doc["versions"], key=lambda x: x['version']) package_doc["updatedAt"] = datetime.utcnow() + + if dry_run: + return jsonify({"message": "Dry run Successful.", "code": 200}) + db.packages.update_one( {"_id": package_doc["_id"]}, {"$set": package_doc}, From 24d4827b8a616944501f19f09ec8bb269855c9e6 Mon Sep 17 00:00:00 2001 From: Henil Panchal Date: Sat, 3 Jun 2023 20:53:57 +0530 Subject: [PATCH 25/64] feat: add support for zip and tarballs --- flask/packages.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/flask/packages.py b/flask/packages.py index 78847288..f02867fe 100644 --- a/flask/packages.py +++ b/flask/packages.py @@ -184,9 +184,13 @@ def upload(): package_doc = db.packages.find_one({"name": package_name, "namespace": namespace_doc["_id"]}) - tarball_name = "{}-{}.tar.gz".format(package_name, package_version) + if tarball.content_type not in ["application/gzip", "application/zip"]: + return jsonify({"code": 400, "message": "Invalid file type"}),400 + + extn = "tar.gz" if tarball.content_type == "application/gzip" else "zip" + tarball_name = "{}-{}.{}".format(package_name, package_version, extn) # Upload the tarball to the Grid FS storage. - file_object_id = file_storage.put(tarball, content_type="application/gzip", filename=tarball_name) + file_object_id = file_storage.put(tarball, content_type=tarball.content_type, filename=tarball_name) From c0289a7a393bd8ed58e362298d1f9c23e47bad80 Mon Sep 17 00:00:00 2001 From: Henil Panchal Date: Sat, 3 Jun 2023 21:39:31 +0530 Subject: [PATCH 26/64] fix : #38 --- registry/src/pages/dashboard.js | 2 +- registry/src/store/actions/authActions.js | 4 ++++ registry/src/store/reducers/authReducer.js | 8 +++++++- 3 files changed, 12 insertions(+), 2 deletions(-) diff --git a/registry/src/pages/dashboard.js b/registry/src/pages/dashboard.js index 6c92f604..b275628c 100644 --- a/registry/src/pages/dashboard.js +++ b/registry/src/pages/dashboard.js @@ -28,7 +28,7 @@ const Dashboard = () => { const username = useSelector((state) => state.auth.username); const packages = useSelector((state) => state.dashboard.packages); const namespaces = useSelector((state) => state.dashboard.namespaces); - const isLoading = useSelector((state) => state.dashboard.isLoading); + const isLoading = useSelector((state) => state.dashboard.isLoading || state.auth.isLoading); const dispatch = useDispatch(); const navigate = useNavigate(); diff --git a/registry/src/store/actions/authActions.js b/registry/src/store/actions/authActions.js index 43083b09..aeacd28b 100644 --- a/registry/src/store/actions/authActions.js +++ b/registry/src/store/actions/authActions.js @@ -55,8 +55,12 @@ export const login = (user_identifier, password) => async (dispatch) => { export const LOGOUT_SUCCESS = "LOGOUT_SUCCESS"; export const LOGOUT_FAILURE = "LOGOUT_FAILURE"; +export const LOGOUT_REQUEST = "LOGOUT_REQUEST"; export const logout = (uuid) => async (dispatch) => { + dispatch({ + type: LOGOUT_REQUEST, + }); let formData = new FormData(); formData.append("uuid", uuid); diff --git a/registry/src/store/reducers/authReducer.js b/registry/src/store/reducers/authReducer.js index f89aab1f..1c0b2d2d 100644 --- a/registry/src/store/reducers/authReducer.js +++ b/registry/src/store/reducers/authReducer.js @@ -7,7 +7,8 @@ import { LOGOUT_FAILURE, RESET_ERROR_MESSAGE, LOGIN_REQUEST, - SIGNUP_REQUEST + SIGNUP_REQUEST, + LOGOUT_REQUEST } from "../actions/authActions"; const initialState = { @@ -25,6 +26,11 @@ const authReducer = (state = initialState, action) => { ...state, isLoading: true, }; + case LOGOUT_REQUEST: + return { + ...state, + isLoading: true, + }; case SIGNUP_REQUEST: return { ...state, From 0467b4c17e5a5b5d3e71b2ccb365818709348c0f Mon Sep 17 00:00:00 2001 From: Henil Panchal Date: Sat, 3 Jun 2023 21:45:07 +0530 Subject: [PATCH 27/64] fix: handle invalid package metadata --- flask/packages.py | 33 ++++++++++++++++++--------------- 1 file changed, 18 insertions(+), 15 deletions(-) diff --git a/flask/packages.py b/flask/packages.py index f02867fe..ab9d9062 100644 --- a/flask/packages.py +++ b/flask/packages.py @@ -207,21 +207,24 @@ def upload(): # No previous recorded versions of the package found. if not package_doc: - package_obj = { - "name": package_name, - "namespace": namespace_doc["_id"], - "description": package_data["description"], - "homepage": package_data["homepage"], - "repository": package_data["repository"], - "license": package_license, - "createdAt": datetime.utcnow(), - "updatedAt": datetime.utcnow(), - "author": user["_id"], - "maintainers": [user["_id"]], - "copyright": "Test copyright", - "tags": ["fortran", "fpm"], - "isDeprecated": False, - } + try: + package_obj = { + "name": package_name, + "namespace": namespace_doc["_id"], + "description": package_data["description"], + "homepage": package_data["homepage"], + "repository": package_data["repository"], + "license": package_license, + "createdAt": datetime.utcnow(), + "updatedAt": datetime.utcnow(), + "author": user["_id"], + "maintainers": [user["_id"]], + "copyright": "Test copyright", + "tags": ["fortran", "fpm"], + "isDeprecated": False, + } + except KeyError as e: + return jsonify({"code": 400, "message": f"Invalid package metadata. {e} is missing"}), 400 version_obj = { "version": package_version, From 84e6eb3d48e75341779c7708e5049d3e24e950cc Mon Sep 17 00:00:00 2001 From: Henil Panchal Date: Sat, 3 Jun 2023 22:03:36 +0530 Subject: [PATCH 28/64] fix: help styling --- registry/src/pages/help.js | 14 ++++++++++++-- registry/src/store/reducers/adminReducer.js | 2 +- 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/registry/src/pages/help.js b/registry/src/pages/help.js index 619422a4..35fcc541 100644 --- a/registry/src/pages/help.js +++ b/registry/src/pages/help.js @@ -6,10 +6,17 @@ const Help = () => { fontFamily: "Arial, sans-serif", fontSize: "18px", lineHeight: "1.5", - paddingLeft: "20px", + paddingLeft: "30px", paddingTop: "20px", paddinBottom: "20px", color: "#333", + paddingRight: "30px", + }; + + const h2Style = { + textAlign: "left", + alignContent: "left", + fontSize: "26px", }; const codeStyle = { @@ -23,6 +30,7 @@ const Help = () => {

Help



+

Account Registration

First of all, you will need to register a user account:
For uploading your package to the registry, you will have to step by @@ -33,6 +41,7 @@ const Help = () => { password to set up your account.


+

Namespace Creation

For uploading a package from fpm, you will have to first create a namespace. A namespace represents a collection of packages. Each package @@ -44,6 +53,7 @@ const Help = () => { been created by you. You can now generate a token for this namespace.


+

Token Generation and Package Upload

This token will be valid for 1 week , but you can always generate a new token. @@ -51,7 +61,7 @@ const Help = () => {
Use this token to upload packages from the fpm using the CLI:


fpm publish --token token-here -

+



After completing the above steps, you will receive a response in the fpm command line interface whether your upload was successful or not. If diff --git a/registry/src/store/reducers/adminReducer.js b/registry/src/store/reducers/adminReducer.js index 214448ce..928d2c5b 100644 --- a/registry/src/store/reducers/adminReducer.js +++ b/registry/src/store/reducers/adminReducer.js @@ -15,7 +15,7 @@ import { const initialState = { error: null, - isAdmin: true, + isAdmin: false, message: null, statuscode: null, }; From be244f42d5caf9c6c26f16bfd4ee688d3b762792 Mon Sep 17 00:00:00 2001 From: Henil Panchal Date: Sat, 3 Jun 2023 22:14:06 +0530 Subject: [PATCH 29/64] fix: handle extraction of toml from tar and zip, fix dry run --- flask/packages.py | 36 ++++++++++++++++++++++++++---------- 1 file changed, 26 insertions(+), 10 deletions(-) diff --git a/flask/packages.py b/flask/packages.py index ab9d9062..7d1ff04b 100644 --- a/flask/packages.py +++ b/flask/packages.py @@ -8,6 +8,7 @@ from auth import generate_uuid from app import swagger import zipfile +import tarfile import toml from flasgger.utils import swag_from from urllib.parse import unquote @@ -195,10 +196,12 @@ def upload(): # Extract the package metadata from the tarball's fpm.toml file. - package_data = extract_fpm_toml(tarball_name) + try: + package_data = extract_fpm_toml(tarball_name,extn) + except Exception as e: + return jsonify({"code": 400, "message": "Invalid package tarball."}), 400 # TODO: Uncomment this when the package validation is enabled - # validate the package # valid_package = validate_package(tarball_name, tarball_name) # if not valid_package: @@ -219,7 +222,7 @@ def upload(): "updatedAt": datetime.utcnow(), "author": user["_id"], "maintainers": [user["_id"]], - "copyright": "Test copyright", + "copyright": package_data["copyright"], "tags": ["fortran", "fpm"], "isDeprecated": False, } @@ -255,6 +258,10 @@ def upload(): # Current user is the author of the package. user["authorOf"].append(package["_id"]) + + if dry_run: + return jsonify({"message": "Dry run Successful.", "code": 200}) + db.users.update_one({"_id": user["_id"]}, {"$set": user}) return jsonify({"message": "Package Uploaded Successfully.", "code": 200}) @@ -362,7 +369,7 @@ def check_version(current_version, new_version): return new_list > current_list -@app.route("/packages//", methods=["GET"]) +@app.route("/packages//", methods=["GET","POST"]) @swag_from("documentation/get_package.yaml", methods=["GET"]) def get_package(namespace_name, package_name): # Get namespace from namespace name. @@ -632,10 +639,19 @@ def checkUserUnauthorized(user_id, package_namespace): str_user_id = str(user_id) return str_user_id not in admins_id_list and str_user_id not in maintainers_id_list -def extract_fpm_toml(file_obj): - with zipfile.ZipFile(file_obj, 'r') as zip_ref: - zip_ref.extract("fpm.toml") - with open("fpm.toml", 'r') as file: - data = toml.load(file) - +def extract_fpm_toml(file_obj,extn): + if extn == "zip": + with zipfile.ZipFile(file_obj, 'r') as zip_ref: + zip_ref.extract("fpm.toml") + with open("fpm.toml", 'r') as file: + data = toml.load(file) + else: + with tarfile.open(file_obj, 'r') as tar: + for member in tar.getmembers(): + if member.name == "fpm.toml": + file = tar.extractfile(member) + if file: + content = file.read() + file.close() + data = toml.loads(content.decode()) return data \ No newline at end of file From b08f99a2f9257a75bbccd43e106df072af266349 Mon Sep 17 00:00:00 2001 From: Henil Panchal Date: Wed, 7 Jun 2023 10:22:09 +0530 Subject: [PATCH 30/64] fix: emails --- flask/auth.py | 8 ++++---- flask/packages.py | 9 ++++----- 2 files changed, 8 insertions(+), 9 deletions(-) diff --git a/flask/auth.py b/flask/auth.py index 1185b7fe..c378ec4b 100644 --- a/flask/auth.py +++ b/flask/auth.py @@ -23,9 +23,9 @@ env_var["host"] = host env_var["salt"] = salt env_var["sudo_password"] = sudo_password - # smtp = smtplib.SMTP('smtp.gmail.com', 587) - # smtp.starttls() - # smtp.login(fortran_email, fortran_password) + smtp = smtplib.SMTP('smtp.gmail.com', 587) + smtp.starttls() + smtp.login(fortran_email, fortran_password) except KeyError as err: print("Add SALT to .env file") @@ -134,7 +134,7 @@ def signup(): if not registry_user: db.users.insert_one(user) - # send_verify_email(email) #TODO: uncomment this line + send_verify_email(email) #TODO: uncomment this line return ( jsonify( { diff --git a/flask/packages.py b/flask/packages.py index 066c8a55..6f7fbdc1 100644 --- a/flask/packages.py +++ b/flask/packages.py @@ -206,8 +206,6 @@ def upload(): # Upload the tarball to the Grid FS storage. file_object_id = file_storage.put(tarball, content_type=tarball.content_type, filename=tarball_name) - - # Extract the package metadata from the tarball's fpm.toml file. try: package_data = extract_fpm_toml(tarball_name,extn) @@ -255,6 +253,10 @@ def upload(): # Append the first version document. package_obj["versions"].append(version_obj) + + if dry_run: + return jsonify({"message": "Dry run Successful.", "code": 200}) + db.packages.insert_one(package_obj) package = db.packages.find_one( @@ -271,9 +273,6 @@ def upload(): # Current user is the author of the package. user["authorOf"].append(package["_id"]) - - if dry_run: - return jsonify({"message": "Dry run Successful.", "code": 200}) db.users.update_one({"_id": user["_id"]}, {"$set": user}) From 0765971270ae44e97e0eedca6e7c5d200271b140 Mon Sep 17 00:00:00 2001 From: Henil Panchal Date: Wed, 7 Jun 2023 11:33:54 +0530 Subject: [PATCH 31/64] fix: email verification --- registry/src/pages/verifyEmail.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/registry/src/pages/verifyEmail.js b/registry/src/pages/verifyEmail.js index 6c1b4fad..bd4939f3 100644 --- a/registry/src/pages/verifyEmail.js +++ b/registry/src/pages/verifyEmail.js @@ -13,14 +13,14 @@ const VerifyEmail = () => { const handleSubmit = async (e) => { e.preventDefault(); - dispatch(verify(uuid)); + dispatch(verify(uuid)); }; return ( - +

Welcome to fpm Registry!

-

Verify your email

+ {message && (statuscode !== 200 ? (

{message}

From 0cd68d903f53845fdd8bbbfea30c701df6e1509f Mon Sep 17 00:00:00 2001 From: Henil Panchal Date: Wed, 7 Jun 2023 19:18:45 +0530 Subject: [PATCH 32/64] fix: changes and buttons animation and add verification --- registry/src/pages/404.js | 19 ++-- registry/src/pages/admin.js | 168 ++++++++++++++++++++---------------- 2 files changed, 107 insertions(+), 80 deletions(-) diff --git a/registry/src/pages/404.js b/registry/src/pages/404.js index 9c539920..51b37292 100644 --- a/registry/src/pages/404.js +++ b/registry/src/pages/404.js @@ -1,15 +1,18 @@ import React from "react"; -import './404.css'; +import "./404.css"; const NoPage = () => { - return ( -
-

404

-

Page Not Found

-

The Page you are looking for doesn't exist or an other error occured. Go to Home Page.

-
+
+

+

404

+

Page Not Found

+

+ The Page you are looking for doesn't exist or an other error occured. Go + to Home Page. +

+
); }; -export default NoPage; \ No newline at end of file +export default NoPage; diff --git a/registry/src/pages/admin.js b/registry/src/pages/admin.js index 04ac03f0..b401339a 100644 --- a/registry/src/pages/admin.js +++ b/registry/src/pages/admin.js @@ -1,9 +1,8 @@ import React, { useEffect, useState } from "react"; import { Container } from "react-bootstrap"; +import Button from "react-bootstrap/Button"; import { useDispatch, useSelector } from "react-redux"; -import { useNavigate } from "react-router-dom"; import { - MDBBtn, MDBModal, MDBModalDialog, MDBModalContent, @@ -21,21 +20,19 @@ import { deleteRelease, deprecatePackage, } from "../store/actions/adminActions"; +import NoPage from "./404"; const AdminSection = () => { const uuid = useSelector((state) => state.auth.uuid); const dispatch = useDispatch(); - const navigate = useNavigate(); const message = useSelector((state) => state.admin.message); const statuscode = useSelector((state) => state.admin.statuscode); + const isAuthenticated = useSelector((state) => state.auth.isAuthenticated); const isAdmin = useSelector((state) => state.admin.isAdmin); useEffect(() => { dispatch(adminAuth(uuid)); - if (!isAdmin) { - navigate("/404"); - } - }, [isAdmin]); + }, [isAuthenticated, uuid]); useEffect(() => { if (statuscode != null) { @@ -76,6 +73,16 @@ const AdminSection = () => { setModalData({ ...modalData, showModal: !modalData.showModal }); }; + const isEmpty = (...values) => { + if (values.some((value) => value === "")) { + openModal("Empty Fields", "Please fill out all fields.", () => { + toggleShowModal(); + }); + return true; + } + return false; + }; + const handleAction = () => { if (modalData.modalAction) { modalData.modalAction(); @@ -84,15 +91,17 @@ const AdminSection = () => { }; const handleDeletePackage = () => { - openModal( - "Delete Package", - `You will not be able to recover ${formData.namespaceName}/${formData.packageName} package after you delete it.`, - () => { - dispatch( - deletePackage(formData.namespaceName, formData.packageName, uuid) - ); - } - ); + if (!isEmpty(formData.namespaceName, formData.packageName)) { + openModal( + "Delete Package", + `You will not be able to recover ${formData.namespaceName}/${formData.packageName} package after you delete it.`, + () => { + dispatch( + deletePackage(formData.namespaceName, formData.packageName, uuid) + ); + } + ); + } // clear the form data setFormData({ @@ -102,20 +111,29 @@ const AdminSection = () => { }; const handleDeleteRelease = () => { - openModal( - "Delete Release", - `You will not be able to recover ${formData.namespaceName}/${formData.packageName}/${formData.releaseName} release after you delete it.`, - () => { - dispatch( - deleteRelease( - formData.namespaceName, - formData.packageName, - formData.releaseName, - uuid - ) - ); - } - ); + if ( + !isEmpty( + formData.namespaceName, + formData.packageName, + formData.releaseName, + uuid + ) + ) { + openModal( + "Delete Release", + `You will not be able to recover ${formData.namespaceName}/${formData.packageName}/${formData.releaseName} release after you delete it.`, + () => { + dispatch( + deleteRelease( + formData.namespaceName, + formData.packageName, + formData.releaseName, + uuid + ) + ); + } + ); + } // clear the form data setFormData({ @@ -126,13 +144,15 @@ const AdminSection = () => { }; const handleDeleteUser = () => { - openModal( - "Delete User", - `You will not be able to recover ${formData.userName} user after you delete it.`, - () => { - dispatch(deleteUser(formData.userName, uuid)); - } - ); + if (!isEmpty(formData.userName, uuid)) { + openModal( + "Delete User", + `You will not be able to recover ${formData.userName} user after you delete it.`, + () => { + dispatch(deleteUser(formData.userName, uuid)); + } + ); + } // clear the form data setFormData({ @@ -141,13 +161,15 @@ const AdminSection = () => { }; const handleDeleteNamespace = () => { - openModal( - "Delete Namespace", - `You will not be able to recover ${formData.namespaceName} namespace after you delete it.`, - () => { - dispatch(deleteNamespace(formData.namespaceName, uuid)); - } - ); + if (!isEmpty(formData.namespaceName, uuid)) { + openModal( + "Delete Namespace", + `You will not be able to recover ${formData.namespaceName} namespace after you delete it.`, + () => { + dispatch(deleteNamespace(formData.namespaceName, uuid)); + } + ); + } // clear the form data setFormData({ @@ -156,15 +178,17 @@ const AdminSection = () => { }; const handleDeprecatePackage = () => { - openModal( - "Delete Package", - `You will not be able to recover ${formData.namespaceName}/${formData.packageName} package after you delete it.`, - () => { - dispatch( - deprecatePackage(formData.namespaceName, formData.packageName, uuid) - ); - } - ); + if (!isEmpty(formData.namespaceName, formData.packageName, uuid)) { + openModal( + "Delete Package", + `You will not be able to recover ${formData.namespaceName}/${formData.packageName} package after you delete it.`, + () => { + dispatch( + deprecatePackage(formData.namespaceName, formData.packageName, uuid) + ); + } + ); + } // clear the form data setFormData({ @@ -183,7 +207,7 @@ const AdminSection = () => { // }); // }; - return ( + return isAdmin? (

Admin Settings

@@ -207,9 +231,9 @@ const AdminSection = () => { style={{ width: 300 }} />

- +


@@ -240,9 +264,9 @@ const AdminSection = () => { style={{ width: 300 }} />

- +


@@ -265,9 +289,9 @@ const AdminSection = () => { style={{ width: 300 }} />

- +


@@ -282,9 +306,9 @@ const AdminSection = () => { style={{ width: 300 }} />

- +


@@ -299,9 +323,9 @@ const AdminSection = () => { style={{ width: 300 }} />

- +
{/*
// TODO: Enable this feature

@@ -324,7 +348,7 @@ const AdminSection = () => { style={{ width: 300 }} />

- openModal( "Change Password", @@ -335,34 +359,34 @@ const AdminSection = () => { style={{ fontSize: 16 }} > Change Password - +
*/} {modalData.modalTitle} - + > {" "} {modalData.modalMessage} - + + - ); + ):(); }; export default AdminSection; From 5167625087f24ebf965bb650cb6b9d354cb6aa23 Mon Sep 17 00:00:00 2001 From: Henil Panchal Date: Wed, 7 Jun 2023 19:19:43 +0530 Subject: [PATCH 33/64] fix: auth reducers --- registry/src/store/reducers/authReducer.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/registry/src/store/reducers/authReducer.js b/registry/src/store/reducers/authReducer.js index 1c0b2d2d..72f7014f 100644 --- a/registry/src/store/reducers/authReducer.js +++ b/registry/src/store/reducers/authReducer.js @@ -60,12 +60,14 @@ const authReducer = (state = initialState, action) => { uuid: null, username: null, error: null, + isLoading: false, }; case LOGOUT_FAILURE: return { ...state, error: action.payload.error, + isLoading: false, }; case SIGNUP_SUCCESS: From 3123920e8e4ea0d3c34932903011b0d8fd079f34 Mon Sep 17 00:00:00 2001 From: Henil Panchal Date: Wed, 7 Jun 2023 19:20:35 +0530 Subject: [PATCH 34/64] fix: auth --- flask/auth.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/flask/auth.py b/flask/auth.py index c378ec4b..4913679a 100644 --- a/flask/auth.py +++ b/flask/auth.py @@ -275,8 +275,7 @@ def send_verify_email(email): if not user: return jsonify({"message": "User not found", "code": 404}), 404 - uuid = generate_uuid() - db.users.update_one({"email": email}, {"$set": {"uuid": uuid}}) + uuid = user["uuid"] message = f"""\n Dear {user['username']}, @@ -310,6 +309,8 @@ def verify_email(): if not user: return jsonify({"message": "User not found", "code": 404}), 404 + db.users.update_one({"uuid": uuid}, {"$set": {"isverified": True}}) + return jsonify({"message": "Successfully Verified Email", "code": 200}), 200 @app.route("/auth/change-email", methods=["POST"]) From ab9a265290e1c38b645a4158ca1a25674c58bdd5 Mon Sep 17 00:00:00 2001 From: Henil Panchal Date: Mon, 12 Jun 2023 19:08:34 +0530 Subject: [PATCH 35/64] feat: Support multiple tarballs format --- flask/auth.py | 2 +- flask/packages.py | 352 ++++++++++++++++++++++++++++++++-------------- 2 files changed, 246 insertions(+), 108 deletions(-) diff --git a/flask/auth.py b/flask/auth.py index 4913679a..16943a6b 100644 --- a/flask/auth.py +++ b/flask/auth.py @@ -316,7 +316,7 @@ def verify_email(): @app.route("/auth/change-email", methods=["POST"]) def change_email(): uuid = request.form.get("uuid") - new_email = request.form.get("new_email") + new_email = request.form.get("newemail") if not uuid: return jsonify({"message": "Unauthorized", "code": 401}), 401 diff --git a/flask/packages.py b/flask/packages.py index b84d6c0b..be659f2e 100644 --- a/flask/packages.py +++ b/flask/packages.py @@ -15,6 +15,7 @@ import math import semantic_version from license_expression import get_spdx_licensing + # from validate_package import validate_package parameters = { @@ -25,6 +26,7 @@ "downloads": "downloads", } + def is_valid_version_str(version_str): """ Function to verify whether the version string is valid or not. @@ -42,6 +44,7 @@ def is_valid_version_str(version_str): except: return False + def is_valid_license_identifier(license_str): """ Function to check whether the license string is a valid identifier or not. @@ -59,6 +62,7 @@ def is_valid_license_identifier(license_str): except: return False + @app.route("/packages", methods=["GET"]) @swag_from("documentation/search_packages.yaml", methods=["GET"]) def search_packages(): @@ -122,9 +126,18 @@ def search_packages(): i["namespace"] = namespace["namespace"] i["author"] = author["username"] search_packages.append(i) - return jsonify({"code": 200, "packages": search_packages, "total_pages": total_pages}), 200 + return ( + jsonify( + {"code": 200, "packages": search_packages, "total_pages": total_pages} + ), + 200, + ) else: - return jsonify({"status": "error", "message": "packages not found", "code": 404}), 404 + return ( + jsonify({"status": "error", "message": "packages not found", "code": 404}), + 404, + ) + @app.route("/packages", methods=["POST"]) def upload(): @@ -138,77 +151,117 @@ def upload(): dry_run = True if dry_run == "true" else False if not upload_token: - return jsonify({"code": 400, "message": "Upload token missing"}) - + return jsonify({"code": 400, "message": "Upload token missing"}), 400 + if not package_name: - return jsonify({"code": 400, "message": "Package name is missing"}) - + return jsonify({"code": 400, "message": "Package name is missing"}), 400 + if not package_version: - return jsonify({"code": 400, "message": "Package version is missing"}) - + return jsonify({"code": 400, "message": "Package version is missing"}), 400 + if not package_license: - return jsonify({"code": 400, "message": "Package license is missing"}) - + return jsonify({"code": 400, "message": "Package license is missing"}), 400 + # Check whether version string is valid or not. if package_version == "0.0.0" or not is_valid_version_str(package_version): - return jsonify({"code": 400, "message": "Version is not valid"}) - + return jsonify({"code": 400, "message": "Version is not valid"}), 400 + # Check whether license identifier is valid or not. if not is_valid_license_identifier(license_str=package_license): - return jsonify({"code": 400, "message": f"Invalid license identifier {package_license}. Please check the SPDX license identifier list."}) - + return ( + jsonify( + { + "code": 400, + "message": f"Invalid license identifier {package_license}. Please check the SPDX license identifier list.", + } + ), + 400, + ) + # Find the document that contains the upload token. - namespace_doc = db.namespaces.find_one({"upload_tokens": {"$elemMatch": {"token": upload_token}}}) - package_doc = db.packages.find_one({"upload_tokens": {"$elemMatch": {"token": upload_token}}}) + namespace_doc = db.namespaces.find_one( + {"upload_tokens": {"$elemMatch": {"token": upload_token}}} + ) + package_doc = db.packages.find_one( + {"upload_tokens": {"$elemMatch": {"token": upload_token}}} + ) if not namespace_doc and not package_doc: - return jsonify({"code": 401, "message": "Invalid upload token"}) + return jsonify({"code": 401, "message": "Invalid upload token"}), 401 if namespace_doc: - upload_token_doc = next(item for item in namespace_doc['upload_tokens'] if item['token'] == upload_token) - package_doc = db.packages.find_one({"name": package_name, "namespace": namespace_doc["_id"]}) + upload_token_doc = next( + item + for item in namespace_doc["upload_tokens"] + if item["token"] == upload_token + ) + package_doc = db.packages.find_one( + {"name": package_name, "namespace": namespace_doc["_id"]} + ) elif package_doc: if package_doc["name"] != package_name: - return jsonify({"code": 401, "message": "Invalid upload token"}) - - upload_token_doc = next(item for item in package_doc['upload_tokens'] if item['token'] == upload_token) + return jsonify({"code": 401, "message": "Invalid upload token"}), 401 + + upload_token_doc = next( + item + for item in package_doc["upload_tokens"] + if item["token"] == upload_token + ) namespace_doc = db.namespaces.find_one({"_id": package_doc["namespace"]}) # Check if the token is expired. # Expire the token after one week of it's creation. - if check_token_expiry(upload_token_created_at=upload_token_doc['createdAt']): - return jsonify({"code": 401, "message": "Upload token has been expired. Please generate a new one"}) + if check_token_expiry(upload_token_created_at=upload_token_doc["createdAt"]): + return ( + jsonify( + { + "code": 401, + "message": "Upload token has been expired. Please generate a new one", + } + ), + 401, + ) # Get the user connected to the upload token. user_id = upload_token_doc["createdBy"] user = db.users.find_one({"_id": user_id}) if not user: - return jsonify({"code": 404, "message": "User not found"}) - + return jsonify({"code": 404, "message": "User not found"}), 404 + if not package_doc: # User should be either namespace maintainer or namespace admin to upload a package. - if checkUserUnauthorizedForNamespaceTokenCreation(user_id=user["_id"], namespace_doc=namespace_doc): - return jsonify({"code": 401, "message": "Unauthorized"}) + if checkUserUnauthorizedForNamespaceTokenCreation( + user_id=user["_id"], namespace_doc=namespace_doc + ): + return jsonify({"code": 401, "message": "Unauthorized"}), 401 else: # User should be either namespace maintainer or namespace admin or package maintainer to upload a package. - if checkUserUnauthorized(user_id=user["_id"], package_namespace=namespace_doc, package_doc=package_doc): - return jsonify({"message": "Unauthorized", "code": 401}) - - package_doc = db.packages.find_one({"name": package_name, "namespace": namespace_doc["_id"]}) + if checkUserUnauthorized( + user_id=user["_id"], + package_namespace=namespace_doc, + package_doc=package_doc, + ): + return jsonify({"message": "Unauthorized", "code": 401}), 401 + + package_doc = db.packages.find_one( + {"name": package_name, "namespace": namespace_doc["_id"]} + ) if tarball.content_type not in ["application/gzip", "application/zip"]: - return jsonify({"code": 400, "message": "Invalid file type"}),400 - + return jsonify({"code": 400, "message": "Invalid file type"}), 400 + extn = "tar.gz" if tarball.content_type == "application/gzip" else "zip" tarball_name = "{}-{}.{}".format(package_name, package_version, extn) # Upload the tarball to the Grid FS storage. - file_object_id = file_storage.put(tarball, content_type=tarball.content_type, filename=tarball_name) + file_object_id = file_storage.put( + tarball, content_type=tarball.content_type, filename=tarball_name + ) # Extract the package metadata from the tarball's fpm.toml file. try: - package_data = extract_fpm_toml(tarball_name,extn) + package_data = extract_fpm_toml(tarball_name, extn) except Exception as e: return jsonify({"code": 400, "message": "Invalid package tarball."}), 400 @@ -218,7 +271,6 @@ def upload(): # if not valid_package: # return jsonify({"status": "error", "message": "Invalid package", "code": 400}), 400 - # No previous recorded versions of the package found. if not package_doc: try: @@ -238,29 +290,41 @@ def upload(): "isDeprecated": False, } except KeyError as e: - return jsonify({"code": 400, "message": f"Invalid package metadata. {e} is missing"}), 400 - + return ( + jsonify( + { + "code": 400, + "message": f"Invalid package metadata. {e} is missing", + } + ), + 400, + ) + version_obj = { "version": package_version, "tarball": tarball_name, "dependencies": "Test dependencies", "createdAt": datetime.utcnow(), "isDeprecated": False, - "download_url": f"/tarballs/{file_object_id}" + "download_url": f"/tarballs/{file_object_id}", } package_obj["versions"] = [] # Append the first version document. package_obj["versions"].append(version_obj) - + if dry_run: return jsonify({"message": "Dry run Successful.", "code": 200}) db.packages.insert_one(package_obj) package = db.packages.find_one( - {"name": package_name, "versions.version": package_version, "namespace": namespace_doc["_id"]} + { + "name": package_name, + "versions.version": package_version, + "namespace": namespace_doc["_id"], + } ) # Add the package id to the namespace. @@ -273,15 +337,19 @@ def upload(): # Current user is the author of the package. user["authorOf"].append(package["_id"]) - + db.users.update_one({"_id": user["_id"]}, {"$set": user}) return jsonify({"message": "Package Uploaded Successfully.", "code": 200}) else: # Check if version of the package already exists in the backend. - package_version_doc = db.packages.find_one({ - "name": package_name, "namespace": namespace_doc["_id"], "versions.version": package_version - }) + package_version_doc = db.packages.find_one( + { + "name": package_name, + "namespace": namespace_doc["_id"], + "versions.version": package_version, + } + ) if package_version_doc: return jsonify({"message": "Version already exists", "code": 400}), 400 @@ -292,22 +360,25 @@ def upload(): "dependencies": "Test dependencies", "isDeprecated": False, "createdAt": datetime.utcnow(), - "download_url": f"/tarballs/{file_object_id}" + "download_url": f"/tarballs/{file_object_id}", } package_doc["versions"].append(new_version) - package_doc["versions"] = sorted(package_doc["versions"], key=lambda x: x['version']) + package_doc["versions"] = sorted( + package_doc["versions"], key=lambda x: x["version"] + ) package_doc["updatedAt"] = datetime.utcnow() if dry_run: - return jsonify({"message": "Dry run Successful.", "code": 200}) - + return jsonify({"message": "Dry run Successful.", "code": 200}), 200 + db.packages.update_one( {"_id": package_doc["_id"]}, {"$set": package_doc}, ) - return jsonify({"message": "Package Uploaded Successfully.", "code": 200}) + return jsonify({"message": "Package Uploaded Successfully.", "code": 200}), 200 + def check_token_expiry(upload_token_created_at): """ @@ -327,16 +398,22 @@ def check_token_expiry(upload_token_created_at): # Check if the time difference is greater than 1 week if time_diff > timedelta(weeks=1): return True - + return False - -@app.route('/tarballs/', methods=["GET"]) + + +@app.route("/tarballs/", methods=["GET"]) def serve_gridfs_file(oid): try: file = file_storage.get(ObjectId(oid)) # Return the file data as a Flask response object - return send_file(file, download_name=file.filename, as_attachment=True, mimetype=file.content_type) + return send_file( + file, + download_name=file.filename, + as_attachment=True, + mimetype=file.content_type, + ) except NoFile: abort(404) @@ -366,13 +443,18 @@ def update_package(): {"name": name, "namespace": package_namespace["_id"]} ) if package is None: - return jsonify({"status": "error", "message": "Package doesn't exist", "code": 404}), 404 + return ( + jsonify( + {"status": "error", "message": "Package doesn't exist", "code": 404} + ), + 404, + ) isDeprecated = True if isDeprecated == "true" else False package["isDeprecated"] = isDeprecated package["updatedAt"] = datetime.utcnow() db.packages.update_one({"_id": package["_id"]}, {"$set": package}) - return jsonify({"message": "Package Updated Successfully.", "code": 200}) + return jsonify({"message": "Package Updated Successfully.", "code": 200}), 200 def check_version(current_version, new_version): @@ -381,7 +463,7 @@ def check_version(current_version, new_version): return new_list > current_list -@app.route("/packages//", methods=["GET","POST"]) +@app.route("/packages//", methods=["GET", "POST"]) @swag_from("documentation/get_package.yaml", methods=["GET"]) def get_package(namespace_name, package_name): # Get namespace from namespace name. @@ -389,7 +471,10 @@ def get_package(namespace_name, package_name): # Check if namespace exists. if not namespace: - return jsonify({"status": "error", "message": "Namespace not found", "code": 404}), 404 + return ( + jsonify({"status": "error", "message": "Namespace not found", "code": 404}), + 404, + ) # Get package from a package_name and namespace's id. package = db.packages.find_one( @@ -398,7 +483,7 @@ def get_package(namespace_name, package_name): # Check if package is not found. if not package: - return jsonify({"message": "Package not found", "code": 404}) + return jsonify({"message": "Package not found", "code": 404}), 404 if request.method == "GET": # Get the package author from id. @@ -418,7 +503,7 @@ def get_package(namespace_name, package_name): "description": package["description"], } - return jsonify({"data": package_response_data, "code": 200}) + return jsonify({"data": package_response_data, "code": 200}), 200 elif request.method == "POST": """ @@ -429,7 +514,10 @@ def get_package(namespace_name, package_name): # Versions should not be empty array. if len(versions) == 0: - return jsonify({"message": "cached versions list is empty", "code": 400}) + return ( + jsonify({"message": "cached versions list is empty", "code": 400}), + 400, + ) # Sort the versions received in request body. sorted_versions = sort_versions(versions) @@ -448,7 +536,9 @@ def get_package(namespace_name, package_name): # Check if the local registry already has the latest version. if latest_version_backend_list <= latest_version_local_registry_list: return ( - jsonify({"message": "Latest version is already there in local registry"}), + jsonify( + {"message": "Latest version is already there in local registry"} + ), 200, ) @@ -464,11 +554,12 @@ def get_package(namespace_name, package_name): "version": package["versions"][-1]["version"], "tarball": package["versions"][-1]["tarball"], "isDeprecated": package["versions"][-1]["isDeprecated"], - } + }, } return jsonify({"data": package, "code": 200}), 200 - + + @app.route("/packages///verify", methods=["POST"]) def verify_user_role(namespace_name, package_name): uuid = request.form.get("uuid") @@ -484,18 +575,31 @@ def verify_user_role(namespace_name, package_name): namespace = db.namespaces.find_one({"namespace": namespace_name}) if not namespace: - return jsonify({"status": "error", "message": "Namespace not found", "code": 404}), 404 - - package = db.packages.find_one({"name": package_name, "namespace": namespace["_id"]}) + return ( + jsonify({"status": "error", "message": "Namespace not found", "code": 404}), + 404, + ) + + package = db.packages.find_one( + {"name": package_name, "namespace": namespace["_id"]} + ) if not package: - return jsonify({"status": "error", "message": "Package not found", "code": 404}), 404 - - if str(user["_id"]) in [str(obj_id) for obj_id in namespace["maintainers"]] or str(user["_id"]) in [str(obj_id) for obj_id in namespace["admins"]] or str(user["_id"]) in [str(obj_id) for obj_id in package["maintainers"]]: + return ( + jsonify({"status": "error", "message": "Package not found", "code": 404}), + 404, + ) + + if ( + str(user["_id"]) in [str(obj_id) for obj_id in namespace["maintainers"]] + or str(user["_id"]) in [str(obj_id) for obj_id in namespace["admins"]] + or str(user["_id"]) in [str(obj_id) for obj_id in package["maintainers"]] + ): return jsonify({"status": "success", "code": 200, "isVerified": True}), 200 else: return jsonify({"status": "error", "code": 401, "isVerified": False}), 401 - + + @app.route("/packages///", methods=["GET"]) @swag_from("documentation/get_version.yaml", methods=["GET"]) def get_package_from_version(namespace_name, package_name, version): @@ -504,7 +608,7 @@ def get_package_from_version(namespace_name, package_name, version): # Check if namespace does not exists. if not namespace: - return jsonify({"message": "Namespace not found", "code": 404}) + return jsonify({"message": "Namespace not found", "code": 404}), 404 # Get package from a package_name, namespace's id and version. package = db.packages.find_one( @@ -517,7 +621,7 @@ def get_package_from_version(namespace_name, package_name, version): # Check if package is not found. if not package: - return jsonify({"message": "Package not found", "code": 404}) + return jsonify({"message": "Package not found", "code": 404}), 404 else: # Get the package author from id. @@ -542,7 +646,7 @@ def get_package_from_version(namespace_name, package_name, version): "description": package["description"], } - return jsonify({"data": package_response_data, "code": 200}) + return jsonify({"data": package_response_data, "code": 200}), 200 @app.route("/packages///delete", methods=["POST"]) @@ -564,7 +668,7 @@ def delete_package(namespace_name, package_name): { "status": "error", "message": "User is not authorized to delete the package", - "code": 401 + "code": 401, } ), 401, @@ -574,7 +678,7 @@ def delete_package(namespace_name, package_name): namespace = db.namespaces.find_one({"namespace": namespace_name}) if not namespace: - return jsonify({"message": "Namespace not found", "code": 404}) + return jsonify({"message": "Namespace not found", "code": 404}), 404 # Find package using package_name & namespace_name. package = db.packages.find_one( @@ -592,7 +696,7 @@ def delete_package(namespace_name, package_name): if package_deleted.deleted_count > 0: return jsonify({"message": "Package deleted successfully", "code": 200}), 200 else: - return jsonify({"message": "Internal Server Error", "code": 500}) + return jsonify({"message": "Internal Server Error", "code": 500}), 500 @app.route( @@ -616,7 +720,7 @@ def delete_package_version(namespace_name, package_name, version): { "status": "error", "message": "User is not authorized to delete the package", - "code": 401 + "code": 401, } ), 401, @@ -627,7 +731,7 @@ def delete_package_version(namespace_name, package_name, version): # Check if namespace does not exists. if not namespace: - return jsonify({"message": "Namespace does not found", "code": 404}) + return jsonify({"message": "Namespace does not found", "code": 404}), 404 # Perform the pull operation. result = db.packages.update_one( @@ -638,7 +742,12 @@ def delete_package_version(namespace_name, package_name, version): if result.matched_count: return jsonify({"message": "Package version deleted successfully"}), 200 else: - return jsonify({"status": "error", "message": "Package version not found", "code": 404}), 404 + return ( + jsonify( + {"status": "error", "message": "Package version not found", "code": 404} + ), + 404, + ) @app.route("/packages/list", methods=["GET"]) @@ -668,6 +777,7 @@ def get_packages(): return jsonify({"packages": response_packages}) + @app.route("/packages///uploadToken", methods=["POST"]) def create_token_upload_token_package(namespace_name, package_name): # Verify the uuid. @@ -675,68 +785,89 @@ def create_token_upload_token_package(namespace_name, package_name): if not uuid: return jsonify({"code": 401, "message": "Unauthorized"}), 401 - + # Get the user from uuid. user_doc = db.users.find_one({"uuid": uuid}) if not user_doc: return jsonify({"code": 401, "message": "Unauthorized"}), 401 - + # Get the namespace from namespace_name. namespace_doc = db.namespaces.find_one({"namespace": namespace_name}) if not namespace_doc: return jsonify({"code": 404, "message": "Namespace not found"}), 404 - + # Get the package from package_name & namespace_id. - package_doc = db.packages.find_one({"name": package_name, "namespace": namespace_doc["_id"]}) + package_doc = db.packages.find_one( + {"name": package_name, "namespace": namespace_doc["_id"]} + ) if not package_doc: return jsonify({"code": 404, "message": "Package not found"}), 404 - + # Check if the user is authorized to generate package token. # Only package maintainers will have the option to generate tokens for a package. - if not str(user_doc["_id"]) in [str(obj_id) for obj_id in package_doc["maintainers"]]: - return jsonify({"code": 401, "message": "Only package maintainers can create tokens"}), 401 - + if not str(user_doc["_id"]) in [ + str(obj_id) for obj_id in package_doc["maintainers"] + ]: + return ( + jsonify( + {"code": 401, "message": "Only package maintainers can create tokens"} + ), + 401, + ) + # Generate the token. upload_token = generate_uuid() upload_token_obj = { "token": upload_token, "createdAt": datetime.utcnow(), - "createdBy": user_doc["_id"] + "createdBy": user_doc["_id"], } db.packages.update_one( - {"_id": package_doc["_id"]}, - {"$addToSet": {"upload_tokens": upload_token_obj}} + {"_id": package_doc["_id"]}, {"$addToSet": {"upload_tokens": upload_token_obj}} ) - - return jsonify({"code": 200, "message": "Upload token created successfully", "uploadToken": upload_token}), 200 + + return ( + jsonify( + { + "code": 200, + "message": "Upload token created successfully", + "uploadToken": upload_token, + } + ), + 200, + ) + + @app.route("/packages///maintainers", methods=["GET"]) def package_maintainers(namespace, package): namespace_doc = db.namespaces.find_one({"namespace": namespace}) if not namespace_doc: return jsonify({"message": "Namespace not found", "code": 404}) - - package_doc = db.packages.find_one({"name": package, "namespace": namespace_doc["_id"]}) + + package_doc = db.packages.find_one( + {"name": package, "namespace": namespace_doc["_id"]} + ) if not package_doc: return jsonify({"message": "Package not found", "code": 404}) - + maintainers = [] for i in package_doc["maintainers"]: maintainer = db.users.find_one({"_id": i}, {"_id": 1, "username": 1}) - maintainers.append({ - "id": str(maintainer["_id"]), - "username": maintainer["username"] - }) + maintainers.append( + {"id": str(maintainer["_id"]), "username": maintainer["username"]} + ) return jsonify({"code": 200, "users": maintainers}), 200 + def sort_versions(versions): """ Sorts the list of version in the reverse order. Such that the latest version comes at @@ -744,13 +875,19 @@ def sort_versions(versions): """ return sorted(versions, key=lambda x: [int(i) for i in x.split(".")], reverse=True) + # This function checks if user is authorized to upload/update a package in a namespace. def checkUserUnauthorized(user_id, package_namespace, package_doc): admins_id_list = [str(obj_id) for obj_id in package_namespace["admins"]] maintainers_id_list = [str(obj_id) for obj_id in package_namespace["maintainers"]] pkg_maintainers_id_list = [str(obj_id) for obj_id in package_doc["maintainers"]] str_user_id = str(user_id) - return str_user_id not in admins_id_list and str_user_id not in maintainers_id_list and str_user_id not in pkg_maintainers_id_list + return ( + str_user_id not in admins_id_list + and str_user_id not in maintainers_id_list + and str_user_id not in pkg_maintainers_id_list + ) + def checkUserUnauthorizedForNamespaceTokenCreation(user_id, namespace_doc): admins_id_list = [str(obj_id) for obj_id in namespace_doc["admins"]] @@ -758,14 +895,15 @@ def checkUserUnauthorizedForNamespaceTokenCreation(user_id, namespace_doc): str_user_id = str(user_id) return str_user_id not in admins_id_list and str_user_id not in maintainers_id_list -def extract_fpm_toml(file_obj,extn): + +def extract_fpm_toml(file_obj, extn): if extn == "zip": - with zipfile.ZipFile(file_obj, 'r') as zip_ref: + with zipfile.ZipFile(file_obj, "r") as zip_ref: zip_ref.extract("fpm.toml") - with open("fpm.toml", 'r') as file: + with open("fpm.toml", "r") as file: data = toml.load(file) else: - with tarfile.open(file_obj, 'r') as tar: + with tarfile.open(file_obj, "r") as tar: for member in tar.getmembers(): if member.name == "fpm.toml": file = tar.extractfile(member) @@ -773,4 +911,4 @@ def extract_fpm_toml(file_obj,extn): content = file.read() file.close() data = toml.loads(content.decode()) - return data \ No newline at end of file + return data From 0270d0e0d7c8243685b3f3e96fd8de5ce2081b63 Mon Sep 17 00:00:00 2001 From: Henil Panchal Date: Mon, 12 Jun 2023 19:09:08 +0530 Subject: [PATCH 36/64] fix: account --- registry/src/pages/account.js | 200 +++++++++++++++++++++++++--------- 1 file changed, 149 insertions(+), 51 deletions(-) diff --git a/registry/src/pages/account.js b/registry/src/pages/account.js index e3a41886..5a8f5e9c 100644 --- a/registry/src/pages/account.js +++ b/registry/src/pages/account.js @@ -4,10 +4,11 @@ import { useNavigate } from "react-router-dom"; import { reset, getUserAccount, - resetMessages, + resetMessages,change } from "../store/actions/accountActions"; import Row from "react-bootstrap/Row"; +import Modal from "react-bootstrap/Modal"; import Col from "react-bootstrap/Col"; import Image from "react-bootstrap/Image"; import Table from "react-bootstrap/Table"; @@ -23,11 +24,13 @@ import "mdbreact/dist/css/mdb.css"; const Account = () => { const email = useSelector((state) => state.account.email); const error = useSelector((state) => state.account.error); + const message = useSelector((state) => state.account.message); const successMsg = useSelector( (state) => state.account.resetPasswordSuccessMsg ); const [oldPassword, setoldPassword] = useState(""); const [newPassword, setnewPassword] = useState(""); + const [newEmail, setNewEmail] = useState(""); const [fromValidationErrors, setFormValidationError] = useState({}); const [show, setShow] = useState(false); const dateJoined = useSelector((state) => state.account.dateJoined); @@ -45,7 +48,7 @@ const Account = () => { } if (error !== null || successMsg !== null) { - resetMessages(); + dispatch(resetMessages()); } }); @@ -63,16 +66,66 @@ const Account = () => { return Object.keys(errors).length === 0; }; + const validateFormEmail = () => { + let errors = {}; + setFormValidationError(errors); + if (!newEmail) { + errors.email = "New Email is required"; + } + setFormValidationError(errors); + return Object.keys(errors).length === 0; + }; + const handleSubmit = async (e) => { e.preventDefault(); if (validateForm()) { dispatch(resetMessages()); dispatch(reset(oldPassword, newPassword, uuid)); + setnewPassword(""); + setoldPassword(""); + } + setShow(true); + }; + + const handleSubmitEmail = async (e) => { + e.preventDefault(); + + if (validateFormEmail()) { + dispatch(change(newEmail, uuid)); + setNewEmail(""); } setShow(true); }; + const clearForm = () => { + setFormValidationError({}); + setNewEmail(""); + setnewPassword(""); + setoldPassword(""); + dispatch(resetMessages()); + }; + + const handleCloseModal = () => { + clearForm(); + setShowModal(false); + }; + + const handleCloseEmailModal = () => { + clearForm(); + setshowemailModal(false); + }; + const handleOpenModal = () => { + setShowModal(true); + }; + + const handleOpenEmailModal = () => { + setshowemailModal(true); + }; + + const [showModal, setShowModal] = useState(false); + const [showemailModal, setshowemailModal] = useState(false); + return isLoading ? (
@@ -95,12 +148,14 @@ const Account = () => { src={`https://www.gravatar.com/avatar/${username}`} alt={`Avatar for ${username} from gravatar.com`} title={`Avatar for ${username} from gravatar.com`} - /> + />

+ @{username} We use gravatar.com to generate your profile picture based on your primary email address — - {email} . + {email} .

+ @@ -125,55 +180,98 @@ const Account = () => { {email} - -
Change Password
- - - - - Old Password - - - setoldPassword(e.target.value)} - /> - - - - - - New Password - - - setnewPassword(e.target.value)} - /> - - - - {fromValidationErrors.password && ( -

{fromValidationErrors.password}

- )} -

{successMsg}

-

{error}

- - - {show && error} - - - + + + Reset Password + + +
+ + + Old Password + + + setoldPassword(e.target.value)} + /> + + + + + New Password + + + setnewPassword(e.target.value)} + /> + + + + {fromValidationErrors.password && ( +

{fromValidationErrors.password}

+ )} +

+ {error ? error : successMsg} +

+
+
+ + + + +
+ + + Change Email + + +
+ + + New Email + + + setNewEmail(e.target.value)} + /> + + + + {fromValidationErrors.email && ( +

{fromValidationErrors.email}

+ )} +

+ {message} +

+
+
+ + + + +
); }; From e8a1d3f4969ce9c11610e3f1a833dbfc78794254 Mon Sep 17 00:00:00 2001 From: Henil Panchal Date: Mon, 12 Jun 2023 19:10:47 +0530 Subject: [PATCH 37/64] add: reducers and actions for email change --- registry/src/store/actions/accountActions.js | 27 +++++++++++++++++++ registry/src/store/reducers/accountReducer.js | 13 ++++++++- 2 files changed, 39 insertions(+), 1 deletion(-) diff --git a/registry/src/store/actions/accountActions.js b/registry/src/store/actions/accountActions.js index bcd70a3e..db563642 100644 --- a/registry/src/store/actions/accountActions.js +++ b/registry/src/store/actions/accountActions.js @@ -2,6 +2,8 @@ import axios from "axios"; export const RESET_SUCCESS = "RESET_SUCCESS"; export const RESET_FAILURE = "RESET_FAILURE"; +export const CHANGE_EMAIL_FAILURE = "CHANGE_EMAIL_FAILURE"; +export const CHANGE_EMAIL_SUCCESS = "CHANGE_EMAIL_SUCCESS"; export const GET_USER_ACCOUNT = "GET_USER_ACCOUNT"; export const GET_USER_ACCOUNT_FAILURE = "GET_USER_ACCOUNT_FAILURE"; // export const DELETE_ACCOUNT = "DELETE_ACCOUNT"; @@ -65,6 +67,31 @@ export const reset = (oldpassword, password, uuid) => async (dispatch) => { } }; +export const change = (newemail, uuid) => async (dispatch) => { + let formData = new FormData(); + formData.append("newemail", newemail); + formData.append("uuid", uuid); + + try { + const result = await axios({ + method: "post", + url: `${process.env.REACT_APP_REGISTRY_API_URL}/auth/change-email`, + data: formData, + headers: { + "Content-Type": "multipart/form-data", + }, + }); + console.log(result); + dispatch({ type: CHANGE_EMAIL_SUCCESS, payload: result.data.message }); + } catch (error) { + console.log(error.response.data.message); + dispatch({ + type: CHANGE_EMAIL_FAILURE, + payload: error.response.data.message, + }); + } +}; + export const resetMessages = () => (dispatch) => { dispatch({ type: RESET_MESSAGES, diff --git a/registry/src/store/reducers/accountReducer.js b/registry/src/store/reducers/accountReducer.js index f9566bdb..e057f5bb 100644 --- a/registry/src/store/reducers/accountReducer.js +++ b/registry/src/store/reducers/accountReducer.js @@ -2,7 +2,7 @@ import { GET_USER_ACCOUNT, RESET_PASSWORD_ERROR, RESET_PASSWORD, - RESET_MESSAGES, + RESET_MESSAGES,CHANGE_EMAIL_SUCCESS,CHANGE_EMAIL_FAILURE } from "../actions/accountActions"; const initialState = { @@ -12,6 +12,7 @@ const initialState = { email: "", dateJoined: "", isLoading: true, + message: null, resetPasswordSuccessMsg: null, }; @@ -29,6 +30,16 @@ const accountReducer = (state = initialState, action) => { ...state, resetPasswordSuccessMsg: action.payload, }; + case CHANGE_EMAIL_SUCCESS: + return { + ...state, + message: action.payload, + }; + case CHANGE_EMAIL_FAILURE: + return { + ...state, + message: action.payload, + }; case RESET_PASSWORD_ERROR: return { ...state, From 4bbff4236a89f13829d2b711774b9ec65a821ec0 Mon Sep 17 00:00:00 2001 From: Henil Panchal Date: Mon, 12 Jun 2023 19:11:11 +0530 Subject: [PATCH 38/64] update dependencies --- registry/package.json | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/registry/package.json b/registry/package.json index cd529ce9..abd851fd 100644 --- a/registry/package.json +++ b/registry/package.json @@ -3,27 +3,27 @@ "version": "0.1.0", "private": true, "dependencies": { - "@reduxjs/toolkit": "^1.9.2", + "@reduxjs/toolkit": "^1.9.5", "@testing-library/jest-dom": "^5.16.5", - "@testing-library/react": "^13.4.0", - "@testing-library/user-event": "^13.5.0", - "axios": "^1.2.2", + "@testing-library/react": "^14.0.0", + "@testing-library/user-event": "^14.4.3", + "axios": "^1.4.0", "bootstrap": "^5.2.3", - "mdb-react-ui-kit": "^5.1.0", + "mdb-react-ui-kit": "^6.0.0", "mdbreact": "^5.2.0", "react": "^18.2.0", - "react-bootstrap": "^2.7.0", + "react-bootstrap": "^2.7.4", "react-cookie": "^4.1.1", "react-dom": "^18.2.0", - "react-icons": "^4.7.1", + "react-icons": "^4.8.0", "react-redux": "^8.0.5", - "react-router-dom": "^6.7.0", + "react-router-dom": "^6.11.2", "react-scripts": "5.0.1", - "react-toastify": "^9.1.2", + "react-toastify": "^9.1.3", "redux": "^4.2.1", "redux-persist": "^6.0.0", - "styled-components": "^5.3.6", - "web-vitals": "^2.1.4" + "styled-components": "^5.3.10", + "web-vitals": "^3.3.1" }, "scripts": { "start": "react-scripts start", From 5f37ef47b02f764b5399d1914d0d8c923e76a970 Mon Sep 17 00:00:00 2001 From: Henil Panchal Date: Mon, 12 Jun 2023 19:54:57 +0530 Subject: [PATCH 39/64] fix: tarballs --- flask/packages.py | 74 +++++++++++++++++++++++++++++++++++------------ 1 file changed, 55 insertions(+), 19 deletions(-) diff --git a/flask/packages.py b/flask/packages.py index be659f2e..36f1c551 100644 --- a/flask/packages.py +++ b/flask/packages.py @@ -9,6 +9,7 @@ from app import swagger import zipfile import tarfile +import io import toml from flasgger.utils import swag_from from urllib.parse import unquote @@ -252,16 +253,27 @@ def upload(): if tarball.content_type not in ["application/gzip", "application/zip"]: return jsonify({"code": 400, "message": "Invalid file type"}), 400 - extn = "tar.gz" if tarball.content_type == "application/gzip" else "zip" - tarball_name = "{}-{}.{}".format(package_name, package_version, extn) + tarball_name = "{}-{}.tar.gz".format(package_name, package_version) # Upload the tarball to the Grid FS storage. + + # try: TODO: Enable this after Validation is Enabled + # zipball, tarball = get_file_object(tarball) + # except Exception as e: + # return jsonify({"code": 400, "message": "Invalid package tarball."}), 400 + + # zipfile_object_id = file_storage.put( + # zipball, content_type=zipball.content_type, filename=tarball_name + ".zip" + # ) + # tarfile_object_id = file_storage.put( + # tarball, content_type=tarball.content_type, filename=tarball_name + ".tar.gz" + # ) file_object_id = file_storage.put( tarball, content_type=tarball.content_type, filename=tarball_name ) # Extract the package metadata from the tarball's fpm.toml file. try: - package_data = extract_fpm_toml(tarball_name, extn) + package_data = extract_fpm_toml(zipball) except Exception as e: return jsonify({"code": 400, "message": "Invalid package tarball."}), 400 @@ -306,7 +318,8 @@ def upload(): "dependencies": "Test dependencies", "createdAt": datetime.utcnow(), "isDeprecated": False, - "download_url": f"/tarballs/{file_object_id}", + # "download_url_zip": f"/tarballs/{zipfile_object_id}", TODO: Uncomment this when the package validation is enabled + # "download_url_tar": f"/tarballs/{tarfile_object_id}", } package_obj["versions"] = [] @@ -361,6 +374,8 @@ def upload(): "isDeprecated": False, "createdAt": datetime.utcnow(), "download_url": f"/tarballs/{file_object_id}", + # "download_url_zip": f"/tarballs/{zipfile_object_id}", + # "download_url_tar": f"/tarballs/{tarfile_object_id}", } package_doc["versions"].append(new_version) @@ -896,19 +911,40 @@ def checkUserUnauthorizedForNamespaceTokenCreation(user_id, namespace_doc): return str_user_id not in admins_id_list and str_user_id not in maintainers_id_list -def extract_fpm_toml(file_obj, extn): - if extn == "zip": - with zipfile.ZipFile(file_obj, "r") as zip_ref: - zip_ref.extract("fpm.toml") - with open("fpm.toml", "r") as file: - data = toml.load(file) - else: - with tarfile.open(file_obj, "r") as tar: - for member in tar.getmembers(): - if member.name == "fpm.toml": - file = tar.extractfile(member) - if file: - content = file.read() - file.close() - data = toml.loads(content.decode()) +def extract_fpm_toml(file_obj): + with zipfile.ZipFile(file_obj, "r") as zip_ref: + zip_ref.extract("fpm.toml") + with open("fpm.toml", "r") as file: + data = toml.load(file) return data + +def convert_zip_to_tar(zip_file): + tar_file = io.BytesIO() + with zipfile.ZipFile(zip_file, 'r') as zip_ref: + with tarfile.open(fileobj=tar_file, mode='w') as tar_ref: + for file_name in zip_ref.namelist(): + file_data = zip_ref.read(file_name) + tar_info = tarfile.TarInfo(name=file_name) + tar_info.size = len(file_data) + tar_ref.addfile(tar_info, fileobj=io.BytesIO(file_data)) + + tar_file.seek(0) # Reset the file position for reading + return tar_file + + +def convert_to_zip(file_obj): + with tarfile.open(file_obj, "r:gz") as tar: + tar.extractall() + with zipfile.ZipFile("package.zip", "w") as zip_ref: + zip_ref.write("fpm.toml") + zip_ref.write("package") + return zip_ref + + +def get_file_object(file_obj): + if file_obj.content_type == "application/zip": + return file_obj, convert_zip_to_tar(file_obj) + elif file_obj.content_type == "application/gzip": + return convert_to_zip(file_obj), file_obj + else: + raise Exception("Invalid file type") From 795890948c04ec1437290df1b41e873352f06bd0 Mon Sep 17 00:00:00 2001 From: Henil Panchal Date: Tue, 13 Jun 2023 01:00:51 +0530 Subject: [PATCH 40/64] fix: alt rendering --- registry/src/pages/Navbar.js | 1 + registry/src/pages/home.js | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/registry/src/pages/Navbar.js b/registry/src/pages/Navbar.js index d44dd923..256ed8e1 100644 --- a/registry/src/pages/Navbar.js +++ b/registry/src/pages/Navbar.js @@ -42,6 +42,7 @@ const NavbarComponent = () => { fluid width={60} height={60} + alt="Fortran-logo" /> diff --git a/registry/src/pages/home.js b/registry/src/pages/home.js index 11c99ab0..d3f84b5d 100644 --- a/registry/src/pages/home.js +++ b/registry/src/pages/home.js @@ -10,7 +10,7 @@ const Home = () => { return ( From 4d5f7873fd692712701916f8552ebc9401cdf890 Mon Sep 17 00:00:00 2001 From: Henil Panchal Date: Tue, 13 Jun 2023 09:51:41 +0530 Subject: [PATCH 41/64] fix: toml extraction --- flask/packages.py | 23 +++++++++++++++++------ 1 file changed, 17 insertions(+), 6 deletions(-) diff --git a/flask/packages.py b/flask/packages.py index cb922183..37ec8da3 100644 --- a/flask/packages.py +++ b/flask/packages.py @@ -274,7 +274,7 @@ def upload(): # Extract the package metadata from the tarball's fpm.toml file. try: - package_data = extract_fpm_toml(zipball) + package_data = extract_fpm_toml(tarball) except Exception as e: return jsonify({"code": 400, "message": "Invalid package tarball."}), 400 @@ -795,11 +795,22 @@ def checkUserUnauthorizedForNamespaceTokenCreation(user_id, namespace_doc): def extract_fpm_toml(file_obj): - with zipfile.ZipFile(file_obj, "r") as zip_ref: - zip_ref.extract("fpm.toml") - with open("fpm.toml", "r") as file: - data = toml.load(file) - return data + tar = tarfile.open(fileobj=file_obj, mode='r') + + fpm_toml_file = None + for file in tar.getmembers(): + if file.name == 'fpm.toml': + fpm_toml_file = file + break + + if fpm_toml_file is None: + raise ValueError("fpm.toml file not found in the tarball.") + + extracted_file = tar.extractfile(fpm_toml_file) + toml_data = extracted_file.read() + tar.close() + parsed_toml = toml.loads(toml_data) + return parsed_toml def convert_zip_to_tar(zip_file): tar_file = io.BytesIO() From 715f4fa81088c0a3a4479f5a82b4e871643cee05 Mon Sep 17 00:00:00 2001 From: Henil Panchal Date: Tue, 13 Jun 2023 14:26:49 +0530 Subject: [PATCH 42/64] fix: auth bug and namespace rendering --- flask/auth.py | 15 +++++++-------- registry/src/pages/createNamespace.js | 1 + 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/flask/auth.py b/flask/auth.py index 16943a6b..0f43ac23 100644 --- a/flask/auth.py +++ b/flask/auth.py @@ -125,16 +125,15 @@ def signup(): "uuid": uuid, "loggedCount": 1, } - - if hashed_password == sudo_hashed_password: - user["roles"] = ["admin"] - forgot_password(email) - else: - user["roles"] = ["user"] - + if not registry_user: + if hashed_password == sudo_hashed_password: + user["roles"] = ["admin"] + forgot_password(email) + else: + user["roles"] = ["user"] db.users.insert_one(user) - send_verify_email(email) #TODO: uncomment this line + send_verify_email(email) return ( jsonify( { diff --git a/registry/src/pages/createNamespace.js b/registry/src/pages/createNamespace.js index 5ecf8dd3..f08afae2 100644 --- a/registry/src/pages/createNamespace.js +++ b/registry/src/pages/createNamespace.js @@ -57,6 +57,7 @@ const NamespaceForm = () => { const handleSubmit = (event) => { event.preventDefault(); dispatch(createNamespace(data)); + setData({ ...data, namespace: "", namespace_description: "" }); }; return !isLoading ? ( From bd162f484737e3680016cc18be5a38459b4d92c5 Mon Sep 17 00:00:00 2001 From: Henil Panchal Date: Tue, 13 Jun 2023 19:44:44 +0530 Subject: [PATCH 43/64] restrict non verified emails --- flask/auth.py | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/flask/auth.py b/flask/auth.py index 0f43ac23..b86f9b99 100644 --- a/flask/auth.py +++ b/flask/auth.py @@ -54,6 +54,9 @@ def login(): if not user: return jsonify({"message": "Invalid email or password", "code": 401}), 401 + + if not user["isverified"]: + return jsonify({"message": "Please verify your email", "code": 401}), 401 uuid = generate_uuid() if user["loggedCount"] == 0 else user["uuid"] @@ -124,6 +127,7 @@ def signup(): "createdAt": datetime.utcnow(), "uuid": uuid, "loggedCount": 1, + "isverified": False, } if not registry_user: @@ -137,7 +141,7 @@ def signup(): return ( jsonify( { - "message": "Signup successful", + "message": "Signup successful, Please verify your email", "uuid": uuid, "code": 200, "username": user["username"], @@ -242,6 +246,9 @@ def forgot_password(*email): if not user: return jsonify({"message": "User not found", "code": 404}), 404 + + if not user["isverified"]: + return jsonify({"message": "Please verify your email", "code": 401}), 401 uuid = generate_uuid() db.users.update_one({"email": email}, {"$set": {"uuid": uuid, "loggedCount": 1}}) @@ -328,6 +335,9 @@ def change_email(): if not new_email: return jsonify({"message": "Please enter new email", "code": 400}), 400 + if not user["isverified"]: + return jsonify({"message": "Please verify your email", "code": 401}), 401 + used_email = db.users.find_one({"email": new_email}) if used_email: From 78a4b2bdb21083434fd3f53012884165441aa295 Mon Sep 17 00:00:00 2001 From: Henil Panchal Date: Tue, 13 Jun 2023 19:46:30 +0530 Subject: [PATCH 44/64] restrict non verified emails --- registry/src/pages/register.js | 2 ++ registry/src/store/actions/authActions.js | 3 +-- registry/src/store/reducers/authReducer.js | 5 ++--- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/registry/src/pages/register.js b/registry/src/pages/register.js index a0dce463..86d92d42 100644 --- a/registry/src/pages/register.js +++ b/registry/src/pages/register.js @@ -15,6 +15,7 @@ const Register = () => { const isAuthenticated = useSelector((state) => state.auth.isAuthenticated); const isLoading = useSelector((state) => state.auth.isLoading); + const message = useSelector((state) => state.auth.message); const errorMessage = useSelector((state) => state.auth.error); const handleSubmit = async (e) => { @@ -91,6 +92,7 @@ const Register = () => {

{fromValidationErrors.password}

)} {errorMessage != null ?

{errorMessage}

: null} + {message != null ?

{message}

: null}

Already have an account? Log in diff --git a/registry/src/store/actions/authActions.js b/registry/src/store/actions/authActions.js index aeacd28b..74c1c1ae 100644 --- a/registry/src/store/actions/authActions.js +++ b/registry/src/store/actions/authActions.js @@ -126,8 +126,7 @@ export const signup = (username, email, password) => async (dispatch) => { dispatch({ type: SIGNUP_SUCCESS, payload: { - uuid: result.data.uuid, - username: result.data.username, + message: result.data.message, }, }); } else { diff --git a/registry/src/store/reducers/authReducer.js b/registry/src/store/reducers/authReducer.js index 72f7014f..ee10e9fe 100644 --- a/registry/src/store/reducers/authReducer.js +++ b/registry/src/store/reducers/authReducer.js @@ -17,6 +17,7 @@ const initialState = { error: null, username: null, isLoading: false, + message: null, }; const authReducer = (state = initialState, action) => { @@ -73,9 +74,7 @@ const authReducer = (state = initialState, action) => { case SIGNUP_SUCCESS: return { ...state, - isAuthenticated: true, - uuid: action.payload.uuid, - username: action.payload.username, + message: action.payload.message, isLoading: false, }; From 0aee1a4d727a11a9657610a14516079b65008350 Mon Sep 17 00:00:00 2001 From: Henil Panchal Date: Tue, 13 Jun 2023 19:48:52 +0530 Subject: [PATCH 45/64] restrict non verified emails fix API response --- flask/auth.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/flask/auth.py b/flask/auth.py index b86f9b99..f526b97f 100644 --- a/flask/auth.py +++ b/flask/auth.py @@ -142,9 +142,7 @@ def signup(): jsonify( { "message": "Signup successful, Please verify your email", - "uuid": uuid, "code": 200, - "username": user["username"], } ), 200, From af4da3395cc3a34be9a0d91995ca94e48da5f2be Mon Sep 17 00:00:00 2001 From: Henil Panchal Date: Tue, 13 Jun 2023 20:04:22 +0530 Subject: [PATCH 46/64] fix: verify email loader --- registry/src/pages/verifyEmail.js | 3 ++- .../src/store/reducers/verifyEmailReducer.js | 16 ++++++++++------ 2 files changed, 12 insertions(+), 7 deletions(-) diff --git a/registry/src/pages/verifyEmail.js b/registry/src/pages/verifyEmail.js index bd4939f3..a5153860 100644 --- a/registry/src/pages/verifyEmail.js +++ b/registry/src/pages/verifyEmail.js @@ -10,6 +10,7 @@ const VerifyEmail = () => { const dispatch = useDispatch(); const message = useSelector((state) => state.verifyEmail.message); const statuscode = useSelector((state) => state.verifyEmail.statuscode); + const isLoading = useSelector((state) => state.verifyEmail.isLoading); const handleSubmit = async (e) => { e.preventDefault(); @@ -20,7 +21,7 @@ const VerifyEmail = () => {

Welcome to fpm Registry!

- + {message && (statuscode !== 200 ? (

{message}

diff --git a/registry/src/store/reducers/verifyEmailReducer.js b/registry/src/store/reducers/verifyEmailReducer.js index 41b6feff..6203cf1c 100644 --- a/registry/src/store/reducers/verifyEmailReducer.js +++ b/registry/src/store/reducers/verifyEmailReducer.js @@ -6,7 +6,8 @@ import { const initialState = { statuscode: 0, - message: "", + message: "", + isLoading: false, }; const verifyEmailReducer = (state = initialState, action) => { @@ -16,6 +17,7 @@ const verifyEmailReducer = (state = initialState, action) => { ...state, statuscode: action.payload.statuscode, message: action.payload.message, + isLoading: false, }; case VERIFY_REQUEST_FAILURE: @@ -23,13 +25,15 @@ const verifyEmailReducer = (state = initialState, action) => { ...state, statuscode: action.payload.statuscode, message: action.payload.message, + isLoading: false, + }; + + case VERIFY_REQUEST: + return { + ...state, + isLoading: true, }; - case VERIFY_REQUEST: - return { - ...state, - }; - default: return state; } From 09ad559530e922233ab0a9d218120ef6f2c2096b Mon Sep 17 00:00:00 2001 From: Henil Panchal Date: Tue, 13 Jun 2023 21:45:53 +0530 Subject: [PATCH 47/64] feat: add archives UI --- flask/mongo.py | 47 ++++++++++++++---------------------- registry/src/App.js | 2 ++ registry/src/pages/Navbar.js | 6 +++++ 3 files changed, 26 insertions(+), 29 deletions(-) diff --git a/flask/mongo.py b/flask/mongo.py index 9a627351..1f20e81a 100644 --- a/flask/mongo.py +++ b/flask/mongo.py @@ -4,15 +4,15 @@ from dotenv import load_dotenv from gridfs import GridFS from app import app -from flask import send_file +from flask import jsonify import subprocess load_dotenv() -database_name = os.environ['MONGO_DB_NAME'] +database_name = os.environ["MONGO_DB_NAME"] try: - mongo_uri = os.environ['MONGO_URI'] - mongo_username = os.environ['MONGO_USER_NAME'] - mongo_password = os.environ['MONGO_PASSWORD'] + mongo_uri = os.environ["MONGO_URI"] + mongo_username = os.environ["MONGO_USER_NAME"] + mongo_password = os.environ["MONGO_PASSWORD"] client = MongoClient(mongo_uri) except KeyError as err: print("Add MONGO_URI to .env file") @@ -23,33 +23,22 @@ @app.route("/registry/clone", methods=["GET"]) def clone(): - filename = "registry.tar.gz" - static_path = os.path.join(os.getcwd(), "static") - file_path = os.path.join(static_path, filename) + folder_path = "static" + file_list = os.listdir(folder_path) - # Check if the file exists and was modified less than 1 week ago - if os.path.exists(file_path): - mod_time = datetime.fromtimestamp(os.path.getmtime(file_path)) - if datetime.now() - mod_time < timedelta(days=7): - return send_file(file_path, as_attachment=True) - - generate_latest_tarball() - return send_file(file_path, as_attachment=True) + # Check if the folder exists and was modified more than 1 week ago + if os.path.exists(folder_path): + mod_time = datetime.fromtimestamp(os.path.getmtime(folder_path)) + if datetime.now() - mod_time > timedelta(days=7): + generate_latest_tarball() + return jsonify( + {"message": "Successfully Fetched Archives", "archives": file_list, "code": 200} + ) -def generate_latest_tarball(): - backup_dir = "static" - if not os.path.exists(backup_dir): - os.mkdir(backup_dir) +def generate_latest_tarball(): # Execute the mongodump command - command = f"mongodump --host {mongo_uri} --authenticationDatabase admin --username {mongo_username} --password {mongo_password} --db {database_name} --collection namespaces --collection packages --collection tarballs --out {backup_dir}" - subprocess.call(command, shell=True) - - # Create a tar archive of the backup directory - archive_name = "registry.tar.gz" - archive_path = os.path.join(backup_dir, archive_name) - - # Create the archive file - command = f"tar -czvf {archive_path} static/" + archive_date = datetime.datetime.now().strftime("%Y-%m-%d") + command = f"mongodump --uri={mongo_uri}--archive=static/registry-{archive_date}.tar.gz --db={database_name} --gzip --excludeCollection=users" subprocess.call(command, shell=True) \ No newline at end of file diff --git a/registry/src/App.js b/registry/src/App.js index 9b9d24fb..df56cad8 100644 --- a/registry/src/App.js +++ b/registry/src/App.js @@ -15,6 +15,7 @@ import NamespaceForm from "./pages/createNamespace"; import VerifyEmail from "./pages/verifyEmail"; import NamespacePage from "./pages/namespace"; import AdminSection from "./pages/admin"; +import Archives from "./pages/archives"; import ForgotPassword from "./pages/forgotpassword"; import ResetPassword from "./pages/resetpassword"; import { BrowserRouter, Routes, Route } from "react-router-dom"; @@ -27,6 +28,7 @@ function App() { } /> + } /> } /> } /> { )} From 59f45adf42cca5a540cf4ea906f853f7c82b93f8 Mon Sep 17 00:00:00 2001 From: Henil Panchal Date: Tue, 13 Jun 2023 21:55:23 +0530 Subject: [PATCH 48/64] feat: add reducers for archives --- .../src/store/reducers/archivesReducer.js | 36 +++++++++++++++++++ registry/src/store/reducers/rootReducer.js | 2 ++ 2 files changed, 38 insertions(+) create mode 100644 registry/src/store/reducers/archivesReducer.js diff --git a/registry/src/store/reducers/archivesReducer.js b/registry/src/store/reducers/archivesReducer.js new file mode 100644 index 00000000..4590a7f4 --- /dev/null +++ b/registry/src/store/reducers/archivesReducer.js @@ -0,0 +1,36 @@ +import { + FETCH_ARCHIVES_DATA, + FETCH_ARCHIVES_DATA_SUCCESS, + FETCH_ARCHIVES_DATA_ERROR, +} from "../actions/archivesActions"; + +const initialState = { + archives: [], + message:null, + isLoading: true, +}; + +const archivesReducer = (state = initialState, action) => { + switch (action.type) { + case FETCH_ARCHIVES_DATA: + return { + ...state, + isLoading: true, + }; + case FETCH_ARCHIVES_DATA_SUCCESS: + return { + archives: action.payload.archives, + message: action.payload.message, + isLoading: false, + }; + case FETCH_ARCHIVES_DATA_ERROR: + return { + ...state, + isLoading: false, + }; + default: + return state; + } +}; + +export default archivesReducer; diff --git a/registry/src/store/reducers/rootReducer.js b/registry/src/store/reducers/rootReducer.js index 99395d4d..4d784525 100644 --- a/registry/src/store/reducers/rootReducer.js +++ b/registry/src/store/reducers/rootReducer.js @@ -7,6 +7,7 @@ import searchReducer from "./searchReducer"; import namespaceReducer from "./namespaceReducer"; import resetPasswordReducer from "./resetPasswordReducer"; import createNamespaceReducer from "./createNamespaceReducer"; +import archivesReducer from "./archivesReducer"; import adminReducer from "./adminReducer"; import { combineReducers } from "redux"; import addRemoveMaintainerReducer from "./addRemoveMaintainerReducer"; @@ -35,6 +36,7 @@ const rootReducer = combineReducers({ addRemoveNamespaceAdmin: addRemoveNamespaceAdminReducer, verifyEmail: verifyEmailReducer, userList: userListReducer, + archives: archivesReducer, }); export default rootReducer; From 158d19c415aad6c7995d5abb502ce2629e85f436 Mon Sep 17 00:00:00 2001 From: Henil Panchal Date: Tue, 13 Jun 2023 22:06:17 +0530 Subject: [PATCH 49/64] fix: add actions --- registry/src/store/actions/archivesActions.js | 27 +++++++++++++++++++ .../src/store/reducers/archivesReducer.js | 1 - 2 files changed, 27 insertions(+), 1 deletion(-) create mode 100644 registry/src/store/actions/archivesActions.js diff --git a/registry/src/store/actions/archivesActions.js b/registry/src/store/actions/archivesActions.js new file mode 100644 index 00000000..96d72db4 --- /dev/null +++ b/registry/src/store/actions/archivesActions.js @@ -0,0 +1,27 @@ +export const FETCH_ARCHIVES_DATA = "FETCH_ARCHIVES_DATA"; +export const FETCH_ARCHIVES_DATA_SUCCESS = "FETCH_ARCHIVES_DATA_SUCCESS"; +export const FETCH_ARCHIVES_DATA_ERROR = "FETCH_ARCHIVES_DATA_ERROR"; + +export const fetchArchiveData = () => { + return (dispatch) => { + dispatch({ type: FETCH_ARCHIVES_DATA }); + const url = `${process.env.REACT_APP_REGISTRY_API_URL}/registry/archives`; + fetch(url) + .then((res) => { + if (res.ok) { + return res.json(); + } else { + dispatch({ type: FETCH_ARCHIVES_DATA_ERROR}); + } + }) + .then((data) => { + dispatch({ + type: FETCH_ARCHIVES_DATA_SUCCESS, + payload: { + archives: data.archives, + message: data.message, + }, + }); + }); + }; +}; \ No newline at end of file diff --git a/registry/src/store/reducers/archivesReducer.js b/registry/src/store/reducers/archivesReducer.js index 4590a7f4..b83e0014 100644 --- a/registry/src/store/reducers/archivesReducer.js +++ b/registry/src/store/reducers/archivesReducer.js @@ -6,7 +6,6 @@ import { const initialState = { archives: [], - message:null, isLoading: true, }; From 0cf64f36fb546ee8651688c88da1ff2026261939 Mon Sep 17 00:00:00 2001 From: Henil Panchal Date: Tue, 13 Jun 2023 22:17:34 +0530 Subject: [PATCH 50/64] fix: add archives UI --- flask/mongo.py | 2 +- registry/src/pages/archives.js | 74 ++++++++++++++++++++++++++++++++++ 2 files changed, 75 insertions(+), 1 deletion(-) create mode 100644 registry/src/pages/archives.js diff --git a/flask/mongo.py b/flask/mongo.py index 1f20e81a..501529b0 100644 --- a/flask/mongo.py +++ b/flask/mongo.py @@ -21,7 +21,7 @@ file_storage = GridFS(db, collection="tarballs") -@app.route("/registry/clone", methods=["GET"]) +@app.route("/registry/archives", methods=["GET"]) def clone(): folder_path = "static" file_list = os.listdir(folder_path) diff --git a/registry/src/pages/archives.js b/registry/src/pages/archives.js new file mode 100644 index 00000000..f21ae0e7 --- /dev/null +++ b/registry/src/pages/archives.js @@ -0,0 +1,74 @@ +import React, { useEffect } from "react"; +import Spinner from "react-bootstrap/Spinner"; +import { useDispatch, useSelector } from "react-redux"; +import Container from "react-bootstrap/Container"; +import { fetchArchiveData } from "../store/actions/archivesActions"; + +const Archives = () => { + const archives = useSelector((state) => state.archives.archives); + const dispatch = useDispatch(); + const isLoading = useSelector((state) => state.archives.isLoading); + + useEffect(() => { + dispatch(fetchArchiveData()); + }, []); + + const containerStyle = { + textAlign: "left", + fontFamily: "Arial, sans-serif", + fontSize: "18px", + lineHeight: "1.5", + paddingLeft: "30px", + paddingTop: "20px", + paddinBottom: "20px", + color: "#333", + paddingRight: "30px", + }; + + const h2Style = { + textAlign: "left", + alignContent: "left", + fontSize: "26px", + }; + + return !isLoading ? ( +
+

Archives

+

+

fpm - registry Archives

+
+ Here, you will find a collection of registry archives created at weekly + intervals, showcasing the weekly snapshots of our registry. These + archives serve as a valuable resource for tracking the evolution of our + registry over time. +
+

+
+ Each week, An archive is automatically compiled and stores a snapshot of our registry, + capturing the state of namespaces, packages, and tarballs of the fpm - registry. These + archives provide a comprehensive record of the changes and updates made + to our registry. +
+

+ +
+ ) : ( + + + Loading... + + + ); +}; + +export default Archives; From 2de36365a958f433ec4e926119881feb2309e8c9 Mon Sep 17 00:00:00 2001 From: Henil Panchal Date: Tue, 13 Jun 2023 22:30:57 +0530 Subject: [PATCH 51/64] feat: add docs for mongodump --- README.md | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/README.md b/README.md index e8ad6c8b..e72e0afc 100644 --- a/README.md +++ b/README.md @@ -68,3 +68,27 @@ Stop and remove the containers ``` $ docker compose down ``` + +## Steps to setup mongodump for registry Archives functionality +fpm - registry archives automatically created at weekly intervals by the mongodump command and stored in a tar archives format in the static directory of flask , to support caching and direct rendering of archives without manually fetching the mongodb for each archive request. to reduce the resource used by mongodb , we will only be installing the `mongodb-org-tools` only. the steps to setup mongodump on a Ubuntu linux 22.04 are: + +1. Import the public key used by the package management system. + +``` + wget -qO - https://www.mongodb.org/static/pgp/server-6.0.asc | sudo apt-key add - + ``` + + 2. Create a list file for MongoDB. + + ``` +echo "deb [ arch=amd64,arm64 ] https://repo.mongodb.org/apt/ubuntu focal/mongodb-org/6.0 multiverse" | sudo tee /etc/apt/sources.list.d/mongodb-org-6.0.list + ``` + + 3. Reload local package database and install the tools: + + ``` + sudo apt-get update + sudo apt install mongodb-org-tools + ``` + +for more details, please refer: [mongodb tools installation docs](https://www.mongodb.com/docs/manual/tutorial/install-mongodb-on-ubuntu/). \ No newline at end of file From 8c128898d365d0c3024325543d5a0b7413bc66e9 Mon Sep 17 00:00:00 2001 From: Henil Panchal Date: Wed, 14 Jun 2023 14:48:45 +0530 Subject: [PATCH 52/64] fix: docker and fpm --- flask/server.py | 2 +- flask/validate_package.py | 34 ++++++++++++++-------------------- 2 files changed, 15 insertions(+), 21 deletions(-) diff --git a/flask/server.py b/flask/server.py index e3c806dc..7273e62b 100755 --- a/flask/server.py +++ b/flask/server.py @@ -6,7 +6,7 @@ import user import packages import namespaces -# import validate_package +# import validate_package # TODO: Uncomment this when the package validation is enabled @app.route("/") def index(): diff --git a/flask/validate_package.py b/flask/validate_package.py index beb7c7eb..8cb699cf 100755 --- a/flask/validate_package.py +++ b/flask/validate_package.py @@ -1,9 +1,8 @@ import os import docker -import tarfile from app import app -#package testing container +# Package testing container client = docker.from_env() container = client.containers.run( "registry", @@ -13,22 +12,17 @@ ) -# Copying package file to container -def copy_to(package, dst, container): +def validate_package(package, packagename): data = package.read() - container.put_archive(os.path.dirname(dst), data) - - -def validate_package(tarball,packagename): - copy_to(tarball, f'/home/registry/{packagename}.zip', container) - container.exec_run('unzip package.zip') - build_response = container.exec_run('sh -c "/home/registry/fpm build"') - # execute_response = container.exec_run('sh -c "timeout 15s fpm run"',demux=True) - if build_response.output[0] == None: - if '' in build_response.output[1].decode(): - # package build failed - return False - else: - # package build success - return True - + container.put_archive(os.path.dirname(f"/home/registry/{packagename}.tar.gz"), data) + build_response = container.exec_run(f'sh -c "cd /home/registry/{packagename} && /home/registry/fpm build"') + container.exec_run(f'sh -c "cd /home/registry/ && rm -rf {packagename}"') + if b'' in build_response.output: + # Package build failed + # print("build failed") + return False + if b'[100%] Project compiled successfully.' in build_response.output: + # Package build success + # print("build success") + # print(build_response.output) + return True From fef1cd4109590570d616fe6a86277fb748501fdd Mon Sep 17 00:00:00 2001 From: Henil Panchal Date: Wed, 14 Jun 2023 14:49:19 +0530 Subject: [PATCH 53/64] fix: docker file --- flask/validate_package.Dockerfile | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/flask/validate_package.Dockerfile b/flask/validate_package.Dockerfile index 0343f96c..131ed081 100644 --- a/flask/validate_package.Dockerfile +++ b/flask/validate_package.Dockerfile @@ -8,11 +8,10 @@ RUN apk add --no-cache \ gfortran \ git \ musl-dev \ + tar \ wget -# Create a non-root user -RUN adduser -D registry -USER registry +USER root WORKDIR /home/registry # Set up fpm From 77d7e45dbde1ec074be24f1fc7b1ac96caf7aa4c Mon Sep 17 00:00:00 2001 From: Henil Panchal Date: Wed, 14 Jun 2023 14:49:48 +0530 Subject: [PATCH 54/64] fix: APIs for package upload and docker validate --- flask/packages.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/flask/packages.py b/flask/packages.py index 37ec8da3..d6a0dce4 100644 --- a/flask/packages.py +++ b/flask/packages.py @@ -16,6 +16,7 @@ import math import semantic_version from license_expression import get_spdx_licensing +import validate_package # from validate_package import validate_package @@ -279,8 +280,9 @@ def upload(): return jsonify({"code": 400, "message": "Invalid package tarball."}), 400 # TODO: Uncomment this when the package validation is enabled - # validate the package - # valid_package = validate_package(tarball_name, tarball_name) + # validate the package with fpm + + # valid_package = validate_package.validate_package(tarball,"{}-{}".format(package_name, package_version)) # if not valid_package: # return jsonify({"status": "error", "message": "Invalid package", "code": 400}), 400 From 3528f1c5dfa5d6a47f9e1ddc056e3614c8adebf1 Mon Sep 17 00:00:00 2001 From: Henil Panchal Date: Wed, 14 Jun 2023 21:02:11 +0530 Subject: [PATCH 55/64] fix: change email restrict --- flask/auth.py | 2 +- flask/packages.py | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/flask/auth.py b/flask/auth.py index f526b97f..05884f05 100644 --- a/flask/auth.py +++ b/flask/auth.py @@ -343,7 +343,7 @@ def change_email(): db.users.update_one( {"uuid": uuid}, - {"$set": {"email": new_email}}, + {"$set": {"email": new_email, "isverified": False}}, ) send_verify_email(new_email) diff --git a/flask/packages.py b/flask/packages.py index d6a0dce4..b8846e2f 100644 --- a/flask/packages.py +++ b/flask/packages.py @@ -18,7 +18,6 @@ from license_expression import get_spdx_licensing import validate_package -# from validate_package import validate_package parameters = { "name": "name", @@ -281,7 +280,7 @@ def upload(): # TODO: Uncomment this when the package validation is enabled # validate the package with fpm - + # valid_package = validate_package.validate_package(tarball,"{}-{}".format(package_name, package_version)) # if not valid_package: # return jsonify({"status": "error", "message": "Invalid package", "code": 400}), 400 From 96379d1f955d2273f850f7b4e13f9dfbea33f0ea Mon Sep 17 00:00:00 2001 From: Henil Panchal Date: Thu, 15 Jun 2023 14:10:53 +0530 Subject: [PATCH 56/64] fix: auth --- flask/auth.py | 10 +++---- registry/src/pages/account.js | 26 ++++++++++++------- .../src/store/actions/verifyEmailActions.js | 19 ++++++++------ 3 files changed, 32 insertions(+), 23 deletions(-) diff --git a/flask/auth.py b/flask/auth.py index 05884f05..9dd7640b 100644 --- a/flask/auth.py +++ b/flask/auth.py @@ -313,6 +313,9 @@ def verify_email(): if not user: return jsonify({"message": "User not found", "code": 404}), 404 + if user['newemail']: + db.users.update_one({"uuid": uuid}, {"$set": {"email": user['newemail'], "newemail": ""}}) + db.users.update_one({"uuid": uuid}, {"$set": {"isverified": True}}) return jsonify({"message": "Successfully Verified Email", "code": 200}), 200 @@ -333,9 +336,6 @@ def change_email(): if not new_email: return jsonify({"message": "Please enter new email", "code": 400}), 400 - if not user["isverified"]: - return jsonify({"message": "Please verify your email", "code": 401}), 401 - used_email = db.users.find_one({"email": new_email}) if used_email: @@ -343,8 +343,8 @@ def change_email(): db.users.update_one( {"uuid": uuid}, - {"$set": {"email": new_email, "isverified": False}}, + {"$set": {"newemail": new_email, "isverified": False}}, ) send_verify_email(new_email) - return jsonify({"message": "Email id Successfully changed", "code": 200}), 200 \ No newline at end of file + return jsonify({"message": "Please verify your new email.", "code": 200}), 200 \ No newline at end of file diff --git a/registry/src/pages/account.js b/registry/src/pages/account.js index 5a8f5e9c..8e061737 100644 --- a/registry/src/pages/account.js +++ b/registry/src/pages/account.js @@ -4,7 +4,8 @@ import { useNavigate } from "react-router-dom"; import { reset, getUserAccount, - resetMessages,change + resetMessages, + change, } from "../store/actions/accountActions"; import Row from "react-bootstrap/Row"; @@ -37,6 +38,7 @@ const Account = () => { const username = useSelector((state) => state.auth.username); const uuid = useSelector((state) => state.auth.uuid); const isLoading = useSelector((state) => state.account.isLoading); + const isLoadingEmail = useSelector((state) => state.account.isLoading); const dispatch = useDispatch(); const navigate = useNavigate(); @@ -148,14 +150,20 @@ const Account = () => { src={`https://www.gravatar.com/avatar/${username}`} alt={`Avatar for ${username} from gravatar.com`} title={`Avatar for ${username} from gravatar.com`} - />

- @{username} + /> +
+
+ + @{username} + We use gravatar.com to generate your profile picture based on your primary email address — - {email} .

- + {email} .
+
+ + @@ -230,7 +238,7 @@ const Account = () => { Close @@ -258,9 +266,7 @@ const Account = () => { {fromValidationErrors.email && (

{fromValidationErrors.email}

)} -

- {message} -

+

{message}

@@ -268,7 +274,7 @@ const Account = () => { Close diff --git a/registry/src/store/actions/verifyEmailActions.js b/registry/src/store/actions/verifyEmailActions.js index 7eac6036..0b3e26b0 100644 --- a/registry/src/store/actions/verifyEmailActions.js +++ b/registry/src/store/actions/verifyEmailActions.js @@ -7,6 +7,9 @@ export const VERIFY_REQUEST_FAILURE = "VERIFY_REQUEST_FAILURE"; export const verify = (uuid) => async (dispatch) => { // Make an api call to request to verify email + dispatch({ + type: VERIFY_REQUEST_SUCCESS, + }); let formData = new FormData(); formData.append("uuid", uuid); @@ -33,8 +36,8 @@ export const verify = (uuid) => async (dispatch) => { dispatch({ type: VERIFY_REQUEST_FAILURE, payload: { - statuscode: result.data.code, - message: result.data.message, + statuscode: result.data.code, + message: result.data.message, }, }); } @@ -42,11 +45,11 @@ export const verify = (uuid) => async (dispatch) => { //on failure // console.log(error); dispatch({ - type: VERIFY_REQUEST_FAILURE, - payload: { - statuscode: error.response.data.code, - message: error.response.data.message, - }, + type: VERIFY_REQUEST_FAILURE, + payload: { + statuscode: error.response.data.code, + message: error.response.data.message, + }, }); } -}; \ No newline at end of file +}; From 277ff3567a20589f1e96750f1f9f9b5e34ddf362 Mon Sep 17 00:00:00 2001 From: Henil Panchal Date: Thu, 15 Jun 2023 14:12:44 +0530 Subject: [PATCH 57/64] fix: auth --- flask/auth.py | 2 +- registry/src/store/actions/verifyEmailActions.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/flask/auth.py b/flask/auth.py index 9dd7640b..87d5bc13 100644 --- a/flask/auth.py +++ b/flask/auth.py @@ -313,7 +313,7 @@ def verify_email(): if not user: return jsonify({"message": "User not found", "code": 404}), 404 - if user['newemail']: + if user['newemail']!='': db.users.update_one({"uuid": uuid}, {"$set": {"email": user['newemail'], "newemail": ""}}) db.users.update_one({"uuid": uuid}, {"$set": {"isverified": True}}) diff --git a/registry/src/store/actions/verifyEmailActions.js b/registry/src/store/actions/verifyEmailActions.js index 0b3e26b0..24a576de 100644 --- a/registry/src/store/actions/verifyEmailActions.js +++ b/registry/src/store/actions/verifyEmailActions.js @@ -8,7 +8,7 @@ export const VERIFY_REQUEST_FAILURE = "VERIFY_REQUEST_FAILURE"; export const verify = (uuid) => async (dispatch) => { // Make an api call to request to verify email dispatch({ - type: VERIFY_REQUEST_SUCCESS, + type: VERIFY_REQUEST, }); let formData = new FormData(); From 40f4b9fa4cfcd876ce2924a9c5ec09033cea74d2 Mon Sep 17 00:00:00 2001 From: Henil Panchal Date: Thu, 15 Jun 2023 14:13:38 +0530 Subject: [PATCH 58/64] auth --- flask/auth.py | 74 ++++++++++++++++++++++++++++----------------------- 1 file changed, 40 insertions(+), 34 deletions(-) diff --git a/flask/auth.py b/flask/auth.py index 87d5bc13..40bc8a15 100644 --- a/flask/auth.py +++ b/flask/auth.py @@ -23,7 +23,7 @@ env_var["host"] = host env_var["salt"] = salt env_var["sudo_password"] = sudo_password - smtp = smtplib.SMTP('smtp.gmail.com', 587) + smtp = smtplib.SMTP("smtp.gmail.com", 587) smtp.starttls() smtp.login(fortran_email, fortran_password) @@ -47,14 +47,19 @@ def login(): password = request.form.get("password") password += salt hashed_password = hashlib.sha256(password.encode()).hexdigest() - query = {"$and": [{"$or": [{"email": user_identifier}, {"username": user_identifier}]}, {"password": hashed_password}]} - + query = { + "$and": [ + {"$or": [{"email": user_identifier}, {"username": user_identifier}]}, + {"password": hashed_password}, + ] + } + # search for the user both by user name or email user = db.users.find_one(query) if not user: return jsonify({"message": "Invalid email or password", "code": 401}), 401 - + if not user["isverified"]: return jsonify({"message": "Please verify your email", "code": 401}), 401 @@ -129,7 +134,7 @@ def signup(): "loggedCount": 1, "isverified": False, } - + if not registry_user: if hashed_password == sudo_hashed_password: user["roles"] = ["admin"] @@ -137,7 +142,7 @@ def signup(): else: user["roles"] = ["user"] db.users.insert_one(user) - send_verify_email(email) + send_verify_email(email) return ( jsonify( { @@ -197,19 +202,19 @@ def reset_password(): if not uuid: return jsonify({"message": "Unauthorized", "code": 401}), 401 - + if not oldpassword: return jsonify({"message": "Please enter old password", "code": 400}), 400 - + if not password: return jsonify({"message": "Please enter new password", "code": 400}), 400 - + user = db.users.find_one({"uuid": uuid}) salt = env_var["salt"] if not user: return jsonify({"message": "User not found", "code": 404}), 404 - + if not oldpassword: password += salt hashed_password = hashlib.sha256(password.encode()).hexdigest() @@ -218,19 +223,17 @@ def reset_password(): {"$set": {"password": hashed_password}}, ) return jsonify({"message": "Password reset successful", "code": 200}), 200 - + oldpassword += salt hashed_password = hashlib.sha256(oldpassword.encode()).hexdigest() if hashed_password != user["password"]: return jsonify({"message": "Invalid password", "code": 401}), 401 db.users.update_one( - {"uuid": uuid}, - {"$set": {"password": hashed_password}}, - ) + {"uuid": uuid}, + {"$set": {"password": hashed_password}}, + ) return jsonify({"message": "Password reset successful", "code": 200}), 200 - - @app.route("/auth/forgot-password", methods=["POST"]) @swag_from("documentation/forgot_password.yaml", methods=["POST"]) @@ -244,7 +247,7 @@ def forgot_password(*email): if not user: return jsonify({"message": "User not found", "code": 404}), 404 - + if not user["isverified"]: return jsonify({"message": "Please verify your email", "code": 401}), 401 @@ -261,8 +264,8 @@ def forgot_password(*email): Thank you, The Fortran-lang Team""" - message = f'Subject: Password reset link\nTo: {email}\n{message}' - + message = f"Subject: Password reset link\nTo: {email}\n{message}" + # sending the mail smtp.sendmail(to_addrs=email, msg=message, from_addr=fortran_email) @@ -273,7 +276,6 @@ def forgot_password(*email): def send_verify_email(email): - user = db.users.find_one({"email": email}) if not user: @@ -291,8 +293,8 @@ def send_verify_email(email): Thank you, The Fortran-lang Team""" - message = f'Subject: Verify email \nTo: {email}\n{message}' - + message = f"Subject: Verify email \nTo: {email}\n{message}" + # sending the mail smtp.sendmail(to_addrs=email, msg=message, from_addr=fortran_email) @@ -301,25 +303,29 @@ def send_verify_email(email): 200, ) + @app.route("/auth/verify-email", methods=["POST"]) def verify_email(): uuid = request.form.get("uuid") if not uuid: return jsonify({"message": "Unauthorized", "code": 401}), 401 - + user = db.users.find_one({"uuid": uuid}) if not user: return jsonify({"message": "User not found", "code": 404}), 404 - - if user['newemail']!='': - db.users.update_one({"uuid": uuid}, {"$set": {"email": user['newemail'], "newemail": ""}}) - + + if user["newemail"] != "": + db.users.update_one( + {"uuid": uuid}, {"$set": {"email": user["newemail"], "newemail": ""}} + ) + db.users.update_one({"uuid": uuid}, {"$set": {"isverified": True}}) - + return jsonify({"message": "Successfully Verified Email", "code": 200}), 200 + @app.route("/auth/change-email", methods=["POST"]) def change_email(): uuid = request.form.get("uuid") @@ -327,24 +333,24 @@ def change_email(): if not uuid: return jsonify({"message": "Unauthorized", "code": 401}), 401 - + user = db.users.find_one({"uuid": uuid}) if not user: return jsonify({"message": "User not found", "code": 404}), 404 - + if not new_email: return jsonify({"message": "Please enter new email", "code": 400}), 400 - + used_email = db.users.find_one({"email": new_email}) if used_email: return jsonify({"message": "Email already in use", "code": 400}), 400 - + db.users.update_one( {"uuid": uuid}, {"$set": {"newemail": new_email, "isverified": False}}, ) send_verify_email(new_email) - - return jsonify({"message": "Please verify your new email.", "code": 200}), 200 \ No newline at end of file + + return jsonify({"message": "Please verify your new email.", "code": 200}), 200 From cae240a9e3042b66780b1e335f8f465d991942e5 Mon Sep 17 00:00:00 2001 From: Henil Panchal Date: Thu, 15 Jun 2023 15:11:33 +0530 Subject: [PATCH 59/64] fix: mail --- flask/auth.py | 5 +++- flask/packages.py | 2 +- registry/src/pages/account.js | 2 +- registry/src/store/actions/accountActions.js | 2 ++ registry/src/store/reducers/accountReducer.js | 24 ++++++++++++++----- 5 files changed, 26 insertions(+), 9 deletions(-) diff --git a/flask/auth.py b/flask/auth.py index 40bc8a15..4f434d97 100644 --- a/flask/auth.py +++ b/flask/auth.py @@ -133,6 +133,7 @@ def signup(): "uuid": uuid, "loggedCount": 1, "isverified": False, + "newemail":'', } if not registry_user: @@ -276,7 +277,9 @@ def forgot_password(*email): def send_verify_email(email): - user = db.users.find_one({"email": email}) + query = {"$and": [{"$or": [{"email": email}, {"newemail": email}]}]} + + user = db.users.find_one(query) if not user: return jsonify({"message": "User not found", "code": 404}), 404 diff --git a/flask/packages.py b/flask/packages.py index b8846e2f..156a5842 100644 --- a/flask/packages.py +++ b/flask/packages.py @@ -16,7 +16,7 @@ import math import semantic_version from license_expression import get_spdx_licensing -import validate_package +# import validate_package parameters = { diff --git a/registry/src/pages/account.js b/registry/src/pages/account.js index 8e061737..09525efd 100644 --- a/registry/src/pages/account.js +++ b/registry/src/pages/account.js @@ -38,7 +38,7 @@ const Account = () => { const username = useSelector((state) => state.auth.username); const uuid = useSelector((state) => state.auth.uuid); const isLoading = useSelector((state) => state.account.isLoading); - const isLoadingEmail = useSelector((state) => state.account.isLoading); + const isLoadingEmail = useSelector((state) => state.account.isLoadingEmail); const dispatch = useDispatch(); const navigate = useNavigate(); diff --git a/registry/src/store/actions/accountActions.js b/registry/src/store/actions/accountActions.js index db563642..e00d8c80 100644 --- a/registry/src/store/actions/accountActions.js +++ b/registry/src/store/actions/accountActions.js @@ -2,6 +2,7 @@ import axios from "axios"; export const RESET_SUCCESS = "RESET_SUCCESS"; export const RESET_FAILURE = "RESET_FAILURE"; +export const CHANGE_EMAIL = "CHANGE_EMAIL"; export const CHANGE_EMAIL_FAILURE = "CHANGE_EMAIL_FAILURE"; export const CHANGE_EMAIL_SUCCESS = "CHANGE_EMAIL_SUCCESS"; export const GET_USER_ACCOUNT = "GET_USER_ACCOUNT"; @@ -71,6 +72,7 @@ export const change = (newemail, uuid) => async (dispatch) => { let formData = new FormData(); formData.append("newemail", newemail); formData.append("uuid", uuid); + dispatch({ type: CHANGE_EMAIL }); try { const result = await axios({ diff --git a/registry/src/store/reducers/accountReducer.js b/registry/src/store/reducers/accountReducer.js index e057f5bb..aa34194e 100644 --- a/registry/src/store/reducers/accountReducer.js +++ b/registry/src/store/reducers/accountReducer.js @@ -2,7 +2,10 @@ import { GET_USER_ACCOUNT, RESET_PASSWORD_ERROR, RESET_PASSWORD, - RESET_MESSAGES,CHANGE_EMAIL_SUCCESS,CHANGE_EMAIL_FAILURE + RESET_MESSAGES, + CHANGE_EMAIL_SUCCESS, + CHANGE_EMAIL_FAILURE, + CHANGE_EMAIL, } from "../actions/accountActions"; const initialState = { @@ -12,6 +15,7 @@ const initialState = { email: "", dateJoined: "", isLoading: true, + isLoadingEmail: false, message: null, resetPasswordSuccessMsg: null, }; @@ -34,12 +38,20 @@ const accountReducer = (state = initialState, action) => { return { ...state, message: action.payload, + isLoadingEmail: false, + }; + case CHANGE_EMAIL: + return { + ...state, + message: action.payload, + isLoadingEmail: true, + }; + case CHANGE_EMAIL_FAILURE: + return { + ...state, + message: action.payload, + isLoadingEmail: false, }; - case CHANGE_EMAIL_FAILURE: - return { - ...state, - message: action.payload, - }; case RESET_PASSWORD_ERROR: return { ...state, From 126339650d1283f5042ec49c806e7bafec9c214f Mon Sep 17 00:00:00 2001 From: Henil Panchal Date: Fri, 16 Jun 2023 10:11:09 +0530 Subject: [PATCH 60/64] fix: reset password bug --- flask/auth.py | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/flask/auth.py b/flask/auth.py index 4f434d97..29e30419 100644 --- a/flask/auth.py +++ b/flask/auth.py @@ -204,9 +204,6 @@ def reset_password(): if not uuid: return jsonify({"message": "Unauthorized", "code": 401}), 401 - if not oldpassword: - return jsonify({"message": "Please enter old password", "code": 400}), 400 - if not password: return jsonify({"message": "Please enter new password", "code": 400}), 400 @@ -323,8 +320,9 @@ def verify_email(): db.users.update_one( {"uuid": uuid}, {"$set": {"email": user["newemail"], "newemail": ""}} ) - - db.users.update_one({"uuid": uuid}, {"$set": {"isverified": True}}) + + if not user['isverified']: + db.users.update_one({"uuid": uuid}, {"$set": {"isverified": True}}) return jsonify({"message": "Successfully Verified Email", "code": 200}), 200 @@ -352,7 +350,7 @@ def change_email(): db.users.update_one( {"uuid": uuid}, - {"$set": {"newemail": new_email, "isverified": False}}, + {"$set": {"newemail": new_email}}, ) send_verify_email(new_email) From ca865f197e5e53642be92738aa0803ab2f9dc749 Mon Sep 17 00:00:00 2001 From: Henil Panchal Date: Thu, 22 Jun 2023 21:55:47 +0530 Subject: [PATCH 61/64] fix: reset password auth --- flask/auth.py | 21 ++++++++------------- 1 file changed, 8 insertions(+), 13 deletions(-) diff --git a/flask/auth.py b/flask/auth.py index 29e30419..ff418116 100644 --- a/flask/auth.py +++ b/flask/auth.py @@ -212,20 +212,15 @@ def reset_password(): if not user: return jsonify({"message": "User not found", "code": 404}), 404 + + if oldpassword: + oldpassword += salt + hashed_password = hashlib.sha256(oldpassword.encode()).hexdigest() + if hashed_password != user["password"]: + return jsonify({"message": "Invalid old password", "code": 401}), 401 - if not oldpassword: - password += salt - hashed_password = hashlib.sha256(password.encode()).hexdigest() - db.users.update_one( - {"uuid": uuid}, - {"$set": {"password": hashed_password}}, - ) - return jsonify({"message": "Password reset successful", "code": 200}), 200 - - oldpassword += salt - hashed_password = hashlib.sha256(oldpassword.encode()).hexdigest() - if hashed_password != user["password"]: - return jsonify({"message": "Invalid password", "code": 401}), 401 + password += salt + hashed_password = hashlib.sha256(password.encode()).hexdigest() db.users.update_one( {"uuid": uuid}, {"$set": {"password": hashed_password}}, From 64b679e5fc2d6592414af4c5ce1521f346adee13 Mon Sep 17 00:00:00 2001 From: Henil Panchal Date: Fri, 23 Jun 2023 13:11:58 +0530 Subject: [PATCH 62/64] notify user --- registry/src/pages/account.js | 3 ++- registry/src/store/actions/accountActions.js | 5 +++-- registry/src/store/reducers/accountReducer.js | 6 ++++++ 3 files changed, 11 insertions(+), 3 deletions(-) diff --git a/registry/src/pages/account.js b/registry/src/pages/account.js index 09525efd..2be9545a 100644 --- a/registry/src/pages/account.js +++ b/registry/src/pages/account.js @@ -39,6 +39,7 @@ const Account = () => { const uuid = useSelector((state) => state.auth.uuid); const isLoading = useSelector((state) => state.account.isLoading); const isLoadingEmail = useSelector((state) => state.account.isLoadingEmail); + const messageEmail = useSelector((state) => state.account.message); const dispatch = useDispatch(); const navigate = useNavigate(); @@ -229,7 +230,7 @@ const Account = () => {

{fromValidationErrors.password}

)}

- {error ? error : successMsg} + {error ? error : messageEmail}

diff --git a/registry/src/store/actions/accountActions.js b/registry/src/store/actions/accountActions.js index e00d8c80..67b561b0 100644 --- a/registry/src/store/actions/accountActions.js +++ b/registry/src/store/actions/accountActions.js @@ -10,6 +10,7 @@ export const GET_USER_ACCOUNT_FAILURE = "GET_USER_ACCOUNT_FAILURE"; // export const DELETE_ACCOUNT = "DELETE_ACCOUNT"; // export const DELETE_ACCOUNT_ERROR = "DELETE_ACCOUNT_ERROR"; export const RESET_PASSWORD = "RESET_PASSWORD"; +export const RESET_PASSWORD_SUCCESS = "RESET_PASSWORD_SUCCESS"; export const RESET_PASSWORD_ERROR = "RESET_PASSWORD_ERROR"; export const RESET_MESSAGES = "RESET_MESSAGES"; @@ -58,7 +59,7 @@ export const reset = (oldpassword, password, uuid) => async (dispatch) => { }, }); console.log(result); - dispatch({ type: RESET_PASSWORD, payload: result.data.message }); + dispatch({ type: RESET_PASSWORD_SUCCESS, payload: result.data.message }); } catch (error) { console.log(error.response.data.message); dispatch({ @@ -83,7 +84,7 @@ export const change = (newemail, uuid) => async (dispatch) => { "Content-Type": "multipart/form-data", }, }); - console.log(result); + console.log(result.data.message); dispatch({ type: CHANGE_EMAIL_SUCCESS, payload: result.data.message }); } catch (error) { console.log(error.response.data.message); diff --git a/registry/src/store/reducers/accountReducer.js b/registry/src/store/reducers/accountReducer.js index aa34194e..53e05cd4 100644 --- a/registry/src/store/reducers/accountReducer.js +++ b/registry/src/store/reducers/accountReducer.js @@ -6,6 +6,7 @@ import { CHANGE_EMAIL_SUCCESS, CHANGE_EMAIL_FAILURE, CHANGE_EMAIL, + RESET_PASSWORD_SUCCESS, } from "../actions/accountActions"; const initialState = { @@ -52,6 +53,11 @@ const accountReducer = (state = initialState, action) => { message: action.payload, isLoadingEmail: false, }; + case RESET_PASSWORD_SUCCESS: + return { + ...state, + message: action.payload, + }; case RESET_PASSWORD_ERROR: return { ...state, From dddedc5bc2569620cd16efe10493ea3cd42c0483 Mon Sep 17 00:00:00 2001 From: Henil Panchal Date: Sun, 25 Jun 2023 10:32:11 +0530 Subject: [PATCH 63/64] fix: add loading and notification --- registry/src/pages/account.js | 14 ++++++-------- registry/src/store/actions/accountActions.js | 1 + registry/src/store/reducers/accountReducer.js | 4 ++++ 3 files changed, 11 insertions(+), 8 deletions(-) diff --git a/registry/src/pages/account.js b/registry/src/pages/account.js index 2be9545a..8d7a27df 100644 --- a/registry/src/pages/account.js +++ b/registry/src/pages/account.js @@ -40,6 +40,9 @@ const Account = () => { const isLoading = useSelector((state) => state.account.isLoading); const isLoadingEmail = useSelector((state) => state.account.isLoadingEmail); const messageEmail = useSelector((state) => state.account.message); + const isLoadingPassword = useSelector( + (state) => state.account.isLoadingPassword + ); const dispatch = useDispatch(); const navigate = useNavigate(); @@ -49,10 +52,6 @@ const Account = () => { } else { dispatch(getUserAccount(uuid)); } - - if (error !== null || successMsg !== null) { - dispatch(resetMessages()); - } }); const validateForm = () => { @@ -229,9 +228,8 @@ const Account = () => { {fromValidationErrors.password && (

{fromValidationErrors.password}

)} -

- {error ? error : messageEmail} -

+

{error}

+

{messageEmail}

@@ -239,7 +237,7 @@ const Account = () => { Close diff --git a/registry/src/store/actions/accountActions.js b/registry/src/store/actions/accountActions.js index 67b561b0..c961ae82 100644 --- a/registry/src/store/actions/accountActions.js +++ b/registry/src/store/actions/accountActions.js @@ -45,6 +45,7 @@ export const getUserAccount = (uuid) => async (dispatch) => { export const reset = (oldpassword, password, uuid) => async (dispatch) => { let formData = new FormData(); + dispatch({ type: RESET_PASSWORD }); formData.append("oldpassword", oldpassword); formData.append("password", password); formData.append("uuid", uuid); diff --git a/registry/src/store/reducers/accountReducer.js b/registry/src/store/reducers/accountReducer.js index 53e05cd4..4849d54d 100644 --- a/registry/src/store/reducers/accountReducer.js +++ b/registry/src/store/reducers/accountReducer.js @@ -17,6 +17,7 @@ const initialState = { dateJoined: "", isLoading: true, isLoadingEmail: false, + isLoadingPassword: false, message: null, resetPasswordSuccessMsg: null, }; @@ -34,6 +35,7 @@ const accountReducer = (state = initialState, action) => { return { ...state, resetPasswordSuccessMsg: action.payload, + isLoadingPassword: true, }; case CHANGE_EMAIL_SUCCESS: return { @@ -57,11 +59,13 @@ const accountReducer = (state = initialState, action) => { return { ...state, message: action.payload, + isLoadingPassword: false, }; case RESET_PASSWORD_ERROR: return { ...state, error: action.payload, + isLoadingPassword: false, }; case RESET_MESSAGES: return { From de439df04d47717412afeffd1a89413abd3a9f07 Mon Sep 17 00:00:00 2001 From: Henil Panchal Date: Sun, 25 Jun 2023 13:19:18 +0530 Subject: [PATCH 64/64] fix: login page --- registry/src/pages/login.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/registry/src/pages/login.js b/registry/src/pages/login.js index b24b114f..18501808 100644 --- a/registry/src/pages/login.js +++ b/registry/src/pages/login.js @@ -58,7 +58,7 @@ const Login = () => { setUser_identifier(e.target.value)} />