Skip to content

Allow2/Allow2python-service

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

8 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Allow2 Python Service SDK v2

PyPI version Python versions CI

Official Allow2 Parental Freedom Service SDK for Python -- for web services with user accounts (Django sites, Flask apps, FastAPI, SaaS, forums, etc.).

This is a Service SDK -- it runs on your web server, not on a child's device. Following industry standard practice (Stripe, Firebase, Auth0), Allow2 maintains separate Device and Service SDKs. It handles OAuth2 pairing, permission checking, all 3 request types, voice codes, and feedback via the Allow2 Service API.

Package allow2-service
Targets Python 3.9+
Dependencies requests>=2.28
Language Python (type hints, dataclasses)

Requirements

  • Python 3.9 or later
  • requests -- HTTP requests

Installation

pip install allow2-service

Quick Start

  1. Register your application at developer.allow2.com and note your client_id and client_secret.

  2. Create an Allow2Client and wire up the OAuth2 flow:

from allow2_service import Allow2Client
from allow2_service.storage import FileTokenStorage
from allow2_service.cache import FileCache

allow2 = Allow2Client(
    client_id="YOUR_SERVICE_TOKEN",
    client_secret="YOUR_SERVICE_SECRET",
    token_storage=FileTokenStorage("/var/lib/allow2/tokens.json"),
    cache=FileCache("/tmp/allow2-cache"),
)

# Step 1: Redirect user to Allow2 for pairing
authorize_url = allow2.get_authorize_url(
    user_id=current_user_id,
    redirect_uri="https://yourapp.com/allow2/callback",
    state=csrf_token,
)
# redirect(authorize_url)

# Step 2: Handle the callback
tokens = allow2.exchange_code(
    user_id=current_user_id,
    code=request.args["code"],
    redirect_uri="https://yourapp.com/allow2/callback",
)

# Step 3: Check permissions on every request
result = allow2.check(current_user_id, [1, 3])  # Internet + Gaming

if not result.allowed:
    # Show block page
    pass
else:
    remaining = result.get_remaining_seconds(1)
    # Proceed normally, optionally showing countdown

Key Concept: One Account = One Child

The Service API links a specific user account on your site to exactly one Allow2 child. There is no child selector -- the identity is established at OAuth2 pairing time when the parent selects which child this account belongs to.

This means:

  • Each user account on your site maps to one Allow2 child
  • The parent performs pairing once per account, selecting the child
  • All subsequent permission checks for that account apply to that child automatically
  • Parent/admin accounts can be excluded from checking entirely

OAuth2 Flow

Step 1: Authorization

Redirect the user to Allow2 so their parent can pair the account:

import secrets

state = secrets.token_hex(16)
session["allow2_state"] = state

authorize_url = allow2.get_authorize_url(
    user_id=current_user_id,
    redirect_uri="https://yourapp.com/callback",
    state=state,
)
# redirect(authorize_url)

Step 2: Code Exchange

Handle the OAuth2 callback:

if request.args["state"] != session["allow2_state"]:
    raise ValueError("Invalid state parameter")

tokens = allow2.exchange_code(
    user_id=current_user_id,
    code=request.args["code"],
    redirect_uri="https://yourapp.com/callback",
)
# Tokens are stored automatically via the configured TokenStorage

Step 3: Token Refresh

Tokens expire. The SDK handles refresh automatically -- when you call check() or any other method that requires authentication, the SDK detects expired tokens and refreshes them transparently, persisting the updated tokens via your TokenStorageInterface.

Permission Checking

Check permissions on every page load or API request:

# Simple format -- flat list of activity IDs (auto-expanded with log=True)
result = allow2.check(user_id, [1, 3, 8])  # Internet + Gaming + Screen Time

# Full format -- explicit log flags
result = allow2.check(user_id, [
    {"id": 1, "log": True},   # Internet
    {"id": 8, "log": True},   # Screen Time
], timezone="Australia/Sydney")

if not result.allowed:
    print(f"Blocked! Day type: {result.today_day_type.name}")
    for activity in result.activities:
        if activity.banned:
            print(f"{activity.name} is banned")
        elif not activity.time_block_allowed:
            print(f"{activity.name} outside allowed hours")
else:
    remaining = result.get_remaining_seconds(1)
    # Optionally show countdown in the UI

Convenience Check

# Returns True only if ALL specified activities are allowed
allowed = allow2.is_allowed(user_id, [1, 3])

Caching

The SDK caches check results internally using your configured CacheInterface. The default TTL is 60 seconds and can be overridden via the constructor:

allow2 = Allow2Client(
    client_id="YOUR_TOKEN",
    client_secret="YOUR_SECRET",
    token_storage=storage,
    cache=cache,
    cache_ttl=30,  # cache for 30 seconds
)

Requests

Children can request changes directly from your site. There are three types of request, and the philosophy is simple: the child drives the configuration, the parent just approves or denies.

More Time

request_result = allow2.request_more_time(
    user_id=user_id,
    activity_id=3,       # Gaming
    minutes=30,
    message="Almost done with this level!",
)

print(f"Request ID: {request_result.request_id}")

# Poll for parent response
status = allow2.get_request_status(request_result.request_id, request_result.status_secret)

if status == "approved":
    print("Approved!")
elif status == "denied":
    print("Request denied.")
else:
    print("Still waiting...")

Day Type Change

request_result = allow2.request_day_type_change(
    user_id=user_id,
    day_type_id=2,        # Weekend
    message="We have a day off school today.",
)

Ban Lift

request_result = allow2.request_ban_lift(
    user_id=user_id,
    activity_id=6,       # Social Media
    message="I finished all my homework. Can the ban be lifted?",
)

Voice Codes (Offline Approval)

Even though the child is on a website (online), the parent may have no internet -- perhaps they are at work with no signal, or their phone is flat. Voice codes let the parent approve a request by reading a short numeric code over the phone or in person.

Generate a Challenge

from allow2_service.models import RequestType

pair = allow2.generate_voice_challenge(
    secret=pairing_secret,
    type=RequestType.MORE_TIME,
    activity_id=3,       # Gaming
    minutes=30,          # in 5-min increments
)

print(f"Challenge code: {pair.challenge}")
print("Read this to your parent. Ask them for the response code.")

Verify the Response

is_valid = allow2.verify_voice_response(
    secret=pairing_secret,
    challenge=pair.challenge,
    response=parent_response_code,
)

if is_valid:
    print("Approved! Extra time granted.")
else:
    print("Invalid code. Please try again.")

The codes use HMAC-SHA256 challenge-response, date-bound (expires at midnight). The format is compact enough to read over a phone call: a spaced challenge and a 6-digit response.

Feedback

Let users submit bug reports and feature requests directly to you, the developer:

from allow2_service.models import FeedbackCategory

# Submit feedback -- returns the discussion ID
discussion_id = allow2.submit_feedback(
    user_id=user_id,
    category=FeedbackCategory.BUG,
    message="The block page appears even when I have time remaining.",
)

# Load feedback threads
threads = allow2.load_feedback(user_id)

# Reply to a thread
allow2.reply_to_feedback(user_id, discussion_id, "This happens every Tuesday.")

Architecture

Module Purpose
Allow2Client Main entry point, orchestrates all operations
OAuth2Manager OAuth2 authorize, code exchange, token refresh
PermissionChecker Permission checks with caching
RequestManager All 3 request types with temp token + status polling
VoiceCode HMAC-SHA256 challenge-response for offline approval
FeedbackManager Submit, load, and reply to feedback threads

Models

Model Purpose
CheckResult Parsed permission check response with per-activity status
Activity Single activity's allowed/blocked state and remaining time
DayType Current and upcoming day type information
OAuthTokens Access token, refresh token, expiry
RequestResult Request ID, status secret, and status with helper methods
VoiceCodePair Challenge and expected response pair
RequestType Enum: MORE_TIME, DAY_TYPE_CHANGE, BAN_LIFT
FeedbackCategory Enum: BUG, FEATURE_REQUEST, NOT_WORKING, OTHER

Exceptions

Exception When
Allow2Error Base exception for all SDK errors
ApiError HTTP or API-level errors
TokenExpiredError Token refresh failed (re-pairing needed)
UnpairedError No valid tokens for this user (401/403 from API)

Token Storage

The SDK persists OAuth2 tokens automatically via the TokenStorageInterface you provide at construction. Two built-in adapters are included.

FileTokenStorage

from allow2_service.storage import FileTokenStorage

token_storage = FileTokenStorage("/var/lib/allow2/tokens.json")

MemoryTokenStorage (development/testing)

from allow2_service.storage import MemoryTokenStorage

token_storage = MemoryTokenStorage()

Custom Storage (Django, SQLAlchemy, etc.)

Implement TokenStorageInterface to integrate with your framework:

from allow2_service.token_storage import TokenStorageInterface
from allow2_service.models import OAuthTokens

class DjangoTokenStorage(TokenStorageInterface):
    def store(self, user_id: str, tokens: OAuthTokens) -> None:
        Allow2Token.objects.update_or_create(
            user_id=user_id,
            defaults={
                "access_token": tokens.access_token,
                "refresh_token": tokens.refresh_token,
                "expires_at": tokens.expires_at,
            },
        )

    def retrieve(self, user_id: str) -> OAuthTokens | None:
        try:
            record = Allow2Token.objects.get(user_id=user_id)
        except Allow2Token.DoesNotExist:
            return None
        return OAuthTokens(
            access_token=record.access_token,
            refresh_token=record.refresh_token,
            expires_at=record.expires_at,
        )

    def delete(self, user_id: str) -> None:
        Allow2Token.objects.filter(user_id=user_id).delete()

    def exists(self, user_id: str) -> bool:
        return Allow2Token.objects.filter(user_id=user_id).exists()

Device Operational Lifecycle

The Allow2 Service API follows a 7-step lifecycle, adapted for server-side web applications:

  1. Pairing (one-time) -- OAuth2 flow redirects to Allow2 where the parent selects which child this account belongs to. Tokens are stored per user in your database.

  2. Child Identification (automatic) -- one account = one child. The child's identity is established at pairing time and encoded in the OAuth2 tokens. No child selector is needed.

  3. Parent Access -- admin or parent accounts on your site are simply excluded from Allow2 checking. No special Allow2 flow is needed.

  4. Permission Checks (continuous) -- check on every page load or API request, server-side. Pass log=True to record usage. The response includes remaining time, daily limits, time blocks, day types, and bans.

  5. Warnings & Countdowns -- the server calculates remaining time from the check result. Your frontend displays countdowns and warnings as appropriate (e.g. "5 minutes remaining").

  6. Requests -- child requests changes (more time, day type change, ban lift) from your site. The parent approves or denies from their Allow2 app. For parents without internet, voice codes provide offline approval.

  7. Feedback -- bug reports and feature requests are sent directly to you, the developer, via the Allow2 feedback system. This gives you a built-in support channel without building one yourself.

Offline Operation

"Offline" for a web app sounds contradictory -- but the approval channel can be offline even when the child's browser is online.

When the parent has no internet

The child is on your website (online), but their parent's phone may have no signal. Voice codes solve this:

  1. Child clicks "Request More Time" on your site
  2. Your server generates a spaced challenge code
  3. Child reads the code to their parent (phone call, in person)
  4. Parent enters it into their Allow2 app (works offline) and reads back the 6-digit response
  5. Child enters the response on your site
  6. Your server verifies the HMAC-SHA256 response and grants the time

When your server can't reach Allow2

If the Allow2 API is temporarily unreachable:

  • Cache the last check result -- continue enforcing the last known permissions for a short grace period
  • Deny by default -- after the grace period, block access to prevent bypass
  • Queue requests -- store request attempts and replay them when connectivity resumes

License

Copyright 2017-2026 Allow2 Pty Ltd. All rights reserved.

See LICENSE for details.

About

Allow2 Python Service SDK for OpenSource Parental Freedom

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors