@jalzae/vue-captcha
Version:
Interactive human verification library for Vue 3 and Nuxt 3 with canvas-based CAPTCHA and dice games
724 lines (564 loc) • 13.6 kB
Markdown
# 🚀 @jalzae/vue-captcha - Nuxt Module Documentation
Complete guide for using `@jalzae/vue-captcha` as a Nuxt 3 module.
---
## 📦 Installation
```bash
npm install @jalzae/vue-captcha
```
---
## ⚙️ Quick Setup
### 1. Add to `nuxt.config.ts`
```typescript
export default defineNuxtConfig({
modules: [
'@jalzae/vue-captcha'
]
})
```
That's it! The module is now enabled with default settings.
### 2. Use in Your Component
```vue
<script setup>
const { verifyHuman, isVerifying } = useVerifyHuman()
const handleClick = async () => {
const verified = await verifyHuman()
if (verified) {
alert('✅ You are verified!')
}
}
</script>
<template>
<button @click="handleClick" :disabled="isVerifying">
{{ isVerifying ? 'Verifying...' : 'Verify I\'m Human' }}
</button>
</template>
```
---
## ⚙️ Module Configuration
### Default Options
```typescript
export default defineNuxtConfig({
modules: [
'@jalzae/vue-captcha'
],
verifyCaptcha: {
// Automatically import useVerifyHuman composable
autoImports: true,
// Provide $verifyHuman via plugin
addPlugin: true
}
})
```
### Configuration Options
#### `autoImports` (boolean)
- **Default:** `true`
- **Description:** Auto-imports the `useVerifyHuman` composable so you don't need to manually import it in every component
```typescript
// With autoImports: true (default)
const { verifyHuman } = useVerifyHuman() // ✅ Works directly
// With autoImports: false
import { useVerifyHuman } from '@jalzae/vue-captcha'
const { verifyHuman } = useVerifyHuman() // ✅ Need manual import
```
#### `addPlugin` (boolean)
- **Default:** `true`
- **Description:** Registers the `$verifyHuman` plugin for access via `useNuxtApp()`
```typescript
// With addPlugin: true (default)
const { $verifyHuman } = useNuxtApp()
const result = await $verifyHuman() // ✅ Works
// With addPlugin: false
// $verifyHuman is not available via useNuxtApp()
```
---
## 📚 Usage Methods
### Method 1: Composable (Recommended)
Best for components that need reactive state and error handling.
```vue
<script setup>
const {
verifyHuman, // Function to trigger verification
reset, // Reset verification state
isVerifying, // Loading state while modal is open
verified, // Whether last verification succeeded
error // Error message if any
} = useVerifyHuman()
const handleVerify = async () => {
const result = await verifyHuman()
if (result) {
console.log('✅ Verification successful')
// Proceed with action
} else {
console.log('❌ Verification cancelled')
}
}
const handleReset = () => {
reset() // Clear all state
}
</script>
<template>
<div class="verify-container">
<!-- Verification button -->
<button
@click="handleVerify"
:disabled="isVerifying"
class="verify-btn"
>
{{ isVerifying ? 'Verifying...' : 'Verify I\'m Human' }}
</button>
<!-- Status messages -->
<div v-if="verified" class="success-message">
✅ You have been verified!
</div>
<div v-if="error" class="error-message">
⚠️ {{ error }}
</div>
<!-- Reset button -->
<button v-if="verified" @click="handleReset" class="reset-btn">
Start Over
</button>
</div>
</template>
<style scoped>
.verify-container {
display: flex;
flex-direction: column;
gap: 12px;
}
.verify-btn {
padding: 10px 20px;
background: #3498db;
color: white;
border: none;
border-radius: 6px;
cursor: pointer;
font-weight: bold;
}
.verify-btn:disabled {
background: #95a5a6;
cursor: not-allowed;
opacity: 0.7;
}
.success-message {
color: green;
font-weight: bold;
}
.error-message {
color: red;
font-weight: bold;
}
.reset-btn {
padding: 8px 16px;
background: #f39c12;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
}
</style>
```
#### Composable API Reference
```typescript
const {
/**
* Triggers the verification modal
* @returns {Promise<boolean>} true if verified, false if cancelled
*/
verifyHuman: () => Promise<boolean>,
/**
* Resets all verification state
*/
reset: () => void,
/**
* Reactive: Whether verification modal is open/loading
*/
isVerifying: Ref<boolean>,
/**
* Reactive: Whether last verification succeeded
*/
verified: Ref<boolean>,
/**
* Reactive: Error message if verification failed
*/
error: Ref<string | null>
} = useVerifyHuman()
```
---
### Method 2: Plugin via useNuxtApp()
Simple method for basic usage without reactive state.
```vue
<script setup>
const { $verifyHuman } = useNuxtApp()
const handleVerify = async () => {
const result = await $verifyHuman()
if (result) {
console.log('✅ Verified!')
}
}
</script>
<template>
<button @click="handleVerify">Verify</button>
</template>
```
---
### Method 3: Direct Import
Works in any Vue app (not specific to Nuxt).
```vue
<script setup>
import { verifyHuman } from '@jalzae/vue-captcha'
const handleVerify = async () => {
const result = await verifyHuman()
if (result) {
console.log('✅ Verified!')
}
}
</script>
<template>
<button @click="handleVerify">Verify</button>
</template>
```
---
## 💡 Real-World Examples
### Example 1: Login Form with Verification
```vue
<template>
<form @submit.prevent="handleSubmit">
<div class="form-group">
<label>Email</label>
<input v-model="email" type="email" required />
</div>
<div class="form-group">
<label>Password</label>
<input v-model="password" type="password" required />
</div>
<button
type="submit"
:disabled="isLoading"
class="submit-btn"
>
{{ isLoading ? 'Logging in...' : 'Login' }}
</button>
<div v-if="error" class="error">{{ error }}</div>
<div v-if="success" class="success">✅ Login successful!</div>
</form>
</template>
<script setup>
import { ref } from 'vue'
import { useRouter } from 'vue-router'
const email = ref('')
const password = ref('')
const isLoading = ref(false)
const error = ref('')
const success = ref(false)
const router = useRouter()
const { verifyHuman } = useVerifyHuman()
const handleSubmit = async () => {
error.value = ''
isLoading.value = true
try {
// Step 1: Verify human
const verified = await verifyHuman()
if (!verified) {
error.value = 'Verification cancelled'
return
}
// Step 2: Login
const response = await fetch('/api/login', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
email: email.value,
password: password.value
})
})
if (!response.ok) {
const data = await response.json()
error.value = data.message || 'Login failed'
return
}
success.value = true
setTimeout(() => {
router.push('/dashboard')
}, 1500)
} catch (err) {
error.value = err instanceof Error ? err.message : 'An error occurred'
} finally {
isLoading.value = false
}
}
</script>
<style scoped>
.form-group {
margin-bottom: 16px;
}
label {
display: block;
margin-bottom: 8px;
font-weight: bold;
}
input {
width: 100%;
padding: 8px;
border: 1px solid #ccc;
border-radius: 4px;
font-size: 14px;
}
.submit-btn {
width: 100%;
padding: 10px;
background: #3498db;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
font-weight: bold;
}
.submit-btn:disabled {
background: #95a5a6;
cursor: not-allowed;
}
.error {
color: red;
margin-top: 10px;
font-weight: bold;
}
.success {
color: green;
margin-top: 10px;
font-weight: bold;
}
</style>
```
### Example 2: Protected Route Middleware
Create `middleware/verify.ts`:
```typescript
import { useVerifyHuman } from '@jalzae/vue-captcha'
export default defineRouteMiddleware(async (to, from) => {
// Check if user is already verified
const verified = useState('userVerified', () => false)
if (!verified.value && to.path === '/protected') {
const { verifyHuman } = useVerifyHuman()
const result = await verifyHuman()
if (!result) {
return navigateTo('/')
}
verified.value = true
}
})
```
Use in page:
```vue
<script setup>
definePageMeta({
middleware: 'verify'
})
</script>
<template>
<div>
<h1>Protected Content</h1>
<p>Only verified humans can see this!</p>
</div>
</template>
```
### Example 3: Comment Submission
```vue
<template>
<div class="comment-section">
<textarea
v-model="comment"
placeholder="Write a comment..."
class="comment-input"
></textarea>
<button
@click="submitComment"
:disabled="isVerifying || isSubmitting"
class="submit-btn"
>
<span v-if="isVerifying">⏳ Verifying...</span>
<span v-else-if="isSubmitting">📤 Posting...</span>
<span v-else>Post Comment</span>
</button>
<div v-if="error" class="error-message">{{ error }}</div>
<div v-if="success" class="success-message">✅ Comment posted!</div>
</div>
</template>
<script setup>
import { ref } from 'vue'
const comment = ref('')
const isSubmitting = ref(false)
const error = ref('')
const success = ref(false)
const { verifyHuman, isVerifying } = useVerifyHuman()
const submitComment = async () => {
if (!comment.value.trim()) {
error.value = 'Comment cannot be empty'
return
}
error.value = ''
success.value = false
try {
// Verify before posting
const verified = await verifyHuman()
if (!verified) return
isSubmitting.value = true
// Submit comment
const response = await fetch('/api/comments', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ text: comment.value })
})
if (!response.ok) {
throw new Error('Failed to post comment')
}
success.value = true
comment.value = ''
setTimeout(() => {
success.value = false
}, 3000)
} catch (err) {
error.value = err instanceof Error ? err.message : 'Error posting comment'
} finally {
isSubmitting.value = false
}
}
</script>
<style scoped>
.comment-section {
display: flex;
flex-direction: column;
gap: 12px;
max-width: 600px;
}
.comment-input {
padding: 12px;
border: 1px solid #ccc;
border-radius: 4px;
font-family: inherit;
resize: vertical;
min-height: 100px;
}
.submit-btn {
padding: 10px 20px;
background: #2ecc71;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
font-weight: bold;
}
.submit-btn:disabled {
background: #95a5a6;
cursor: not-allowed;
opacity: 0.7;
}
.error-message {
color: #e74c3c;
font-weight: bold;
}
.success-message {
color: #27ae60;
font-weight: bold;
}
</style>
```
---
## 🔒 Security Best Practices
### 1. Always Verify on Backend
**Important:** Client-side verification alone is not secure! Always verify on your server.
```typescript
// Backend example (Node.js/Express)
app.post('/api/protected-action', async (req, res) => {
// User passed client-side verification
// Now verify on server side:
1. Check user session
2. Verify request origin
3. Log verification attempt
4. Proceed with action
res.json({ success: true })
})
```
### 2. Rate Limiting
Prevent verification abuse:
```typescript
import rateLimit from 'express-rate-limit'
const verifyLimiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15 minutes
max: 5, // 5 attempts
message: 'Too many verification attempts'
})
app.post('/api/verify-action', verifyLimiter, (req, res) => {
// Handle protected action
})
```
### 3. HTTPS Only
Always use HTTPS in production to prevent man-in-the-middle attacks.
### 4. CORS Configuration
Configure CORS properly:
```typescript
export default defineNuxtConfig({
nitro: {
middleware: {
cors: {
origin: ['https://yourdomain.com'],
credentials: true
}
}
}
})
```
---
## 🐛 Troubleshooting
### Issue: Modal Doesn't Appear
**Solution:** Ensure you're awaiting the promise:
```typescript
// ✅ Correct
const result = await verifyHuman()
// ❌ Wrong
verifyHuman() // Missing await
```
### Issue: Composable Not Found
**Ensure autoImports is enabled:**
```typescript
export default defineNuxtConfig({
verifyCaptcha: {
autoImports: true // Make sure this is true
}
})
```
Or manually import:
```typescript
import { useVerifyHuman } from '@jalzae/vue-captcha'
```
### Issue: $verifyHuman Not Available
**Ensure plugin is enabled:**
```typescript
export default defineNuxtConfig({
verifyCaptcha: {
addPlugin: true // Make sure this is true
}
})
```
---
## 📦 What's Included
When you install the module, you get:
- ✅ `verifyHuman()` - Main verification function
- ✅ `useVerifyHuman()` - Composable with state management
- ✅ `$verifyHuman` - Plugin provider
- ✅ Auto-import of composable
- ✅ 2 verification games (CAPTCHA image + dice)
- ✅ Full type support
---
## 🎯 Next Steps
1. **Install:** `npm install @jalzae/vue-captcha`
2. **Add to nuxt.config.ts:** Include in modules array
3. **Use in component:** Call `useVerifyHuman()` and enjoy!
4. **Customize:** Add your own styling and error handling
5. **Deploy:** Remember to verify on backend!
---
## 📞 Support
For issues or questions:
1. Check the [Troubleshooting](#-troubleshooting) section
2. Review the [examples](#-real-world-examples) above
3. Read the main [README.md](./README.md)
---
**Happy verifying! 🚀**