Repository: inventree/InvenTree

Issue:
[FR] Add email link based logins
### Please verify that this feature request has NOT been suggested before.

- [X] I checked and didn't find similar feature request

### Problem statement

We currently have no password-less forms of logging in

### Suggested solution

Implement magic links like slack provides.
Ref: https://github.com/aaugustin/django-sesame

### Describe alternatives you've considered

Allauth options are also good for now

### Examples of other systems

Slack

### Do you want to develop this?

- [X] I want to develop this.


Discussion:
Using WebAuthn can be a good alternative for passwordless logins.
https://github.com/inventree/InvenTree/issues/2326#issuecomment-1299080622
@rkalman WebAuthn has nothing to do with link based logins (often called magic links) and should be its separate FR if you want it.

WebAuthn is an additional factor for login while link-based logins replace the login entirely with a mail with a short lived token for single-use login. The first is used to add security while the latter is mostly used for users that seldomly use a platform.

Resolving patch:
diff --git a/InvenTree/InvenTree/urls.py b/InvenTree/InvenTree/urls.py
--- a/InvenTree/InvenTree/urls.py
+++ b/InvenTree/InvenTree/urls.py
@@ -33,6 +33,7 @@
 from stock.api import stock_api_urls
 from stock.urls import stock_urls
 from users.api import user_urls
+from web.urls import spa_view
 from web.urls import urlpatterns as platform_urls
 
 from .api import APISearchView, InfoView, NotFoundView
@@ -210,7 +211,7 @@
 new_frontendpatterns = [
     # Platform urls
     re_path(r'^platform/', include(platform_urls)),
-
+    re_path(r'^platform', spa_view, name='platform'),
 ]
 
 # Load patterns for frontend according to settings
diff --git a/InvenTree/web/urls.py b/InvenTree/web/urls.py
--- a/InvenTree/web/urls.py
+++ b/InvenTree/web/urls.py
@@ -22,5 +22,6 @@ def get(self, request, *args, **kwargs):
 urlpatterns = [
     path("assets/<path:path>", RedirectAssetView.as_view()),
     re_path(r"^(?P<path>.*)/$", spa_view),
+    path("set-password?uid=<uid>&token=<token>", spa_view, name="password_reset_confirm"),
     path("", spa_view),
 ]


Test patch:
diff --git a/src/frontend/tests/classic.spec.ts b/src/frontend/tests/classic.spec.ts
--- a/src/frontend/tests/classic.spec.ts
+++ b/src/frontend/tests/classic.spec.ts
@@ -1,5 +1,4 @@
 import { expect, test } from '@playwright/test';
-import exp from 'constants';
 
 test('Check classic index site', async ({ page }) => {
   await page.goto('./api/');
diff --git a/src/frontend/tests/ui_plattform.spec.ts b/src/frontend/tests/ui_plattform.spec.ts
--- a/src/frontend/tests/ui_plattform.spec.ts
+++ b/src/frontend/tests/ui_plattform.spec.ts
@@ -1,20 +1,15 @@
 import { expect, test } from '@playwright/test';
 
 test('Basic Platform UI test', async ({ page }) => {
-  await page.goto('./index/');
-  await expect(page).toHaveTitle('InvenTree Demo Server | Sign In');
-  await expect(
-    page.getByRole('heading', { name: 'InvenTree Demo Server' })
-  ).toBeVisible();
-
+  await page.goto('./platform/');
+  await expect(page).toHaveTitle('InvenTree Demo Server');
+  await page.waitForURL('**/platform/');
   await page.getByLabel('username').fill('allaccess');
   await page.getByLabel('password').fill('nolimits');
-  await page.click('button', { text: 'Sign In' });
-
+  await page.getByRole('button', { name: 'Log in' }).click();
+  await page.waitForURL('**/platform/home');
   await page.goto('./platform/');
 
   await expect(page).toHaveTitle('InvenTree Demo Server');
-  await expect(
-    page.getByRole('heading', { name: 'Welcome to the new frontend!' })
-  ).toBeVisible();
+  await expect(page.getByText('Home').nth(1)).toBeVisible();
 });