evernote-enterprise-rbac
Implement enterprise RBAC for Evernote integrations. Use when building multi-tenant systems, implementing role-based access, or handling business accounts. Trigger with phrases like "evernote enterprise", "evernote rbac", "evernote business", "evernote permissions". allowed-tools: Read, Write, Edit, Grep version: 1.0.0 license: MIT author: Jeremy Longshore <jeremy@intentsolutions.io>
Allowed Tools
No tools specified
Provided by Plugin
evernote-pack
Claude Code skill pack for Evernote (24 skills)
Installation
This skill is included in the evernote-pack plugin:
/plugin install evernote-pack@claude-code-plugins-plus
Click to copy
Instructions
# Evernote Enterprise RBAC
## Overview
Implement role-based access control for Evernote integrations, including multi-tenant architecture, business account handling, and permission management.
## Prerequisites
- Understanding of Evernote Business accounts
- Multi-tenant application architecture
- Authentication/authorization infrastructure
## Evernote Permission Levels
| Level | Scope | Capabilities |
|-------|-------|--------------|
| Read | Note | View content |
| Modify | Note | Edit content |
| Full Access | Note | Edit, share, delete |
| Notebook Read | Notebook | Read all notes |
| Notebook Modify | Notebook | Edit all notes |
| Notebook Full | Notebook | Full control |
## Instructions
### Step 1: Permission Model
```javascript
// models/permissions.js
const PermissionLevel = {
NONE: 0,
READ: 1,
READ_ACTIVITY: 2,
MODIFY: 3,
FULL_ACCESS: 4
};
const NotebookPrivilegeLevel = {
READ_NOTEBOOK: 'READ_NOTEBOOK',
MODIFY_NOTEBOOK_PLUS_ACTIVITY: 'MODIFY_NOTEBOOK_PLUS_ACTIVITY',
READ_NOTEBOOK_PLUS_ACTIVITY: 'READ_NOTEBOOK_PLUS_ACTIVITY',
GROUP: 'GROUP',
FULL_ACCESS: 'FULL_ACCESS'
};
class Permission {
constructor(data) {
this.level = data.level;
this.scope = data.scope; // 'note', 'notebook', 'global'
this.targetGuid = data.targetGuid;
this.grantedBy = data.grantedBy;
this.expiresAt = data.expiresAt;
}
canRead() {
return this.level >= PermissionLevel.READ;
}
canModify() {
return this.level >= PermissionLevel.MODIFY;
}
canDelete() {
return this.level >= PermissionLevel.FULL_ACCESS;
}
canShare() {
return this.level >= PermissionLevel.FULL_ACCESS;
}
isExpired() {
return this.expiresAt && Date.now() > this.expiresAt;
}
}
module.exports = { PermissionLevel, NotebookPrivilegeLevel, Permission };
```
### Step 2: Role Definitions
```javascript
// models/roles.js
const Roles = {
// System roles
SUPER_ADMIN: {
name: 'super_admin',
description: 'Full system access',
permissions: ['*'],
evernoteAccess: 'full'
},
ADMIN: {
name: 'admin',
description: 'Organization administrator',
permissions: [
'users:read', 'users:write',
'notebooks:read', 'notebooks:write',
'notes:read', 'notes:write',
'settings:read', 'settings:write'
],
evernoteAccess: 'full'
},
MANAGER: {
name: 'manager',
description: 'Team manager',
permissions: [
'users:read',
'notebooks:read', 'notebooks:write',
'notes:read', 'notes:write'
],
evernoteAccess: 'modify'
},
MEMBER: {
name: 'member',
description: 'Regular team member',
permissions: [
'notebooks:read',
'notes:read', 'notes:write'
],
evernoteAccess: 'modify'
},
VIEWER: {
name: 'viewer',
description: 'Read-only access',
permissions: [
'notebooks:read',
'notes:read'
],
evernoteAccess: 'read'
},
GUEST: {
name: 'guest',
description: 'Limited guest access',
permissions: [
'shared:read'
],
evernoteAccess: 'read'
}
};
class Role {
constructor(roleConfig) {
this.name = roleConfig.name;
this.description = roleConfig.description;
this.permissions = roleConfig.permissions;
this.evernoteAccess = roleConfig.evernoteAccess;
}
hasPermission(permission) {
if (this.permissions.includes('*')) return true;
return this.permissions.includes(permission);
}
canAccessEvernote(level) {
const levels = ['none', 'read', 'modify', 'full'];
return levels.indexOf(this.evernoteAccess) >= levels.indexOf(level);
}
}
module.exports = { Roles, Role };
```
### Step 3: RBAC Service
```javascript
// services/rbac-service.js
const { Role, Roles } = require('../models/roles');
const { Permission, PermissionLevel } = require('../models/permissions');
class RBACService {
constructor(db) {
this.db = db;
this.permissionCache = new Map();
}
/**
* Assign role to user
*/
async assignRole(userId, roleName, organizationId = null) {
await this.db.query(`
INSERT INTO user_roles (user_id, role_name, organization_id)
VALUES ($1, $2, $3)
ON CONFLICT (user_id, organization_id) DO UPDATE SET
role_name = EXCLUDED.role_name,
updated_at = NOW()
`, [userId, roleName, organizationId]);
this.invalidateCache(userId);
}
/**
* Get user's role
*/
async getUserRole(userId, organizationId = null) {
const result = await this.db.query(`
SELECT role_name FROM user_roles
WHERE user_id = $1 AND (organization_id = $2 OR organization_id IS NULL)
ORDER BY organization_id NULLS LAST
LIMIT 1
`, [userId, organizationId]);
if (result.rows.length === 0) {
return new Role(Roles.GUEST);
}
const roleConfig = Roles[result.rows[0].role_name.toUpperCase()];
return roleConfig ? new Role(roleConfig) : new Role(Roles.GUEST);
}
/**
* Check if user has permission
*/
async hasPermission(userId, permission, organizationId = null) {
const role = await this.getUserRole(userId, organizationId);
return role.hasPermission(permission);
}
/**
* Grant note-level permission
*/
async grantNotePermission(noteGuid, userId, level, grantedBy, expiresAt = null) {
await this.db.query(`
INSERT INTO note_permissions (note_guid, user_id, permission_level, granted_by, expires_at)
VALUES ($1, $2, $3, $4, $5)
ON CONFLICT (note_guid, user_id) DO UPDATE SET
permission_level = EXCLUDED.permission_level,
granted_by = EXCLUDED.granted_by,
expires_at = EXCLUDED.expires_at,
updated_at = NOW()
`, [noteGuid, userId, level, grantedBy, expiresAt]);
this.invalidateCache(userId);
}
/**
* Check user's permission on a note
*/
async getNotePermission(noteGuid, userId) {
// Check direct permission
const direct = await this.db.query(`
SELECT permission_level, expires_at FROM note_permissions
WHERE note_guid = $1 AND user_id = $2
`, [noteGuid, userId]);
if (direct.rows.length > 0) {
const perm = direct.rows[0];
if (!perm.expires_at || Date.now() < new Date(perm.expires_at).getTime()) {
return new Permission({
level: perm.permission_level,
scope: 'note',
targetGuid: noteGuid
});
}
}
// Check notebook-level permission
const notebook = await this.db.query(`
SELECT np.permission_level, np.expires_at
FROM notebook_permissions np
JOIN notes n ON n.notebook_guid = np.notebook_guid
WHERE n.guid = $1 AND np.user_id = $2
`, [noteGuid, userId]);
if (notebook.rows.length > 0) {
const perm = notebook.rows[0];
if (!perm.expires_at || Date.now() < new Date(perm.expires_at).getTime()) {
return new Permission({
level: perm.permission_level,
scope: 'notebook',
targetGuid: noteGuid
});
}
}
// No permission
return new Permission({
level: PermissionLevel.NONE,
scope: 'none',
targetGuid: noteGuid
});
}
/**
* List accessible notebooks for user
*/
async getAccessibleNotebooks(userId, organizationId = null) {
const role = await this.getUserRole(userId, organizationId);
// Admins and above see all
if (role.hasPermission('notebooks:read') && role.name === 'admin') {
return this.db.query(`
SELECT * FROM notebooks WHERE organization_id = $1
`, [organizationId]);
}
// Others see only permitted notebooks
return this.db.query(`
SELECT n.* FROM notebooks n
LEFT JOIN notebook_permissions np ON n.guid = np.notebook_guid AND np.user_id = $2
WHERE n.organization_id = $1
AND (n.owner_id = $2 OR np.permission_level IS NOT NULL)
`, [organizationId, userId]);
}
invalidateCache(userId) {
// Clear cached permissions for user
for (const key of this.permissionCache.keys()) {
if (key.startsWith(`${userId}:`)) {
this.permissionCache.delete(key);
}
}
}
}
module.exports = RBACService;
```
### Step 4: Authorization Middleware
```javascript
// middleware/authorization.js
const RBACService = require('../services/rbac-service');
function authorize(requiredPermission) {
return async (req, res, next) => {
const rbac = new RBACService(req.app.get('db'));
const userId = req.user?.id;
const organizationId = req.params.organizationId || req.user?.organizationId;
if (!userId) {
return res.status(401).json({ error: 'Authentication required' });
}
const hasPermission = await rbac.hasPermission(userId, requiredPermission, organizationId);
if (!hasPermission) {
return res.status(403).json({
error: 'Forbidden',
required: requiredPermission
});
}
next();
};
}
function authorizeNoteAccess(requiredLevel) {
return async (req, res, next) => {
const rbac = new RBACService(req.app.get('db'));
const userId = req.user?.id;
const noteGuid = req.params.noteGuid;
if (!userId) {
return res.status(401).json({ error: 'Authentication required' });
}
const permission = await rbac.getNotePermission(noteGuid, userId);
if (!checkPermissionLevel(permission, requiredLevel)) {
return res.status(403).json({
error: 'Forbidden',
required: requiredLevel,
current: permission.level
});
}
req.notePermission = permission;
next();
};
}
function checkPermissionLevel(permission, required) {
const levels = {
'read': permission.canRead(),
'modify': permission.canModify(),
'delete': permission.canDelete(),
'share': permission.canShare()
};
return levels[required] || false;
}
module.exports = { authorize, authorizeNoteAccess };
```
### Step 5: Multi-Tenant Support
```javascript
// services/tenant-service.js
class TenantService {
constructor(db) {
this.db = db;
}
/**
* Create new organization (tenant)
*/
async createOrganization(data) {
const result = await this.db.query(`
INSERT INTO organizations (name, slug, owner_id, settings)
VALUES ($1, $2, $3, $4)
RETURNING *
`, [data.name, data.slug, data.ownerId, JSON.stringify(data.settings || {})]);
const org = result.rows[0];
// Assign owner as admin
await this.db.query(`
INSERT INTO user_roles (user_id, role_name, organization_id)
VALUES ($1, 'admin', $2)
`, [data.ownerId, org.id]);
return org;
}
/**
* Add user to organization
*/
async addMember(organizationId, userId, roleName = 'member') {
// Check if already member
const existing = await this.db.query(`
SELECT id FROM organization_members
WHERE organization_id = $1 AND user_id = $2
`, [organizationId, userId]);
if (existing.rows.length > 0) {
throw new Error('User already member of organization');
}
await this.db.query(`
INSERT INTO organization_members (organization_id, user_id)
VALUES ($1, $2)
`, [organizationId, userId]);
await this.db.query(`
INSERT INTO user_roles (user_id, role_name, organization_id)
VALUES ($1, $2, $3)
`, [userId, roleName, organizationId]);
return { success: true };
}
/**
* Remove user from organization
*/
async removeMember(organizationId, userId) {
await this.db.query(`
DELETE FROM user_roles WHERE user_id = $1 AND organization_id = $2
`, [userId, organizationId]);
await this.db.query(`
DELETE FROM organization_members WHERE organization_id = $1 AND user_id = $2
`, [organizationId, userId]);
return { success: true };
}
/**
* Get organization settings
*/
async getSettings(organizationId) {
const result = await this.db.query(`
SELECT settings FROM organizations WHERE id = $1
`, [organizationId]);
return result.rows[0]?.settings || {};
}
/**
* Update organization settings
*/
async updateSettings(organizationId, settings) {
await this.db.query(`
UPDATE organizations SET settings = $1, updated_at = NOW()
WHERE id = $2
`, [JSON.stringify(settings), organizationId]);
return { success: true };
}
}
module.exports = TenantService;
```
### Step 6: Evernote Business Integration
```javascript
// services/evernote-business-service.js
const Evernote = require('evernote');
class EvernoteBusinessService {
constructor(accessToken, businessToken) {
this.personalClient = new Evernote.Client({
token: accessToken,
sandbox: false
});
this.businessToken = businessToken;
}
/**
* Get business user store
*/
async getBusinessUserStore() {
const userStore = this.personalClient.getUserStore();
// Get business info
const user = await userStore.getUser();
if (!user.businessUserInfo) {
throw new Error('User is not a business member');
}
// Create business client
const businessClient = new Evernote.Client({
token: this.businessToken,
sandbox: false
});
return businessClient.getUserStore();
}
/**
* Get business note store
*/
async getBusinessNoteStore() {
const userStore = this.personalClient.getUserStore();
const user = await userStore.getUser();
if (!user.businessUserInfo) {
throw new Error('User is not a business member');
}
const businessClient = new Evernote.Client({
token: this.businessToken,
sandbox: false
});
return businessClient.getNoteStore();
}
/**
* List business notebooks
*/
async listBusinessNotebooks() {
const noteStore = await this.getBusinessNoteStore();
return noteStore.listNotebooks();
}
/**
* Create business notebook
*/
async createBusinessNotebook(name) {
const noteStore = await this.getBusinessNoteStore();
const notebook = new Evernote.Types.Notebook();
notebook.name = name;
return noteStore.createNotebook(notebook);
}
/**
* Share notebook with team
*/
async shareNotebookWithTeam(notebookGuid, emails, privilege) {
const noteStore = await this.getBusinessNoteStore();
const sharedNotebook = new Evernote.Types.SharedNotebook();
sharedNotebook.notebookGuid = notebookGuid;
sharedNotebook.email = emails.join(',');
sharedNotebook.privilege = privilege;
return noteStore.createSharedNotebook(sharedNotebook);
}
}
module.exports = EvernoteBusinessService;
```
### Step 7: API Routes with RBAC
```javascript
// routes/notes.js
const express = require('express');
const { authorize, authorizeNoteAccess } = require('../middleware/authorization');
const router = express.Router();
// List notes - requires read permission
router.get('/',
authorize('notes:read'),
async (req, res) => {
// ... list notes
}
);
// Get single note - requires note-level read
router.get('/:noteGuid',
authorizeNoteAccess('read'),
async (req, res) => {
// ... get note
}
);
// Update note - requires note-level modify
router.put('/:noteGuid',
authorizeNoteAccess('modify'),
async (req, res) => {
// ... update note
}
);
// Delete note - requires note-level delete
router.delete('/:noteGuid',
authorizeNoteAccess('delete'),
async (req, res) => {
// ... delete note
}
);
// Share note - requires note-level share
router.post('/:noteGuid/share',
authorizeNoteAccess('share'),
async (req, res) => {
// ... share note
}
);
module.exports = router;
```
## Output
- Permission and role models
- RBAC service implementation
- Authorization middleware
- Multi-tenant support
- Evernote Business integration
- Protected API routes
## Database Schema
```sql
-- Organizations (tenants)
CREATE TABLE organizations (
id SERIAL PRIMARY KEY,
name VARCHAR(255) NOT NULL,
slug VARCHAR(100) UNIQUE NOT NULL,
owner_id INTEGER,
settings JSONB DEFAULT '{}',
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
-- Organization members
CREATE TABLE organization_members (
organization_id INTEGER REFERENCES organizations(id),
user_id INTEGER,
joined_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (organization_id, user_id)
);
-- User roles
CREATE TABLE user_roles (
user_id INTEGER,
role_name VARCHAR(50) NOT NULL,
organization_id INTEGER REFERENCES organizations(id),
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
UNIQUE (user_id, organization_id)
);
-- Note permissions
CREATE TABLE note_permissions (
note_guid UUID,
user_id INTEGER,
permission_level INTEGER NOT NULL,
granted_by INTEGER,
expires_at TIMESTAMP,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (note_guid, user_id)
);
-- Notebook permissions
CREATE TABLE notebook_permissions (
notebook_guid UUID,
user_id INTEGER,
permission_level INTEGER NOT NULL,
granted_by INTEGER,
expires_at TIMESTAMP,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (notebook_guid, user_id)
);
```
## Resources
- [Evernote Business](https://evernote.com/business)
- [Sharing and Permissions](https://dev.evernote.com/doc/articles/sharing.php)
- [API Key Permissions](https://dev.evernote.com/doc/articles/permissions.php)
## Next Steps
For migration strategies, see `evernote-migration-deep-dive`.