strapi-plugin-magic-link-v5 4.0.13 → 4.0.14

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.
package/README.md CHANGED
@@ -1,3 +1,333 @@
1
- # strapi-plugin-magic-link-v5
1
+ # Magic Link - Passwordless Authentication for Strapi
2
2
 
3
- Magic Link Authentication for Strapi v5
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.
@@ -16,4 +16,3 @@ const Initializer = ({ setPlugin }) => {
16
16
  };
17
17
 
18
18
  export { Initializer };
19
- export default Initializer;
@@ -1,6 +1,5 @@
1
- import { Key } from '@strapi/icons';
1
+ import { PuzzlePiece } from '@strapi/icons';
2
2
 
3
- const PluginIcon = () => <Key />;
3
+ const PluginIcon = () => <PuzzlePiece />;
4
4
 
5
5
  export { PluginIcon };
6
- export default PluginIcon;
@@ -1,11 +1,10 @@
1
- // import { prefixPluginTranslations } from '@strapi/strapi/admin';
1
+ import { prefixPluginTranslations } from '@strapi/strapi/admin';
2
2
  import pluginPkg from '../../package.json';
3
3
  import pluginId from './pluginId';
4
4
  import Initializer from './components/Initializer';
5
5
  import PluginIcon from './components/PluginIcon';
6
6
  import pluginPermissions from './permissions';
7
7
  import getTrad from './utils/getTrad';
8
- import prefixPluginTranslations from './utils/prefixPluginTranslations';
9
8
 
10
9
  const name = pluginPkg.strapi.name;
11
10
 
@@ -23,21 +22,33 @@ export default {
23
22
  }))
24
23
  });
25
24
 
25
+ app.addMenuLink({
26
+ to: `/plugins/${pluginId}/tokens`,
27
+ icon: PluginIcon,
28
+ intlLabel: {
29
+ id: getTrad('tokens.title'),
30
+ defaultMessage: 'Magic Link Tokens',
31
+ },
32
+ Component: () => import('./pages/Tokens').then(module => ({
33
+ default: module.default
34
+ }))
35
+ });
36
+
26
37
  app.createSettingSection(
27
38
  {
28
39
  id: pluginId,
29
40
  intlLabel: {
30
- id: getTrad('Settings.header.title'),
41
+ id: getTrad('Header.Settings'),
31
42
  defaultMessage: 'Magic Link',
32
43
  },
33
44
  },
34
45
  [
35
46
  {
36
47
  intlLabel: {
37
- id: getTrad('Settings.general.title'),
48
+ id: getTrad('Form.title.Settings'),
38
49
  defaultMessage: 'Settings',
39
50
  },
40
- id: 'settings',
51
+ id: 'magic-link-settings',
41
52
  to: `/settings/${pluginId}`,
42
53
  Component: () => import('./pages/Settings').then(module => ({
43
54
  default: module.default
@@ -52,16 +63,6 @@ export default {
52
63
  initializer: Initializer,
53
64
  isReady: false,
54
65
  name,
55
- routes: [
56
- {
57
- method: 'GET',
58
- path: '/tokens',
59
- handler: 'tokens.find',
60
- config: {
61
- policies: [],
62
- },
63
- },
64
- ]
65
66
  });
66
67
  },
67
68
 
@@ -12,14 +12,11 @@ const App = () => {
12
12
  console.log("Aktueller Pfad:", currentPath);
13
13
  console.log("Plugin ID:", pluginId);
14
14
 
15
- // Verbesserte Routenerkennung mit exakter Pfadprüfung
16
- const tokensPath = `/admin/plugins/${pluginId}/tokens`;
17
- const basePath = `/admin/plugins/${pluginId}`;
18
-
19
- // Prüfe, ob der aktuelle Pfad genau der Tokens-Pfad ist
20
- const isTokensPage = currentPath === tokensPath;
21
- // Homepage ist die Basis-URL ohne weitere Pfadsegmente
22
- const isHomePage = currentPath === basePath;
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;
23
20
 
24
21
  console.log("isTokensPage:", isTokensPage);
25
22
  console.log("isHomePage:", isHomePage);
@@ -43,7 +43,7 @@ const HomePage = () => {
43
43
  const fetchTokenData = async () => {
44
44
  try {
45
45
  setIsLoading(true);
46
- const response = await get('/strapi-plugin-magic-link-v5/tokens');
46
+ const response = await get('/magic-link/tokens');
47
47
  const { data, meta } = response.data || { data: [], meta: {} };
48
48
 
49
49
  if (data && Array.isArray(data)) {
@@ -354,7 +354,7 @@ const HomePage = () => {
354
354
  variant="default"
355
355
  endIcon={<ArrowRight />}
356
356
  onClick={() => {
357
- window.location.href = '/admin/plugins/strapi-plugin-magic-link-v5/tokens';
357
+ window.location.href = '/admin/plugins/magic-link/tokens';
358
358
  }}
359
359
  >
360
360
  Öffnen
@@ -380,7 +380,7 @@ const HomePage = () => {
380
380
  variant="default"
381
381
  endIcon={<ArrowRight />}
382
382
  onClick={() => {
383
- window.location.href = '/admin/settings/strapi-plugin-magic-link-v5';
383
+ window.location.href = '/admin/settings/magic-link';
384
384
  }}
385
385
  >
386
386
  Öffnen
@@ -488,7 +488,7 @@ const HomePage = () => {
488
488
  startIcon={<Key />}
489
489
  endIcon={<ArrowRight />}
490
490
  onClick={() => {
491
- window.location.href = '/admin/plugins/strapi-plugin-magic-link-v5/tokens';
491
+ window.location.href = '/admin/plugins/magic-link/tokens';
492
492
  }}
493
493
  >
494
494
  Tokens verwalten
@@ -530,7 +530,7 @@ const HomePage = () => {
530
530
  startIcon={<Shield />}
531
531
  endIcon={<ArrowRight />}
532
532
  onClick={() => {
533
- window.location.href = '/admin/settings/strapi-plugin-magic-link-v5';
533
+ window.location.href = '/admin/settings/magic-link';
534
534
  }}
535
535
  >
536
536
  Einstellungen öffnen
@@ -624,7 +624,7 @@ const HomePage = () => {
624
624
  <Divider />
625
625
  <Box as="pre" padding={4} background="neutral100" marginTop={4} hasRadius style={{overflow: 'auto'}}>
626
626
  Current Path: {window.location.pathname}<br/>
627
- Plugin ID: strapi-plugin-magic-link-v5<br/>
627
+ Plugin ID: magic-link<br/>
628
628
  HomePage Component Status: Loaded<br/>
629
629
  Session Active: Yes<br/>
630
630
  Current Time: {new Date().toLocaleString()}<br/>
@@ -110,7 +110,7 @@ const Settings = () => {
110
110
  console.log('Initiating API call to fetch Magic Link settings');
111
111
 
112
112
  try {
113
- const res = await get('/strapi-plugin-magic-link-v5/settings');
113
+ const res = await get('/magic-link/settings');
114
114
  console.log('Magic Link API response:', res);
115
115
 
116
116
  // Check if data property exists in the response
@@ -256,7 +256,7 @@ const Settings = () => {
256
256
  });
257
257
 
258
258
  // Sende die Änderungen an die API
259
- const response = await put('/strapi-plugin-magic-link-v5/settings', settingsToSubmit);
259
+ const response = await put('/magic-link/settings', settingsToSubmit);
260
260
  console.log('Settings saved response:', response);
261
261
 
262
262
  // Aktualisiere den API-Zustand
@@ -1248,7 +1248,7 @@ const Settings = () => {
1248
1248
  if (window.confirm(safeTranslate('settings.reset.confirmMessage', 'Sind Sie sicher, dass Sie alle Magic Link Daten zurücksetzen möchten? Diese Aktion kann nicht rückgängig gemacht werden!'))) {
1249
1249
  setIsLoading(true);
1250
1250
  // API-Anfrage senden
1251
- post('/strapi-plugin-magic-link-v5/reset-data')
1251
+ post('/magic-link/reset-data')
1252
1252
  .then(response => {
1253
1253
  // Benachrichtigung anzeigen
1254
1254
  toggleNotification({