Skip to content

Allow2/Allow2node-service

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

2 Commits
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Allow2 Parental Freedom Service SDK for Node.js

Developer Resources -- The Allow2 MCP Server provides comprehensive API documentation, integration guides, architecture overviews, and interactive examples. Connect it to your AI coding assistant for the best development experience. Start there.

Server-side SDK for integrating Allow2 Parental Freedom into websites and web applications.

This is a Service SDK -- it runs on your server, not on the child's device. If you are building a device or product owned by a family (a game, smart device, desktop app, browser extension, etc.), you need the Device SDK instead.

Each user account on your service maps to exactly one Allow2 child identity via OAuth2.

Requirements

  • Node.js 18+ (uses native fetch)
  • No external dependencies

Installation

npm install allow2-service

Quick Start

var Allow2Client = require('allow2-service');
var MemoryTokenStorage = require('allow2-service/storage/MemoryTokenStorage');

var client = new Allow2Client({
  clientId: 'your-client-id',
  clientSecret: 'your-client-secret',
  tokenStorage: new MemoryTokenStorage(),
});

For production use, replace MemoryTokenStorage with FileTokenStorage or your own implementation of TokenStorageInterface (e.g., database-backed).

OAuth2 Pairing

Each user account on your service is linked to one Allow2 child identity. The parent authorizes the connection through the OAuth2 flow.

Step 1: Redirect to Allow2

// Generate the authorization URL
var url = client.getAuthorizeUrl(userId, 'https://yourapp.com/callback', 'csrf-state');
// Redirect the parent to this URL
res.redirect(url);

Step 2: Handle the Callback

app.get('/callback', async function (req, res) {
  var code = req.query.code;
  var userId = getCurrentUserId(req);

  var tokens = await client.exchangeCode(userId, code, 'https://yourapp.com/callback');
  // Tokens are automatically stored. The user is now paired.
  res.redirect('/dashboard');
});

Check Pairing Status

var paired = await client.isPaired(userId);
if (!paired) {
  // Show "Connect with Allow2" button
}

Unpair

await client.unpair(userId);

Permission Checking

Check whether a child is allowed to use your service right now.

Basic Check

var result = await client.check(userId, [1, 3], 'Australia/Brisbane');

if (result.allowed) {
  // Child is allowed -- serve content
} else {
  // Child is blocked -- show block page
}

Detailed Check

var result = await client.check(userId, [
  { id: 1, log: true },   // Internet
  { id: 3, log: false },  // Gaming (don't log usage)
], 'Australia/Brisbane');

// Check specific activities
var internet = result.getActivity(1);
if (internet && internet.allowed) {
  console.log('Internet allowed, ' + internet.remaining + ' seconds left');
}

// Day type info
if (result.todayDayType) {
  console.log('Today is: ' + result.todayDayType.name);
}

Simple Boolean Check

var allowed = await client.isAllowed(userId, [1], 'Australia/Brisbane');
if (!allowed) {
  return res.status(403).send('Access restricted by Allow2 Parental Freedom');
}

Activity Formats

Three input formats are supported:

// Array of objects (most explicit)
[{ id: 1, log: true }, { id: 3, log: false }]

// Array of IDs (all logged by default)
[1, 3, 8]

// Object map (all logged by default)
{ 1: 1, 3: 1, 8: 1 }

Requests

Children can request changes when they are blocked.

Request More Time

var result = await client.requestMoreTime(userId, 3, 30); // 30 more minutes of Gaming
console.log('Request ID: ' + result.requestId);
console.log('Status: ' + result.status); // 'pending'

Request Day Type Change

var result = await client.requestDayTypeChange(userId, 5); // Request Holiday day type

Request Ban Lift

var result = await client.requestBanLift(userId, 3); // Lift Gaming ban

Poll Request Status

var status = await client.getRequestStatus(result.requestId, result.statusSecret);
console.log(status); // { status: 'approved' } or { status: 'denied' }

Voice Codes (Offline Approval)

When the parent has no internet, the child can still get approval via voice codes. The child reads a challenge code to the parent (phone call, in person, etc.), the parent enters it in the Allow2 app (works offline), and reads back the response.

var VoiceCode = require('allow2-service/VoiceCode');
var RequestType = require('allow2-service/models/RequestType').RequestType;

// Generate a challenge-response pair
var pair = client.generateVoiceChallenge(
  childSecret,               // shared secret for this child
  RequestType.MORE_TIME,     // request type
  3,                         // activity ID (Gaming)
  30                         // 30 minutes
);

console.log('Challenge: ' + pair.challenge);         // e.g., "0 03 06 42"
console.log('Expected: ' + pair.expectedResponse);   // e.g., "847291"

// Later, verify the parent's response
var valid = client.verifyVoiceResponse(childSecret, pair.challenge, parentResponse);
if (valid) {
  // Apply the requested change
}

Challenge Format

The challenge is T A MM NN where:

  • T = request type (0=more time, 1=day type, 2=ban lift)
  • A = activity ID (zero-padded to 2 digits)
  • MM = 5-minute blocks of time requested
  • NN = random nonce

Feedback

Children and parents can submit feedback directly to you, the developer.

Submit Feedback

var FeedbackCategory = require('allow2-service/models/FeedbackCategory');

await client.submitFeedback(userId, FeedbackCategory.BUG, 'The block page shows the wrong time remaining');

Load and Reply

var discussions = await client.loadFeedback(userId);

await client.replyToFeedback(userId, discussions[0].id, 'Thanks, we are looking into it.');

Architecture

Allow2Client (facade)
  |
  +-- OAuth2Manager        -- Token exchange, refresh, pairing
  +-- PermissionChecker    -- /serviceapi/check with caching
  +-- RequestManager       -- Two-step request creation, status polling
  +-- VoiceCode            -- HMAC-SHA256 challenge-response
  +-- FeedbackManager      -- Submit, load, reply
  |
  +-- HttpClient           -- Native fetch wrapper (replaceable)
  +-- TokenStorageInterface -- Per-user token persistence (you implement)
  +-- CacheInterface       -- Permission check caching (you implement)

Token Storage

Built-in Implementations

  • MemoryTokenStorage -- In-memory Map. Good for testing.
  • FileTokenStorage -- JSON files on disk with restrictive permissions.

Custom Storage

Implement TokenStorageInterface:

var TokenStorageInterface = require('allow2-service/TokenStorageInterface');

class DatabaseTokenStorage extends TokenStorageInterface {
  async store(userId, tokens) {
    await db.query('INSERT INTO allow2_tokens (user_id, data) VALUES (?, ?) ON DUPLICATE KEY UPDATE data = ?',
      [userId, JSON.stringify(tokens.toObject()), JSON.stringify(tokens.toObject())]);
  }

  async retrieve(userId) {
    var row = await db.query('SELECT data FROM allow2_tokens WHERE user_id = ?', [userId]);
    if (!row) return null;
    var OAuthTokens = require('allow2-service/models/OAuthTokens');
    return OAuthTokens.fromObject(JSON.parse(row.data));
  }

  async delete(userId) {
    await db.query('DELETE FROM allow2_tokens WHERE user_id = ?', [userId]);
  }

  async exists(userId) {
    var row = await db.query('SELECT 1 FROM allow2_tokens WHERE user_id = ?', [userId]);
    return !!row;
  }
}

Cache

Built-in Implementations

  • MemoryCache -- In-memory Map with TTL. Good for single-process apps.
  • FileCache -- Per-key JSON files with TTL.

Custom Cache

Implement CacheInterface:

var CacheInterface = require('allow2-service/CacheInterface');

class RedisCache extends CacheInterface {
  async get(key) {
    var val = await redis.get(key);
    return val ? JSON.parse(val) : null;
  }

  async set(key, value, ttl) {
    await redis.setex(key, ttl, JSON.stringify(value));
  }

  async delete(key) {
    await redis.del(key);
  }
}

Error Handling

var UnpairedError = require('allow2-service/exceptions/UnpairedError');
var TokenExpiredError = require('allow2-service/exceptions/TokenExpiredError');
var ApiError = require('allow2-service/exceptions/ApiError');

try {
  var result = await client.check(userId, [1], 'Australia/Brisbane');
} catch (err) {
  if (err instanceof UnpairedError) {
    // User needs to re-pair via OAuth2
    res.redirect(client.getAuthorizeUrl(userId, redirectUri));
  } else if (err instanceof TokenExpiredError) {
    // Refresh failed -- re-pair needed
    res.redirect(client.getAuthorizeUrl(userId, redirectUri));
  } else if (err instanceof ApiError) {
    // API returned an error
    console.error('API error:', err.httpStatusCode, err.responseBody);
  }
}

Configuration

var client = new Allow2Client({
  clientId: 'your-client-id',         // Required
  clientSecret: 'your-client-secret', // Required
  tokenStorage: storage,               // Required -- TokenStorageInterface implementation
  cache: new MemoryCache(),            // Optional -- defaults to MemoryCache
  httpClient: customHttpClient,        // Optional -- defaults to built-in fetch wrapper
  apiHost: 'https://api.allow2.com',   // Optional -- default
  serviceHost: 'https://service.allow2.com', // Optional -- default
  cacheTtl: 60,                        // Optional -- cache TTL in seconds (default 60)
});

License

See LICENSE file.

Links

About

Allow2 Node Service SDK for OpenSource Parental Freedom

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors