strapi-plugin-magic-link-v5 4.0.4 → 4.0.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,28 @@
1
+ {
2
+ "externals": [
3
+ "react",
4
+ "react-dom",
5
+ "react-router-dom",
6
+ "react-intl",
7
+ "styled-components",
8
+ "formik",
9
+ "react-query",
10
+ "prop-types",
11
+ "yup",
12
+ "@strapi/strapi",
13
+ "@strapi/design-system",
14
+ "@strapi/icons"
15
+ ],
16
+ "output": {
17
+ "globals": {
18
+ "react": "React",
19
+ "react-dom": "ReactDOM",
20
+ "react-router-dom": "ReactRouterDom",
21
+ "react-intl": "ReactIntl",
22
+ "styled-components": "styled",
23
+ "@strapi/strapi": "Strapi",
24
+ "@strapi/design-system": "StrapiDesignSystem",
25
+ "@strapi/icons": "StrapiIcons"
26
+ }
27
+ }
28
+ }
package/README.md CHANGED
@@ -1,333 +1,3 @@
1
- # Magic Link - Passwordless Authentication for Strapi
1
+ # strapi-plugin-magic-link-v5
2
2
 
3
- A secure passwordless authentication solution for Strapi, allowing users to log in via email links without requiring passwords.
4
-
5
- ![Magic Link Overview](pics/pic6.png)
6
-
7
- ## Core Features
8
-
9
- - **Passwordless Authentication**: Login via secure email links
10
- - **Token Management**: Admin dashboard for managing and monitoring tokens
11
- - **JWT Session Tracking**: Monitor and manage active sessions
12
- - **Security Features**: IP banning, token expiration controls
13
- - **Admin Interface**: Statistics dashboard and configuration options
14
-
15
- ![Core Features](pics/pic7.png)
16
-
17
- ## Installation
18
-
19
- ```bash
20
- # Using npm
21
- npm install strapi-plugin-magic-link-v5
22
-
23
- # Using yarn
24
- yarn add strapi-plugin-magic-link-v5
25
-
26
- # Install directly from GitHub
27
- npm install begservice/strapi-magic-link
28
- # or
29
- yarn add begservice/strapi-magic-link
30
- ```
31
-
32
- After installation, restart your Strapi server and the plugin will be available in the admin panel.
33
-
34
- ## How It Works
35
-
36
- ### Email User with Login Link
37
-
38
- 1. **Request Process**:
39
-
40
- - User requests a login link by entering their email
41
- - System generates a secure token and sends an email
42
- - Email contains a magic link with the token
43
-
44
- 2. **Token Details**:
45
- - Cryptographically secure random tokens
46
- - Configurable expiration time
47
- - Option for one-time use or reusable tokens
48
- - Tracks IP address and user agent information
49
-
50
- ### Login with Token
51
-
52
- 1. **Authentication Process**:
53
-
54
- - User clicks the link in their email
55
- - System verifies the token is valid and not expired
56
- - User is automatically authenticated
57
- - JWT token is generated for the session
58
-
59
- 2. **Security Measures**:
60
- - IP address validation (optional)
61
- - Token expiration
62
- - One-time use tokens (configurable)
63
- - Automatic blocking after failed attempts
64
-
65
- ![Authentication Process](pics/pic8.png)
66
-
67
- ## Configuration
68
-
69
- Configure the plugin through **Settings > Magic Link** in the Strapi admin panel:
70
-
71
- ![Configuration Interface](pics/pic9.png)
72
-
73
- ### General Settings
74
-
75
- - **Enable Magic Link**: Turn the feature on/off
76
- - **Create New Users**: Automatically create users if they don't exist
77
- - **Token Stays Valid**: Configure one-time use or reusable tokens
78
- - **Expiration Period**: Set how long tokens remain valid
79
- - **Token Length**: Configure the security level of tokens
80
-
81
- ### Authentication Settings
82
-
83
- - **Default Role**: Select which user role is assigned to new users
84
- - **JWT Token Expiration**: Define how long JWT tokens remain valid (e.g., 30d, 24h)
85
- - **Store Login Info**: Enable tracking of user agents and IP addresses
86
- - **Remember Me**: Allow users to stay logged in for extended periods
87
-
88
- ### Email Settings
89
-
90
- - **Sender Information**: Configure the email sender details (name, email)
91
- - **Reply-To Address**: Set a reply-to email address for support inquiries
92
- - **Email Subject**: Customize the subject line of the magic link emails
93
- - **Email Templates**: Customize HTML and text templates
94
- - **Email Designer Integration**: Use with Email Designer 5 if installed
95
-
96
- ## Dashboard & Admin Interface
97
-
98
- Magic Link provides a comprehensive admin interface with several key sections:
99
-
100
- ![Admin Dashboard](pics/pic10.png)
101
-
102
- ### Dashboard Overview
103
-
104
- - **Security Score**: Dynamic score (0-100) showing your current security configuration
105
- - **Active Tokens**: Count and management of currently active tokens
106
- - **Token Usage**: Metrics on token creation and usage patterns
107
- - **Users Using Magic Link**: Number of unique users authenticating via magic links
108
- - **Tokens About to Expire**: Warning system for tokens expiring soon
109
-
110
- ### Token Management
111
-
112
- - **Token List View**: Sortable and filterable list of all tokens
113
- - **Token Status Indicators**: Visual indicators showing token status (active, expired, used)
114
- - **Token Details**: Inspect complete token information including:
115
- - Creation and expiration dates
116
- - IP address and user agent information
117
- - Usage history and context data
118
- - **Bulk Actions**: Select multiple tokens for batch operations
119
- - **Search & Filter**: Find tokens by email, status, or creation date
120
- - **Token Operations**:
121
- - Block/deactivate tokens
122
- - Extend token expiration
123
- - Delete tokens
124
-
125
- ### JWT Session Management
126
-
127
- - **Active Sessions**: Monitor all active JWT sessions across your application
128
- - **Session Revocation**: Ability to revoke any active session immediately
129
- - **Session Details**: View complete session information including:
130
- - User details
131
- - Creation and expiration time
132
- - Last activity
133
- - IP address and device information
134
-
135
- ### Security Features
136
-
137
- - **IP Ban Management**: View and manage banned IP addresses
138
- - **IP Ban Controls**: Ban suspicious IPs and view associated tokens
139
- - **Security Audit**: Track login attempts and security events
140
- - **System Status**: Monitor the health of the plugin and its dependencies
141
-
142
- ### Token Creation Interface
143
-
144
- - **Manual Token Creation**: Generate tokens for specific users
145
- - **Email Control**: Option to send or not send the email with the token
146
- - **Context Injection**: Add custom JSON context data to tokens
147
- - **Email Validation**: Verify email existence before token creation
148
-
149
- ## Admin Features
150
-
151
- ### Token Management
152
-
153
- - View all active/inactive tokens
154
- - Block or activate individual tokens
155
- - Delete expired tokens
156
- - See token usage statistics
157
-
158
- ### Security Dashboard
159
-
160
- - Security score based on your configuration
161
- - IP banning for suspicious activity
162
- - JWT session monitoring and management
163
- - Token expiration warnings
164
-
165
- ## API Endpoints
166
-
167
- - `POST /api/magic-link/send` - Generate and send a magic link
168
- - `GET /api/magic-link/login?loginToken=xxx` - Authenticate with token
169
- - `GET /api/magic-link/tokens` - List tokens (admin only)
170
- - `GET /api/magic-link/jwt-sessions` - List active sessions (admin only)
171
- - `POST /api/magic-link/tokens/:id/block` - Block a specific token
172
- - `POST /api/magic-link/ban-ip` - Ban an IP address
173
-
174
- ## Frontend Implementation
175
-
176
- ```javascript
177
- // Request a magic link
178
- const requestLogin = async (email) => {
179
- try {
180
- await axios.post("/api/magic-link/send", { email });
181
- // Show success message
182
- } catch (error) {
183
- // Handle error
184
- }
185
- };
186
-
187
- // Verify token on the callback page
188
- const verifyToken = async () => {
189
- const token = new URLSearchParams(window.location.search).get("loginToken");
190
- if (token) {
191
- try {
192
- const response = await axios.get(
193
- `/api/magic-link/login?loginToken=${token}`
194
- );
195
- // Store JWT and redirect user
196
- localStorage.setItem("token", response.data.jwt);
197
- window.location.href = "/dashboard";
198
- } catch (error) {
199
- // Handle invalid token
200
- }
201
- }
202
- };
203
- ```
204
-
205
- ## Context Data
206
-
207
- You can include additional context when sending a magic link:
208
-
209
- ```javascript
210
- await axios.post("/api/magic-link/send", {
211
- email: "user@example.com",
212
- context: {
213
- redirectUrl: "/dashboard",
214
- source: "registration",
215
- },
216
- });
217
- ```
218
-
219
- ## Security Best Practices
220
-
221
- - Set reasonable token expiration times
222
- - Use one-time tokens for sensitive operations
223
- - Regularly monitor the security dashboard
224
- - Ban suspicious IP addresses promptly
225
-
226
- ## Troubleshooting
227
-
228
- | Issue | Solution |
229
- | ---------------------- | ---------------------------------------------- |
230
- | Emails not sending | Check your Strapi email provider configuration |
231
- | Token validation fails | Verify token expiration settings |
232
- | User not found errors | Check "Create New Users" setting |
233
-
234
- ## License
235
-
236
- [MIT](LICENSE)
237
-
238
- ## Development & Contributing
239
-
240
- ### Release Process
241
-
242
- This project uses [semantic-release](https://github.com/semantic-release/semantic-release) to automate version management and package publishing.
243
-
244
- To create a new release:
245
-
246
- ```bash
247
- npx semantic-release
248
- ```
249
-
250
- This will automatically:
251
-
252
- 1. Analyze commit messages since the last release
253
- 2. Determine the appropriate version bump (major, minor, or patch)
254
- 3. Generate release notes
255
- 4. Update the version in package.json
256
- 5. Create a new Git tag
257
- 6. Publish the package to npm
258
-
259
- ### Commit Guidelines
260
-
261
- To ensure semantic-release can properly determine the next version number, please follow these commit message conventions:
262
-
263
- #### Commit Message Format
264
-
265
- Each commit message consists of a **header**, an optional **body**, and an optional **footer**:
266
-
267
- ```
268
- <type>(<scope>): <subject>
269
- <BLANK LINE>
270
- <body>
271
- <BLANK LINE>
272
- <footer>
273
- ```
274
-
275
- The **header** is mandatory and must conform to the following format:
276
-
277
- - **type**: What type of change this commit is making. Must be one of:
278
-
279
- - `feat`: A new feature (triggers a minor release)
280
- - `fix`: A bug fix (triggers a patch release)
281
- - `docs`: Documentation changes only
282
- - `style`: Changes that don't affect code functionality (formatting, etc.)
283
- - `refactor`: Code changes that neither fix a bug nor add a feature
284
- - `perf`: Performance improvements
285
- - `test`: Adding or updating tests
286
- - `chore`: Changes to build process or auxiliary tools
287
- - `ci`: Changes to CI configuration files and scripts
288
-
289
- - **scope**: Optional, can be anything specifying the place of the commit change (e.g., `admin`, `api`, `auth`)
290
-
291
- - **subject**: Brief description of the change
292
-
293
- #### Breaking Changes
294
-
295
- For breaking changes, add `BREAKING CHANGE:` in the footer of the commit message or append a `!` after the type/scope:
296
-
297
- ```
298
- feat(api)!: completely restructure API endpoints
299
-
300
- BREAKING CHANGE: The API endpoints have been completely restructured.
301
- ```
302
-
303
- This will trigger a major version bump.
304
-
305
- #### Examples
306
-
307
- ```
308
- feat(auth): add support for multiple roles
309
-
310
- fix(email): correct template rendering issue
311
-
312
- docs: update API documentation
313
-
314
- chore(deps): update dependencies
315
-
316
- fix!: critical security vulnerability in token validation
317
- ```
318
-
319
- ### Branching Strategy
320
-
321
- - `main`: Production-ready code
322
- - `develop`: Integration branch for features
323
- - `feature/*`: New features
324
- - `fix/*`: Bug fixes
325
- - `docs/*`: Documentation changes
326
-
327
- When creating PRs, always merge feature branches into `develop` first. The `develop` branch is periodically merged into `main` for releases.
328
-
329
- ---
330
-
331
- ## Support
332
-
333
- If you encounter issues or have questions, please check the admin documentation or open an issue.
3
+ Magic Link Authentication for Strapi v5
@@ -1,98 +1,82 @@
1
- import { prefixPluginTranslations } from '@strapi/strapi/admin';
2
- import pluginPkg from '../../package.json';
3
- import pluginId from './pluginId';
4
- import Initializer from './components/Initializer';
5
- import PluginIcon from './components/PluginIcon';
6
- import pluginPermissions from './permissions';
1
+ // @ts-check
2
+ import { prefixPluginTranslations } from '@strapi/helper-plugin';
3
+ import { PLUGIN_ID } from './pluginId';
7
4
  import getTrad from './utils/getTrad';
5
+ import reducers from './reducers';
6
+ import permissions from './permissions';
7
+ import PluginIcon from './components/PluginIcon/index.jsx';
8
+ import pluginId from './pluginId';
8
9
 
9
- const name = pluginPkg.strapi.name;
10
+ const name = 'strapi-plugin-magic-link-v5';
11
+
12
+ const icon = PluginIcon;
10
13
 
11
14
  export default {
12
15
  register(app) {
13
- app.addMenuLink({
14
- to: `/plugins/${pluginId}`,
15
- icon: PluginIcon,
16
- intlLabel: {
17
- id: `${pluginId}.plugin.name`,
18
- defaultMessage: name,
19
- },
20
- Component: () => import('./pages/HomePage').then(module => ({
21
- default: module.default
22
- }))
23
- });
16
+ app.addReducers(reducers);
24
17
 
25
18
  app.addMenuLink({
26
- to: `/plugins/${pluginId}/tokens`,
27
- icon: PluginIcon,
19
+ to: `/plugins/${PLUGIN_ID}`,
20
+ icon,
28
21
  intlLabel: {
29
- id: getTrad('tokens.title'),
30
- defaultMessage: 'Magic Link Tokens',
22
+ id: `${PLUGIN_ID}.plugin.name`,
23
+ defaultMessage: 'Magic Link',
31
24
  },
32
- Component: () => import('./pages/Tokens').then(module => ({
33
- default: module.default
34
- }))
25
+ Component: async () => {
26
+ const component = await import('./pages/App');
27
+ return component;
28
+ },
29
+ permissions: [
30
+ {
31
+ action: `plugin::${pluginId}.read`,
32
+ subject: null,
33
+ },
34
+ ],
35
35
  });
36
36
 
37
37
  app.createSettingSection(
38
38
  {
39
39
  id: pluginId,
40
40
  intlLabel: {
41
- id: getTrad('Header.Settings'),
41
+ id: getTrad('Settings.header'),
42
42
  defaultMessage: 'Magic Link',
43
43
  },
44
44
  },
45
45
  [
46
46
  {
47
+ id: 'strapi-plugin-magic-link-v5-settings',
47
48
  intlLabel: {
48
- id: getTrad('Form.title.Settings'),
49
+ id: getTrad('Settings.subHeader'),
49
50
  defaultMessage: 'Settings',
50
51
  },
51
- id: 'magic-link-settings',
52
- to: `/settings/${pluginId}`,
53
- Component: () => import('./pages/Settings').then(module => ({
54
- default: module.default
55
- })),
56
- permissions: pluginPermissions.readSettings,
52
+ Component: async () => {
53
+ const component = await import('./pages/Settings');
54
+ return component;
55
+ },
56
+ permissions: permissions.accessSettings,
57
57
  },
58
58
  ]
59
59
  );
60
-
61
- app.registerPlugin({
62
- id: pluginId,
63
- initializer: Initializer,
64
- isReady: false,
65
- name,
66
- });
67
60
  },
68
61
 
69
- bootstrap() {
70
- // Nothing to do here
71
- },
62
+ bootstrap() {},
72
63
 
73
64
  async registerTrads({ locales }) {
74
65
  const importedTrads = await Promise.all(
75
- locales.map(locale => {
76
- try {
77
- return import(`./translations/${locale}.json`)
78
- .then(({ default: data }) => {
79
- return {
80
- data: prefixPluginTranslations(data, pluginId),
81
- locale,
82
- };
83
- })
84
- .catch(() => {
85
- return {
86
- data: {},
87
- locale,
88
- };
89
- });
90
- } catch (error) {
91
- return {
92
- data: {},
93
- locale,
94
- };
95
- }
66
+ locales.map((locale) => {
67
+ return import(`./translations/${locale}.json`)
68
+ .then(({ default: data }) => {
69
+ return {
70
+ data: prefixPluginTranslations(data, pluginId),
71
+ locale,
72
+ };
73
+ })
74
+ .catch(() => {
75
+ return {
76
+ data: {},
77
+ locale,
78
+ };
79
+ });
96
80
  })
97
81
  );
98
82
 
@@ -1,50 +1,38 @@
1
- import React from 'react';
2
- import { Main, Box, Typography } from '@strapi/design-system';
3
- import pluginId from '../../pluginId';
1
+ import React, { useEffect } from 'react';
2
+ import { Switch, Route, useLocation } from 'react-router-dom';
3
+ import { SkipToContent } from '@strapi/design-system';
4
+ import { useIntl } from 'react-intl';
5
+ import { AnErrorOccurred } from '@strapi/helper-plugin';
4
6
  import HomePage from '../HomePage';
7
+ import PLUGIN_ID from '../../pluginId';
8
+ import { Helmet } from 'react-helmet';
9
+ import SettingsPage from '../Settings';
5
10
  import TokensPage from '../Tokens';
11
+ import getTrad from '../../utils/getTrad';
6
12
 
7
13
  const App = () => {
8
- console.log("Magic Link App-Root-Komponente wird geladen");
9
-
10
- // Ermittle den aktuellen Pfad, um zu sehen, welche Route angezeigt werden soll
11
- const currentPath = window.location.pathname;
12
- console.log("Aktueller Pfad:", currentPath);
13
- console.log("Plugin ID:", pluginId);
14
-
15
- // Einfachere Bedingungen, die nur prüfen, ob der Pfad bestimmte Segmente enthält
16
- const isTokensPage = currentPath.includes(`/plugins/${pluginId}/tokens`) ||
17
- currentPath.includes(`/plugins/magic-link/tokens`);
18
- const isHomePage = (currentPath.includes(`/plugins/${pluginId}`) ||
19
- currentPath.includes(`/plugins/magic-link`)) && !isTokensPage;
14
+ const { formatMessage } = useIntl();
15
+ const location = useLocation();
16
+ const currentPath = location.pathname;
17
+ const isTokensPage =
18
+ currentPath.includes(`/plugins/strapi-plugin-magic-link-v5/tokens`);
19
+ const isSettingsPage = (
20
+ currentPath.includes(`/plugins/strapi-plugin-magic-link-v5`)) && !isTokensPage;
20
21
 
21
- console.log("isTokensPage:", isTokensPage);
22
- console.log("isHomePage:", isHomePage);
23
-
24
- let content;
25
- if (isTokensPage) {
26
- console.log("Zeige TokensPage an");
27
- content = <TokensPage />;
28
- } else if (isHomePage) {
29
- console.log("Zeige HomePage an");
30
- content = <HomePage />;
31
- } else {
32
- console.log("Zeige 404 Seite an");
33
- content = (
34
- <Box padding={8} background="neutral100">
35
- <Typography variant="alpha">
36
- 404 - Route nicht gefunden: {currentPath}
37
- </Typography>
38
- </Box>
39
- );
40
- }
41
-
42
22
  return (
43
- <Main>
44
- {content}
45
- </Main>
23
+ <div>
24
+ <Helmet title={formatMessage({ id: getTrad('Header.title'), defaultMessage: 'Magic Link' })} />
25
+ <SkipToContent>
26
+ {formatMessage({ id: getTrad('Header.title'), defaultMessage: 'Magic Link' })}
27
+ </SkipToContent>
28
+ <Switch>
29
+ <Route path={`/plugins/${PLUGIN_ID}/tokens`} component={TokensPage} exact />
30
+ <Route path={`/settings/${PLUGIN_ID}`} component={SettingsPage} exact />
31
+ <Route path={`/plugins/${PLUGIN_ID}`} component={HomePage} exact />
32
+ <Route component={AnErrorOccurred} />
33
+ </Switch>
34
+ </div>
46
35
  );
47
36
  };
48
37
 
49
- export { App };
50
38
  export default App;
@@ -23,7 +23,7 @@ import {
23
23
  Information,
24
24
  Calendar
25
25
  } from '@strapi/icons';
26
- import { useFetchClient } from '@strapi/helper-plugin';
26
+ import { useFetchClient } from "@strapi/strapi/admin";
27
27
 
28
28
  const HomePage = () => {
29
29
  const { get } = useFetchClient();
@@ -37,13 +37,30 @@ const HomePage = () => {
37
37
  tokenExpiresNextDays: 0
38
38
  });
39
39
  const [isLoading, setIsLoading] = useState(true);
40
+ const [tokens, setTokens] = useState([]);
41
+
42
+ const fetchTokens = async () => {
43
+ setIsLoading(true);
44
+ try {
45
+ const response = await get('/strapi-plugin-magic-link-v5/tokens');
46
+ setTokens(response.data);
47
+ } catch (error) {
48
+ console.error('Failed to fetch tokens', error);
49
+ toggleNotification({
50
+ type: 'warning',
51
+ message: 'Error loading tokens',
52
+ });
53
+ } finally {
54
+ setIsLoading(false);
55
+ }
56
+ };
40
57
 
41
58
  // API-Anfrage, um echte Token-Daten zu laden
42
59
  useEffect(() => {
43
60
  const fetchTokenData = async () => {
44
61
  try {
45
62
  setIsLoading(true);
46
- const response = await get('/magic-link/tokens');
63
+ const response = await get('/strapi-plugin-magic-link-v5/tokens');
47
64
  const { data, meta } = response.data || { data: [], meta: {} };
48
65
 
49
66
  if (data && Array.isArray(data)) {
@@ -101,6 +118,14 @@ const HomePage = () => {
101
118
  return () => clearInterval(interval);
102
119
  }, [get]);
103
120
 
121
+ const goToTokensPage = () => {
122
+ window.location.href = '/admin/plugins/strapi-plugin-magic-link-v5/tokens';
123
+ };
124
+
125
+ const goToSettings = () => {
126
+ window.location.href = '/admin/settings/strapi-plugin-magic-link-v5';
127
+ };
128
+
104
129
  return (
105
130
  <Main>
106
131
  <Box
@@ -353,9 +378,7 @@ const HomePage = () => {
353
378
  <Button
354
379
  variant="default"
355
380
  endIcon={<ArrowRight />}
356
- onClick={() => {
357
- window.location.href = '/admin/plugins/magic-link/tokens';
358
- }}
381
+ onClick={goToTokensPage}
359
382
  >
360
383
  Öffnen
361
384
  </Button>
@@ -379,9 +402,7 @@ const HomePage = () => {
379
402
  <Button
380
403
  variant="default"
381
404
  endIcon={<ArrowRight />}
382
- onClick={() => {
383
- window.location.href = '/admin/settings/magic-link';
384
- }}
405
+ onClick={goToSettings}
385
406
  >
386
407
  Öffnen
387
408
  </Button>
@@ -487,9 +508,7 @@ const HomePage = () => {
487
508
  variant="secondary"
488
509
  startIcon={<Key />}
489
510
  endIcon={<ArrowRight />}
490
- onClick={() => {
491
- window.location.href = '/admin/plugins/magic-link/tokens';
492
- }}
511
+ onClick={goToTokensPage}
493
512
  >
494
513
  Tokens verwalten
495
514
  </Button>
@@ -529,9 +548,7 @@ const HomePage = () => {
529
548
  variant="secondary"
530
549
  startIcon={<Shield />}
531
550
  endIcon={<ArrowRight />}
532
- onClick={() => {
533
- window.location.href = '/admin/settings/magic-link';
534
- }}
551
+ onClick={goToSettings}
535
552
  >
536
553
  Einstellungen öffnen
537
554
  </Button>