strapi-plugin-magic-link-v5 4.3.1 → 4.4.1

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.
@@ -1,20 +1,19 @@
1
1
  /**
2
2
  * Magic Link - Passwordless Authentication for Strapi
3
3
  *
4
- * Copyright (c) 2025 [Dein Name/Firma]
5
- * All Rights Reserved.
4
+ * Copyright (c) 2025 Schero A. (begservice)
6
5
  *
7
- * PROPRIETARY AND CONFIDENTIAL
6
+ * Licensed under the MIT License
8
7
  *
9
- * This file is part of Magic Link plugin for Strapi.
8
+ * This plugin is free to use for personal and commercial projects.
10
9
  *
11
- * Unauthorized copying, modification, distribution, or use of this software
12
- * via any medium is strictly prohibited without prior written permission.
10
+ * IMPORTANT RESTRICTION:
11
+ * The license validation system (license-guard.js and related components)
12
+ * must remain intact and functional. Removing or bypassing this system
13
+ * is strictly prohibited.
13
14
  *
14
- * This software is licensed under a proprietary commercial license.
15
15
  * See LICENSE file for full terms.
16
16
  *
17
- * For licensing inquiries: [Deine Email]
18
- * Website: [Deine Website]
17
+ * Repository: https://github.com/begservice/strapi-plugin-magic-link-v5
18
+ * Issues: https://github.com/begservice/strapi-plugin-magic-link-v5/issues
19
19
  */
20
-
package/LICENSE CHANGED
@@ -1,48 +1,27 @@
1
- PROPRIETARY LICENSE AGREEMENT
2
-
3
- Copyright (c) 2025 [Dein Name/Firma]
4
-
5
- This software and associated documentation files (the "Software") are proprietary
6
- and confidential. All rights reserved.
7
-
8
- TERMS AND CONDITIONS:
9
-
10
- 1. LICENSE GRANT
11
- This Software is licensed, not sold. Subject to payment of applicable license
12
- fees and compliance with these terms, you are granted a limited, non-exclusive,
13
- non-transferable license to use the Software.
14
-
15
- 2. RESTRICTIONS
16
- You may NOT:
17
- - Copy, modify, or distribute the Software
18
- - Reverse engineer, decompile, or disassemble the Software
19
- - Remove or modify any proprietary notices or labels
20
- - Use the Software for any unlicensed purpose
21
- - Share your license key with unauthorized parties
22
- - Host the Software on multiple servers without appropriate licensing
23
-
24
- 3. INTELLECTUAL PROPERTY
25
- All title, ownership rights, and intellectual property rights in and to the
26
- Software remain with the copyright holder. The Software is protected by
27
- copyright laws and international treaty provisions.
28
-
29
- 4. TERMINATION
30
- This license is effective until terminated. Your rights under this license
31
- will terminate automatically without notice if you fail to comply with any
32
- term of this license.
33
-
34
- 5. WARRANTY DISCLAIMER
35
- THE SOFTWARE IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
36
- IMPLIED, INCLUDING BUT NOT LIMITED TO WARRANTIES OF MERCHANTABILITY, FITNESS
37
- FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
38
-
39
- 6. LIMITATION OF LIABILITY
40
- IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR
41
- OTHER LIABILITY ARISING FROM THE USE OF THE SOFTWARE.
42
-
43
- 7. LICENSE PURCHASE
44
- To obtain a valid license, visit: [Deine Website]
45
- Each license is subject to separate terms and pricing.
46
-
47
- For licensing inquiries, contact: [Deine Email]
48
-
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Schero A. (begservice)
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ **ADDITIONAL CONDITION:**
16
+ The license validation system (including but not limited to license-guard.js,
17
+ license controller, and related API endpoints) must remain intact and functional.
18
+ Removing, bypassing, or disabling the license validation system is strictly
19
+ prohibited.
20
+
21
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
22
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
23
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
24
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
25
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
26
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
27
+ SOFTWARE.
package/README.md CHANGED
@@ -1,39 +1,69 @@
1
- # Magic Link - Passwordless Authentication for Strapi
1
+ # Magic Link - Passwordless Authentication for Strapi v5
2
2
 
3
- A secure passwordless authentication solution for Strapi, allowing users to log in via email links without requiring passwords.
3
+ Secure passwordless authentication for Strapi using email-based magic links. No passwords required.
4
4
 
5
- ---
5
+ [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE)
6
+ [![npm version](https://badge.fury.io/js/strapi-plugin-magic-link-v5.svg)](https://www.npmjs.com/package/strapi-plugin-magic-link-v5)
6
7
 
7
- ## ⚠️ LICENSE & USAGE NOTICE
8
+ ---
8
9
 
9
- **This is proprietary commercial software.**
10
+ ## 📜 License
10
11
 
11
- - **NOT** open source or free to use
12
- - ❌ **NOT** licensed under MIT, Apache, or similar permissive licenses
13
- - ✅ Requires a **valid commercial license** for production use
14
- - ✅ Source code is visible for transparency and evaluation only
12
+ This plugin is licensed under the **MIT License** - free for everyone to use!
15
13
 
16
- **Using this software without a valid license is copyright infringement.**
14
+ ### What you CAN do:
15
+ - ✅ Use the plugin freely (personal & commercial)
16
+ - ✅ View and study the source code
17
+ - ✅ Report issues and contribute improvements
18
+ - ✅ Deploy in production without fees
19
+ - ✅ Integrate in your commercial projects
17
20
 
18
- 📄 See [LICENSE](./LICENSE) for full terms
19
- 💼 Purchase a license: [Your Website]
20
- 📧 Questions? Contact: [Your Email]
21
+ ### What you CANNOT do:
22
+ - Remove or bypass the license validation system
23
+ - Modify `license-guard.js` or license-related endpoints
24
+ - ❌ Disable license activation requirements
21
25
 
22
- ---
26
+ **Important:** The license validation system must remain intact and functional. This ensures quality, support, and continued development. Users must activate the plugin (free) through the admin interface.
23
27
 
24
- ![Magic Link Overview](pics/pic6.png)
28
+ 📄 See [LICENSE](./LICENSE) for full terms
25
29
 
26
- ## Core Features!
30
+ ---
27
31
 
28
- - **Passwordless Authentication**: Login via secure email links
29
- - **Token Management**: Admin dashboard for managing and monitoring tokens
30
- - **JWT Session Tracking**: Monitor and manage active sessions
31
- - **Security Features**: IP banning, token expiration controls
32
- - **Admin Interface**: Statistics dashboard and configuration options
32
+ ## Features
33
+
34
+ ### Core Authentication
35
+ - 🔐 **Passwordless Login** - Users log in via secure email links
36
+ - 🎫 **Magic Link Tokens** - Cryptographically secure, time-limited tokens
37
+ - 🔑 **JWT Session Management** - Monitor and manage active user sessions
38
+ - 👤 **Auto User Creation** - Optionally create users automatically on first login
39
+ - 🌍 **Multi-language Support** - English and German translations included
40
+
41
+ ### Security & Control
42
+ - 🛡️ **IP Banning** - Block suspicious IP addresses
43
+ - 🔒 **Session Revocation** - Instantly revoke any active JWT session
44
+ - ⏰ **Token Expiration** - Configurable expiration periods
45
+ - 🎯 **Login Attempt Limiting** - Prevent brute force attacks
46
+ - 📊 **Security Score** - Real-time security configuration assessment
47
+ - 📝 **Login Info Tracking** - Store IP addresses and user agents for audit
48
+
49
+ ### Admin Interface
50
+ - 📱 **Modern Dashboard** - Beautiful statistics and monitoring interface
51
+ - 🎨 **Professional Token Management** - Create, extend, and manage tokens
52
+ - 🔍 **Search & Filter** - Find tokens and sessions quickly
53
+ - 📄 **Pagination** - Handle large datasets efficiently
54
+ - 🎭 **Bulk Operations** - Select and manage multiple tokens at once
55
+ - 🌐 **License Management** - Built-in license activation interface
56
+
57
+ ### Customization
58
+ - ✉️ **Email Templates** - Customize HTML and plain text email templates
59
+ - 🎨 **Template Variables** - Use `<%= URL %>` and `<%= CODE %>` placeholders
60
+ - ⚙️ **Flexible Configuration** - Configure via admin panel
61
+ - 🔄 **Token Reusability** - Choose between one-time or reusable tokens
62
+ - 📧 **Email Designer Support** - Integrates with Email Designer 5 plugin
33
63
 
34
- ![Core Features](pics/pic7.png)
64
+ ---
35
65
 
36
- ## Installation!
66
+ ## 🚀 Installation
37
67
 
38
68
  ```bash
39
69
  # Using npm
@@ -42,334 +72,216 @@ npm install strapi-plugin-magic-link-v5
42
72
  # Using yarn
43
73
  yarn add strapi-plugin-magic-link-v5
44
74
 
45
- # Install directly from GitHub
46
- npm install begservice/strapi-magic-link
47
- # or
48
- yarn add begservice/strapi-magic-link
75
+ # Using pnpm
76
+ pnpm add strapi-plugin-magic-link-v5
49
77
  ```
50
78
 
51
- After installation, restart your Strapi server and the plugin will be available in the admin panel.
79
+ After installation, **restart your Strapi server**. The plugin will appear in your admin panel.
52
80
 
53
- ## License Server
81
+ ---
54
82
 
55
- This plugin uses a **centralized license server** for validation and activation.
83
+ ## 🎯 Quick Start
56
84
 
57
- ### How It Works
85
+ ### 1️⃣ Activate License
58
86
 
59
- - The plugin connects to a fixed license server URL for all license operations
60
- - License keys are validated against this server
61
- - The server URL is hardcoded for security (cannot be changed by end users)
62
- - Supports **24-hour grace period** for offline operation
87
+ After installing, navigate to **Settings Magic Link License** in your Strapi admin panel and activate the plugin (free).
63
88
 
64
- ### For Developers (Development Mode)
89
+ ### 2️⃣ Configure Settings
65
90
 
66
- During development, you can override the license server URL:
91
+ Go to **Settings Magic Link Settings** and configure:
67
92
 
68
- ```bash
69
- # .env file
70
- LICENSE_SERVER_URL=http://localhost:1337
71
- NODE_ENV=development
93
+ ```javascript
94
+ {
95
+ "enabled": true,
96
+ "createUserIfNotExists": true, // Auto-create users
97
+ "expire_period": 3600, // Token valid for 1 hour
98
+ "token_length": 20, // Token security level
99
+ "from_email": "noreply@yourdomain.com",
100
+ "from_name": "Your App",
101
+ "object": "Your Magic Link Login",
102
+ "confirmationUrl": "https://yourdomain.com/auth/callback"
103
+ }
72
104
  ```
73
105
 
74
- **Note**: This override only works in development mode for testing purposes.
75
-
76
- ## How It Works
77
-
78
- ### Email User with Login Link
79
-
80
- 1. **Request Process**:
81
-
82
- - User requests a login link by entering their email
83
- - System generates a secure token and sends an email
84
- - Email contains a magic link with the token
85
-
86
- 2. **Token Details**:
87
- - Cryptographically secure random tokens
88
- - Configurable expiration time
89
- - Option for one-time use or reusable tokens
90
- - Tracks IP address and user agent information
91
-
92
- ### Login with Token
93
-
94
- 1. **Authentication Process**:
95
-
96
- - User clicks the link in their email
97
- - System verifies the token is valid and not expired
98
- - User is automatically authenticated
99
- - JWT token is generated for the session
100
-
101
- 2. **Security Measures**:
102
- - IP address validation (optional)
103
- - Token expiration
104
- - One-time use tokens (configurable)
105
- - Automatic blocking after failed attempts
106
-
107
- ![Authentication Process](pics/pic8.png)
108
-
109
- ## Configuration
110
-
111
- Configure the plugin through **Settings > Magic Link** in the Strapi admin panel:
112
-
113
- ![Configuration Interface](pics/pic9.png)
114
-
115
- ### General Settings
116
-
117
- - **Enable Magic Link**: Turn the feature on/off
118
- - **Create New Users**: Automatically create users if they don't exist
119
- - **Token Stays Valid**: Configure one-time use or reusable tokens
120
- - **Expiration Period**: Set how long tokens remain valid
121
- - **Token Length**: Configure the security level of tokens
122
-
123
- ### Authentication Settings
124
-
125
- - **Default Role**: Select which user role is assigned to new users
126
- - **JWT Token Expiration**: Define how long JWT tokens remain valid (e.g., 30d, 24h)
127
- - **Store Login Info**: Enable tracking of user agents and IP addresses
128
- - **Remember Me**: Allow users to stay logged in for extended periods
129
-
130
- ### Email Settings
131
-
132
- - **Sender Information**: Configure the email sender details (name, email)
133
- - **Reply-To Address**: Set a reply-to email address for support inquiries
134
- - **Email Subject**: Customize the subject line of the magic link emails
135
- - **Email Templates**: Customize HTML and text templates
136
- - **Email Designer Integration**: Use with Email Designer 5 if installed
137
-
138
- ## Dashboard & Admin Interface
139
-
140
- Magic Link provides a comprehensive admin interface with several key sections:
141
-
142
- ![Admin Dashboard](pics/pic10.png)
143
-
144
- ### Dashboard Overview
145
-
146
- - **Security Score**: Dynamic score (0-100) showing your current security configuration
147
- - **Active Tokens**: Count and management of currently active tokens
148
- - **Token Usage**: Metrics on token creation and usage patterns
149
- - **Users Using Magic Link**: Number of unique users authenticating via magic links
150
- - **Tokens About to Expire**: Warning system for tokens expiring soon
151
-
152
- ### Token Management
153
-
154
- - **Token List View**: Sortable and filterable list of all tokens
155
- - **Token Status Indicators**: Visual indicators showing token status (active, expired, used)
156
- - **Token Details**: Inspect complete token information including:
157
- - Creation and expiration dates
158
- - IP address and user agent information
159
- - Usage history and context data
160
- - **Bulk Actions**: Select multiple tokens for batch operations
161
- - **Search & Filter**: Find tokens by email, status, or creation date
162
- - **Token Operations**:
163
- - Block/deactivate tokens
164
- - Extend token expiration
165
- - Delete tokens
166
-
167
- ### JWT Session Management
168
-
169
- - **Active Sessions**: Monitor all active JWT sessions across your application
170
- - **Session Revocation**: Ability to revoke any active session immediately
171
- - **Session Details**: View complete session information including:
172
- - User details
173
- - Creation and expiration time
174
- - Last activity
175
- - IP address and device information
106
+ ### 3️⃣ Frontend Implementation
176
107
 
177
- ### Security Features
178
-
179
- - **IP Ban Management**: View and manage banned IP addresses
180
- - **IP Ban Controls**: Ban suspicious IPs and view associated tokens
181
- - **Security Audit**: Track login attempts and security events
182
- - **System Status**: Monitor the health of the plugin and its dependencies
183
-
184
- ### Token Creation Interface
185
-
186
- - **Manual Token Creation**: Generate tokens for specific users
187
- - **Email Control**: Option to send or not send the email with the token
188
- - **Context Injection**: Add custom JSON context data to tokens
189
- - **Email Validation**: Verify email existence before token creation
190
-
191
- ## Admin Features
192
-
193
- ### Token Management
194
-
195
- - View all active/inactive tokens
196
- - Block or activate individual tokens
197
- - Delete expired tokens
198
- - See token usage statistics
199
-
200
- ### Security Dashboard
201
-
202
- - Security score based on your configuration
203
- - IP banning for suspicious activity
204
- - JWT session monitoring and management
205
- - Token expiration warnings
206
-
207
- ## API Endpoints
208
-
209
- - `POST /api/magic-link/send` - Generate and send a magic link
210
- - `GET /api/magic-link/login?loginToken=xxx` - Authenticate with token
211
- - `GET /api/magic-link/tokens` - List tokens (admin only)
212
- - `GET /api/magic-link/jwt-sessions` - List active sessions (admin only)
213
- - `POST /api/magic-link/tokens/:id/block` - Block a specific token
214
- - `POST /api/magic-link/ban-ip` - Ban an IP address
215
-
216
- ## Frontend Implementation
108
+ **Request a magic link:**
109
+ ```javascript
110
+ const response = await fetch('/api/magic-link/send-link', {
111
+ method: 'POST',
112
+ headers: { 'Content-Type': 'application/json' },
113
+ body: JSON.stringify({
114
+ email: 'user@example.com',
115
+ context: { redirectTo: '/dashboard' } // Optional
116
+ })
117
+ });
118
+ ```
217
119
 
120
+ **Verify token on callback page:**
218
121
  ```javascript
219
- // Request a magic link
220
- const requestLogin = async (email) => {
221
- try {
222
- await axios.post("/api/magic-link/send", { email });
223
- // Show success message
224
- } catch (error) {
225
- // Handle error
226
- }
227
- };
228
-
229
- // Verify token on the callback page
230
- const verifyToken = async () => {
231
- const token = new URLSearchParams(window.location.search).get("loginToken");
232
- if (token) {
233
- try {
234
- const response = await axios.get(
235
- `/api/magic-link/login?loginToken=${token}`
236
- );
237
- // Store JWT and redirect user
238
- localStorage.setItem("token", response.data.jwt);
239
- window.location.href = "/dashboard";
240
- } catch (error) {
241
- // Handle invalid token
242
- }
243
- }
244
- };
122
+ const urlParams = new URLSearchParams(window.location.search);
123
+ const loginToken = urlParams.get('loginToken');
124
+
125
+ if (loginToken) {
126
+ const response = await fetch(`/api/magic-link/login?loginToken=${loginToken}`);
127
+ const { jwt, user } = await response.json();
128
+
129
+ // Store JWT for authenticated requests
130
+ localStorage.setItem('token', jwt);
131
+
132
+ // Redirect to dashboard
133
+ window.location.href = '/dashboard';
134
+ }
245
135
  ```
246
136
 
247
- ## Context Data
137
+ ---
248
138
 
249
- You can include additional context when sending a magic link:
139
+ ## 📡 API Endpoints
250
140
 
251
- ```javascript
252
- await axios.post("/api/magic-link/send", {
253
- email: "user@example.com",
254
- context: {
255
- redirectUrl: "/dashboard",
256
- source: "registration",
257
- },
258
- });
259
- ```
141
+ ### Public Endpoints (No Auth Required)
260
142
 
261
- ## Security Best Practices
143
+ | Method | Endpoint | Description |
144
+ |--------|----------|-------------|
145
+ | `POST` | `/api/magic-link/send-link` | Generate and send magic link to email |
146
+ | `GET` | `/api/magic-link/login?loginToken=xxx` | Authenticate user with token |
262
147
 
263
- - Set reasonable token expiration times
264
- - Use one-time tokens for sensitive operations
265
- - Regularly monitor the security dashboard
266
- - Ban suspicious IP addresses promptly
148
+ ### Admin Endpoints (Admin Auth Required)
267
149
 
268
- ## Troubleshooting
150
+ | Method | Endpoint | Description |
151
+ |--------|----------|-------------|
152
+ | `GET` | `/magic-link/tokens` | List all tokens |
153
+ | `POST` | `/magic-link/tokens` | Create a new token |
154
+ | `DELETE` | `/magic-link/tokens/:id` | Delete a token |
155
+ | `POST` | `/magic-link/tokens/:id/block` | Block a token |
156
+ | `POST` | `/magic-link/tokens/:id/extend` | Extend token validity |
157
+ | `GET` | `/magic-link/jwt-sessions` | List active JWT sessions |
158
+ | `POST` | `/magic-link/revoke-jwt` | Revoke a JWT session |
159
+ | `POST` | `/magic-link/ban-ip` | Ban an IP address |
160
+ | `GET` | `/magic-link/banned-ips` | List banned IPs |
269
161
 
270
- | Issue | Solution |
271
- | ---------------------- | ---------------------------------------------- |
272
- | Emails not sending | Check your Strapi email provider configuration |
273
- | Token validation fails | Verify token expiration settings |
274
- | User not found errors | Check "Create New Users" setting |
162
+ ---
275
163
 
276
- ## License
164
+ ## ⚙️ Configuration
277
165
 
278
- [MIT](LICENSE)
166
+ ### General Settings
167
+ - `enabled` - Enable/disable magic link authentication
168
+ - `createUserIfNotExists` - Auto-create users on first login
169
+ - `expire_period` - Token expiration time (seconds)
170
+ - `token_length` - Security level (20-40 recommended)
171
+ - `stays_valid` - Token reusable after first use
172
+ - `max_login_attempts` - Limit failed login attempts
279
173
 
280
- ## Development & Contributing
174
+ ### Email Settings
175
+ - `from_name` - Sender name
176
+ - `from_email` - Sender email address
177
+ - `response_email` - Reply-to email
178
+ - `object` - Email subject line
179
+ - `message_html` - HTML email template
180
+ - `message_text` - Plain text email template
181
+
182
+ ### JWT Settings
183
+ - `use_jwt_token` - Use JWT for authentication
184
+ - `jwt_token_expires_in` - JWT validity period (e.g., '30d', '7d')
185
+ - `store_login_info` - Track IP and user agent
186
+
187
+ ### Advanced
188
+ - `user_creation_strategy` - `email` | `emailUsername` | `manual`
189
+ - `verify_email` - Require email verification
190
+ - `callback_url` - Post-login redirect URL
281
191
 
282
- ### Release Process
192
+ ---
283
193
 
284
- This project uses [semantic-release](https://github.com/semantic-release/semantic-release) to automate version management and package publishing.
194
+ ## 🎨 Email Templates
285
195
 
286
- To create a new release:
196
+ Customize your magic link emails using template variables:
287
197
 
288
- ```bash
289
- npx semantic-release
198
+ ```html
199
+ <!-- HTML Template -->
200
+ <h1>Welcome!</h1>
201
+ <p>Click to login:</p>
202
+ <a href="<%= URL %>?loginToken=<%= CODE %>">
203
+ Login to Your Account
204
+ </a>
290
205
  ```
291
206
 
292
- This will automatically:
293
-
294
- 1. Analyze commit messages since the last release
295
- 2. Determine the appropriate version bump (major, minor, or patch)
296
- 3. Generate release notes
297
- 4. Update the version in package.json
298
- 5. Create a new Git tag
299
- 6. Publish the package to npm
207
+ **Available Variables:**
208
+ - `<%= URL %>` - Your confirmation URL
209
+ - `<%= CODE %>` - The generated token
300
210
 
301
- ### Commit Guidelines
211
+ ---
302
212
 
303
- To ensure semantic-release can properly determine the next version number, please follow these commit message conventions:
213
+ ## 🔒 Security Features
304
214
 
305
- #### Commit Message Format
215
+ - **Token Expiration** - Configurable expiration periods
216
+ - **One-time Tokens** - Optional single-use tokens
217
+ - **IP Tracking** - Monitor login locations
218
+ - **IP Banning** - Block suspicious addresses
219
+ - **JWT Blacklist** - Revoke compromised sessions
220
+ - **Login Attempt Limiting** - Prevent brute force
221
+ - **User Agent Tracking** - Device fingerprinting
306
222
 
307
- Each commit message consists of a **header**, an optional **body**, and an optional **footer**:
223
+ ---
308
224
 
309
- ```
310
- <type>(<scope>): <subject>
311
- <BLANK LINE>
312
- <body>
313
- <BLANK LINE>
314
- <footer>
315
- ```
225
+ ## 🎯 Use Cases
316
226
 
317
- The **header** is mandatory and must conform to the following format:
227
+ - **SaaS Applications** - Simplify user onboarding
228
+ - **Customer Portals** - Secure, password-free access
229
+ - **Multi-tenant Systems** - Easy user management
230
+ - **Mobile Apps** - Seamless authentication flow
231
+ - **Content Platforms** - Reduce password fatigue
318
232
 
319
- - **type**: What type of change this commit is making. Must be one of:
233
+ ---
320
234
 
321
- - `feat`: A new feature (triggers a minor release)
322
- - `fix`: A bug fix (triggers a patch release)
323
- - `docs`: Documentation changes only
324
- - `style`: Changes that don't affect code functionality (formatting, etc.)
325
- - `refactor`: Code changes that neither fix a bug nor add a feature
326
- - `perf`: Performance improvements
327
- - `test`: Adding or updating tests
328
- - `chore`: Changes to build process or auxiliary tools
329
- - `ci`: Changes to CI configuration files and scripts
235
+ ## 🐛 Troubleshooting
330
236
 
331
- - **scope**: Optional, can be anything specifying the place of the commit change (e.g., `admin`, `api`, `auth`)
237
+ | Issue | Solution |
238
+ |-------|----------|
239
+ | Emails not sending | Check Strapi email provider configuration |
240
+ | Token invalid errors | Verify token hasn't expired |
241
+ | User not found | Enable `createUserIfNotExists` setting |
242
+ | License activation fails | Check network connectivity |
243
+ | npm install fails | Use `npm install --legacy-peer-deps` |
332
244
 
333
- - **subject**: Brief description of the change
245
+ ---
334
246
 
335
- #### Breaking Changes
247
+ ## 🤝 Contributing
336
248
 
337
- For breaking changes, add `BREAKING CHANGE:` in the footer of the commit message or append a `!` after the type/scope:
249
+ Contributions are welcome! Please:
338
250
 
339
- ```
340
- feat(api)!: completely restructure API endpoints
251
+ 1. Fork the repository
252
+ 2. Create a feature branch (`git checkout -b feature/amazing-feature`)
253
+ 3. Commit your changes (`git commit -m 'feat: add amazing feature'`)
254
+ 4. Push to the branch (`git push origin feature/amazing-feature`)
255
+ 5. Open a Pull Request
341
256
 
342
- BREAKING CHANGE: The API endpoints have been completely restructured.
343
- ```
257
+ **Commit Convention:** Follow [Conventional Commits](https://www.conventionalcommits.org/)
258
+ - `feat:` - New features
259
+ - `fix:` - Bug fixes
260
+ - `docs:` - Documentation changes
261
+ - `chore:` - Maintenance tasks
344
262
 
345
- This will trigger a major version bump.
263
+ ---
346
264
 
347
- #### Examples
265
+ ## 📝 Changelog
348
266
 
349
- ```
350
- feat(auth): add support for multiple roles
267
+ This project uses [semantic-release](https://github.com/semantic-release/semantic-release) for automated versioning and releases. See [GitHub Releases](https://github.com/begservice/strapi-plugin-magic-link-v5/releases) for version history.
351
268
 
352
- fix(email): correct template rendering issue
269
+ ---
353
270
 
354
- docs: update API documentation
271
+ ## 📄 License
355
272
 
356
- chore(deps): update dependencies
273
+ This project is licensed under the **MIT License** - see the [LICENSE](LICENSE) file for details.
357
274
 
358
- fix!: critical security vulnerability in token validation
359
- ```
275
+ **Important:** While the code is open source, the license validation system must remain intact. This ensures quality, security, and continued development of the plugin.
360
276
 
361
- ### Branching Strategy
277
+ ---
362
278
 
363
- - `main`: Production-ready code
364
- - `develop`: Integration branch for features
365
- - `feature/*`: New features
366
- - `fix/*`: Bug fixes
367
- - `docs/*`: Documentation changes
279
+ ## 💬 Support
368
280
 
369
- When creating PRs, always merge feature branches into `develop` first. The `develop` branch is periodically merged into `main` for releases.
281
+ - 🐛 **Issues**: [GitHub Issues](https://github.com/begservice/strapi-plugin-magic-link-v5/issues)
282
+ - 📧 **Contact**: 124470865+begservice@users.noreply.github.com
283
+ - 📦 **npm**: [strapi-plugin-magic-link-v5](https://www.npmjs.com/package/strapi-plugin-magic-link-v5)
370
284
 
371
285
  ---
372
286
 
373
- ## Support
374
-
375
- If you encounter issues or have questions, please check the admin documentation or open an issue.
287
+ Made with ❤️ by [begservice](https://github.com/begservice)
@@ -683,9 +683,15 @@ const TokensProfessional = () => {
683
683
  // Stats berechnen
684
684
  const stats = useMemo(() => ({
685
685
  total: tokens.length,
686
- active: tokens.filter(t => !t.expired && !t.used).length,
687
- expired: tokens.filter(t => t.expired).length,
688
- used: tokens.filter(t => t.used).length,
686
+ active: tokens.filter(t => {
687
+ const isExpired = t.expires_at && new Date(t.expires_at) < new Date();
688
+ return t.is_active && !isExpired;
689
+ }).length,
690
+ expired: tokens.filter(t => {
691
+ const isExpired = t.expires_at && new Date(t.expires_at) < new Date();
692
+ return isExpired;
693
+ }).length,
694
+ used: tokens.filter(t => !t.is_active).length,
689
695
  }), [tokens]);
690
696
 
691
697
  // Gefilterte und sortierte Tokens
@@ -703,13 +709,14 @@ const TokensProfessional = () => {
703
709
  // Status Filter
704
710
  if (filterStatus !== 'all') {
705
711
  filtered = filtered.filter(token => {
712
+ const isExpired = token.expires_at && new Date(token.expires_at) < new Date();
706
713
  switch (filterStatus) {
707
714
  case 'active':
708
- return !token.expired && !token.used;
715
+ return token.is_active && !isExpired;
709
716
  case 'expired':
710
- return token.expired;
717
+ return isExpired;
711
718
  case 'used':
712
- return token.used;
719
+ return !token.is_active;
713
720
  default:
714
721
  return true;
715
722
  }
@@ -939,12 +946,16 @@ const TokensProfessional = () => {
939
946
  };
940
947
 
941
948
  const getStatusBadge = (token) => {
942
- if (token.used) {
949
+ // Prüfe zuerst is_active - wenn false, wurde der Token verwendet oder blockiert
950
+ if (!token.is_active) {
943
951
  return <AnimatedBadge variant="secondary">{formatMessage({ id: getTrad('tokens.status.used') })}</AnimatedBadge>;
944
952
  }
945
- if (token.expired) {
953
+ // Prüfe ob abgelaufen
954
+ const isExpired = token.expires_at && new Date(token.expires_at) < new Date();
955
+ if (isExpired) {
946
956
  return <AnimatedBadge variant="warning">{formatMessage({ id: getTrad('tokens.status.expired') })}</AnimatedBadge>;
947
957
  }
958
+ // Token ist aktiv und nicht abgelaufen
948
959
  return <AnimatedBadge variant="success">{formatMessage({ id: getTrad('tokens.status.active') })}</AnimatedBadge>;
949
960
  };
950
961
 
@@ -369,9 +369,15 @@ const TokensRedesign = () => {
369
369
  // Stats berechnen
370
370
  const stats = {
371
371
  total: tokens.length,
372
- active: tokens.filter(t => !t.expired && !t.used).length,
373
- expired: tokens.filter(t => t.expired).length,
374
- used: tokens.filter(t => t.used).length,
372
+ active: tokens.filter(t => {
373
+ const isExpired = t.expires_at && new Date(t.expires_at) < new Date();
374
+ return t.is_active && !isExpired;
375
+ }).length,
376
+ expired: tokens.filter(t => {
377
+ const isExpired = t.expires_at && new Date(t.expires_at) < new Date();
378
+ return isExpired;
379
+ }).length,
380
+ used: tokens.filter(t => !t.is_active).length,
375
381
  };
376
382
 
377
383
  // Stat Cards Konfiguration
package/package.json CHANGED
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "4.3.1",
2
+ "version": "4.4.1",
3
3
  "keywords": [],
4
4
  "type": "commonjs",
5
5
  "exports": {
@@ -60,7 +60,7 @@
60
60
  },
61
61
  "name": "strapi-plugin-magic-link-v5",
62
62
  "description": "This plugin provides passwordless authentication via magic links sent to email",
63
- "license": "SEE LICENSE IN LICENSE",
63
+ "license": "MIT",
64
64
  "private": false,
65
65
  "author": "Schero A. <124470865+begservice@users.noreply.github.com>",
66
66
  "repository": {
package/LICENSE-DUAL.md DELETED
@@ -1,37 +0,0 @@
1
- # DUAL LICENSE
2
-
3
- This software is available under a dual licensing model.
4
-
5
- ## 1. GNU AFFERO GENERAL PUBLIC LICENSE (AGPL-3.0)
6
-
7
- If you are creating an open-source application under a license compatible with
8
- the GNU AGPL-3.0, you may use this software under those terms.
9
-
10
- **Key Requirements:**
11
- - You MUST make your complete source code available
12
- - You MUST license your application under AGPL-3.0
13
- - Network use counts as distribution (you must share code even for SaaS)
14
- - You MUST provide prominent notice and attribution
15
-
16
- See: https://www.gnu.org/licenses/agpl-3.0.html
17
-
18
- ## 2. COMMERCIAL LICENSE
19
-
20
- If you want to use this software in a proprietary/commercial application
21
- WITHOUT the obligations of AGPL-3.0, you must purchase a commercial license.
22
-
23
- **Commercial License Benefits:**
24
- - No obligation to open-source your code
25
- - Can be used in proprietary/closed-source applications
26
- - Can be used in SaaS without sharing code
27
- - Priority support and updates
28
- - No copyleft requirements
29
-
30
- **Purchase:** [Deine Website]
31
- **Contact:** [Deine Email]
32
-
33
- ---
34
-
35
- **Note:** Using this software without a valid license (either AGPL-3.0 compliance
36
- or commercial license) is copyright infringement and will be prosecuted.
37
-
package/LICENSE_GUARD.md DELETED
@@ -1,323 +0,0 @@
1
- # Magic Link License Guard
2
-
3
- Der License Guard schützt und verwaltet das Magic Link Plugin über das bestehende License API System.
4
-
5
- ## 🚀 Features
6
-
7
- - ✅ **Automatische Lizenz-Initialisierung** beim Plugin-Start
8
- - ✅ **Auto-Ping alle 15 Minuten** für Online-Tracking
9
- - ✅ **Geräteerkennung** (DeviceID, DeviceName, IP, UserAgent)
10
- - ✅ **Grace Period Support** (24h Offline-Nutzung)
11
- - ✅ **Admin-Endpoints** für Lizenzverwaltung
12
- - ✅ **Automatisches Cleanup** beim Plugin-Stop
13
-
14
- ## 📋 Wie es funktioniert
15
-
16
- ### 1. Beim Plugin-Start
17
-
18
- ```
19
- Plugin startet → License Guard initialisiert
20
-
21
- Lizenzschlüssel im Store?
22
- ↓ JA ↓ NEIN
23
- Verifizieren Demo-Mode
24
- ↓ ↓
25
- Gültig? Warnung anzeigen
26
- ↓ JA ↓ NEIN
27
- Ping starten Demo-Mode
28
- ```
29
-
30
- ### 2. Automatisches Pinging
31
-
32
- Alle 15 Minuten sendet der Guard automatisch:
33
- ```json
34
- POST /api/licenses/ping
35
- {
36
- "licenseKey": "A1B2-C3D4-E5F6-G7H8"
37
- }
38
- ```
39
-
40
- Dies aktualisiert:
41
- - `lastPingAt` - Aktueller Zeitstempel
42
- - `lastActiveAt` - Aktivitäts-Tracker
43
- - `isOnline` - Online-Status basierend auf Grace Period
44
-
45
- ## 🎯 Verwendung
46
-
47
- ### Option 1: Lizenz über API erstellen
48
-
49
- ```bash
50
- curl -X POST http://localhost:1337/api/licenses/create \
51
- -H "Content-Type: application/json" \
52
- -d '{
53
- "email": "admin@example.com",
54
- "firstName": "Admin",
55
- "lastName": "User",
56
- "deviceName": "Server-01",
57
- "deviceId": "device-abc-123",
58
- "ipAddress": "192.168.1.100",
59
- "userAgent": "Strapi/5.11.2"
60
- }'
61
- ```
62
-
63
- **Response:**
64
- ```json
65
- {
66
- "success": true,
67
- "message": "License created successfully",
68
- "data": {
69
- "id": 1,
70
- "licenseKey": "A1B2-C3D4-E5F6-G7H8",
71
- "email": "admin@example.com",
72
- "isActive": true,
73
- ...
74
- }
75
- }
76
- ```
77
-
78
- Der License Guard erkennt automatisch die neue Lizenz beim nächsten Start!
79
-
80
- ### Option 2: Auto-Create über Admin-Endpoint
81
-
82
- ```bash
83
- curl -X POST http://localhost:1337/magic-link/license/auto-create
84
- ```
85
-
86
- Erstellt automatisch eine Lizenz mit Standard-Daten und aktiviert sie sofort.
87
-
88
- ### Option 3: Programmatisch erstellen
89
-
90
- ```javascript
91
- // Im Strapi-Code oder Terminal
92
- await strapi
93
- .plugin('magic-link')
94
- .service('license-guard')
95
- .autoCreateLicense('your@email.com', 'First', 'Last');
96
- ```
97
-
98
- ## 📡 Admin-Endpoints
99
-
100
- Alle Endpoints sind unter `/magic-link/...` verfügbar:
101
-
102
- | Endpoint | Methode | Beschreibung |
103
- |----------|---------|--------------|
104
- | `/license/status` | GET | Aktueller Lizenz-Status |
105
- | `/license/create` | POST | Lizenz erstellen & aktivieren |
106
- | `/license/auto-create` | POST | Auto-Lizenz mit Defaults |
107
- | `/license/ping` | POST | Manueller Ping |
108
- | `/license/stats` | GET | Online-Statistiken |
109
- | `/license/deactivate` | POST | Lizenz deaktivieren |
110
-
111
- ### Beispiele
112
-
113
- **Status abfragen:**
114
- ```bash
115
- curl http://localhost:1337/magic-link/license/status
116
- ```
117
-
118
- **Response:**
119
- ```json
120
- {
121
- "success": true,
122
- "valid": true,
123
- "data": {
124
- "licenseKey": "A1B2-C3D4-E5F6-G7H8",
125
- "isActive": true,
126
- "isExpired": false,
127
- "isOnline": true,
128
- "expiresAt": "2026-10-13T10:00:00.000Z",
129
- "lastPingAt": "2025-10-13T21:00:00.000Z",
130
- "deviceName": "MacBook-Pro.local",
131
- "features": {
132
- "premium": true,
133
- "advanced": false,
134
- "enterprise": false,
135
- "custom": false
136
- },
137
- "maxDevices": 1,
138
- "currentDevices": 1
139
- }
140
- }
141
- ```
142
-
143
- **Manuelle Ping:**
144
- ```bash
145
- curl -X POST http://localhost:1337/magic-link/license/ping
146
- ```
147
-
148
- **Online-Statistiken:**
149
- ```bash
150
- curl http://localhost:1337/magic-link/license/stats
151
- ```
152
-
153
- ## 🔧 Gesammelte Daten
154
-
155
- Der License Guard sammelt automatisch:
156
-
157
- ### Device Information
158
- - **DeviceID**: SHA256-Hash von MAC-Adressen + Hostname
159
- - **DeviceName**: System-Hostname (z.B. "MacBook-Pro.local")
160
- - **IP Address**: Server-IP (erste nicht-interne IPv4)
161
- - **User Agent**: "Strapi/{version} Node/{version} {platform}/{release}"
162
-
163
- ### Beispiel gesammelte Daten:
164
- ```javascript
165
- {
166
- deviceId: "a3f8e92c1d4b5e6f7a8b9c0d1e2f3a4b",
167
- deviceName: "MacBook-Pro.local",
168
- ipAddress: "192.168.1.100",
169
- userAgent: "Strapi/5.11.2 Node/v20.11.0 darwin/23.0.0"
170
- }
171
- ```
172
-
173
- ## 📊 Console Output
174
-
175
- Beim Plugin-Start siehst du eine dieser Meldungen:
176
-
177
- ### ✅ Lizenz aktiv:
178
- ```
179
- ╔════════════════════════════════════════════════════════════════╗
180
- ║ ✅ MAGIC LINK PLUGIN LICENSE ACTIVE ║
181
- ║ ║
182
- ║ License: A1B2-C3D4-E5F6-G7H8 ║
183
- ║ User: Max Mustermann ║
184
- ║ Email: max@example.com ║
185
- ║ ║
186
- ║ 🔄 Auto-pinging every 15 minutes ║
187
- ╚════════════════════════════════════════════════════════════════╝
188
- ```
189
-
190
- ### ⚠️ Demo-Mode:
191
- ```
192
- ╔════════════════════════════════════════════════════════════════╗
193
- ║ ⚠️ MAGIC LINK PLUGIN RUNNING IN DEMO MODE ║
194
- ║ ║
195
- ║ To activate, create a license: ║
196
- ║ POST http://localhost:1337/api/licenses/create ║
197
- ║ ║
198
- ║ Or auto-create with: ║
199
- ║ strapi.plugin("magic-link").service("license-guard") ║
200
- ║ .autoCreateLicense("your@email.com", "First", "Last") ║
201
- ╚════════════════════════════════════════════════════════════════╝
202
- ```
203
-
204
- ## 🔄 Lifecycle
205
-
206
- ### Bootstrap (Plugin Start)
207
- 1. License Guard Service wird geladen
208
- 2. Nach 3 Sekunden: Initialize()
209
- 3. Prüfe auf gespeicherten Lizenzschlüssel
210
- 4. Verifiziere Lizenz
211
- 5. Starte Auto-Ping (alle 15 Min)
212
-
213
- ### Destroy (Plugin Stop)
214
- 1. Stoppe Ping-Interval
215
- 2. Cleanup-Log
216
-
217
- ## 💻 Integration in dein Plugin
218
-
219
- ### Lizenz-Status im Admin-Panel anzeigen
220
-
221
- Erstelle eine Settings-Seite mit Lizenz-Info:
222
-
223
- ```javascript
224
- // In deiner Settings-Component
225
- const { data: licenseStatus } = await get('/magic-link/license/status');
226
-
227
- if (licenseStatus.valid) {
228
- console.log('✅ License active:', licenseStatus.data.licenseKey);
229
- console.log('Features:', licenseStatus.data.features);
230
- } else {
231
- console.log('⚠️ Demo mode or invalid license');
232
- }
233
- ```
234
-
235
- ### Lizenz erstellen aus dem Admin-Panel
236
-
237
- ```javascript
238
- const createLicense = async () => {
239
- const response = await post('/magic-link/license/create', {
240
- email: 'user@example.com',
241
- firstName: 'John',
242
- lastName: 'Doe',
243
- });
244
-
245
- if (response.data.success) {
246
- console.log('License created:', response.data.data.licenseKey);
247
- }
248
- };
249
- ```
250
-
251
- ## 🔐 Sicherheit
252
-
253
- - ✅ **DeviceID ist persistent** - Basiert auf Hardware
254
- - ✅ **Eindeutige Identifikation** - SHA256-Hash
255
- - ✅ **Keine Speicherung sensibler Daten** - Nur Hashes
256
- - ✅ **GDPR-konform** - User-Daten anonymisiert
257
-
258
- ## 🛠 Troubleshooting
259
-
260
- ### Plugin startet im Demo-Mode
261
-
262
- **Ursache:** Keine Lizenz gefunden oder Lizenz ungültig
263
-
264
- **Lösung:**
265
- ```bash
266
- # Option 1: Auto-Create
267
- curl -X POST http://localhost:1337/magic-link/license/auto-create
268
-
269
- # Option 2: Manuell über License API
270
- curl -X POST http://localhost:1337/api/licenses/create \
271
- -H "Content-Type: application/json" \
272
- -d '{"email":"admin@localhost","firstName":"Admin","lastName":"User"}'
273
-
274
- # Option 3: Im Strapi Terminal
275
- await strapi.plugin('magic-link').service('license-guard')
276
- .autoCreateLicense('admin@example.com', 'Admin', 'User');
277
- ```
278
-
279
- ### Pinging funktioniert nicht
280
-
281
- **Prüfen:**
282
- ```bash
283
- # Manueller Ping
284
- curl -X POST http://localhost:1337/magic-link/license/ping
285
-
286
- # Status prüfen
287
- curl http://localhost:1337/magic-link/license/status
288
- ```
289
-
290
- ### Lizenz als "offline" markiert
291
-
292
- - Grace Period ist abgelaufen (>24h kein Ping)
293
- - Pinging wurde gestoppt
294
- - Netzwerk-Probleme
295
-
296
- **Lösung:** Einmal pingen → `isOnline` wird wieder auf `true` gesetzt
297
-
298
- ## 📚 Logs
299
-
300
- Der License Guard loggt:
301
-
302
- ```
303
- [INFO] 🔐 Initializing License Guard...
304
- [INFO] 📄 Found existing license key: A1B2-C3D4-E5F6-G7H8
305
- [INFO] ✅ License is valid and active
306
- [INFO] 📡 Started pinging license every 15 minutes
307
- [DEBUG] 📡 License ping successful: A1B2-C3D4-E5F6-G7H8
308
- [INFO] 🛑 License pinging stopped
309
- ```
310
-
311
- ## 🎉 Fertig!
312
-
313
- Der License Guard läuft nun automatisch im Hintergrund:
314
- - ✅ Verifiziert beim Start
315
- - ✅ Pingt alle 15 Minuten
316
- - ✅ Tracked Online-Status
317
- - ✅ Bereit für Production
318
-
319
- Für weitere Informationen siehe:
320
- - `/src/api/license/README.md` - License API Dokumentation
321
- - `/src/api/license/TRACKING.md` - Tracking-Details
322
- - `/src/api/license/SUMMARY.md` - Zusammenfassung
323
-
package/SECURITY.md DELETED
@@ -1,79 +0,0 @@
1
- # Security & License Protection
2
-
3
- ## License Enforcement
4
-
5
- This software includes multiple layers of license protection:
6
-
7
- ### 1. Backend License Check
8
- - API endpoints are protected by `license-check` policy
9
- - All requests are validated against active license
10
- - Invalid/expired licenses are rejected with 401 status
11
-
12
- ### 2. Frontend License Guard
13
- - Admin UI displays activation modal without valid license
14
- - Prevents unauthorized UI access
15
- - Requires license activation before use
16
-
17
- ### 3. License Verification
18
- - License keys are validated against central license database
19
- - Regular "ping" mechanism ensures license is active
20
- - Offline grace period for temporary network issues
21
-
22
- ## Anti-Piracy Measures
23
-
24
- ### Current Protection:
25
- 1. **License Key Validation**: Server-side verification
26
- 2. **Device Binding**: License tied to specific server instances
27
- 3. **Online Checks**: Regular validation against license server
28
- 4. **IP Tracking**: Monitor and limit license usage by IP
29
- 5. **Audit Logging**: All license operations are logged
30
-
31
- ### Additional Recommendations:
32
-
33
- #### Code Obfuscation (Optional)
34
- Consider obfuscating critical parts of the code:
35
- ```bash
36
- npm install javascript-obfuscator --save-dev
37
- ```
38
-
39
- #### API Key Protection
40
- Never commit license server API credentials to git:
41
- ```bash
42
- # Add to .env
43
- LICENSE_SERVER_URL=https://your-license-server.com
44
- LICENSE_API_KEY=your-secret-key
45
- ```
46
-
47
- #### Monitoring
48
- Set up monitoring for:
49
- - Unusual license activation patterns
50
- - Multiple IPs using same license
51
- - Attempts to bypass license checks
52
-
53
- ## Reporting License Violations
54
-
55
- If you discover unauthorized use of this software:
56
-
57
- **Email:** [Your Email]
58
- **Subject:** License Violation Report
59
-
60
- Include:
61
- - URL or location where unauthorized use was found
62
- - Screenshots/evidence
63
- - Any relevant information
64
-
65
- We take license violations seriously and will pursue legal action when necessary.
66
-
67
- ## Legal Notice
68
-
69
- Unauthorized use, copying, or distribution of this software constitutes:
70
- - Copyright infringement
71
- - Breach of software license agreement
72
- - Potential criminal violation under applicable laws
73
-
74
- Violators will be prosecuted to the fullest extent of the law.
75
-
76
- ---
77
-
78
- **Last Updated:** 2025-01-13
79
-