Get started

The client APP

Its available on Google play

For site owners

Register OR update your site

Site details
Site contact person

Site contact email

Site Name

Site domain


Domain ownership verification
DNS (TXT) verification: (add 'SNOTY' to TXT record) Not implemented yet
HTTPS-verification: (add $SITE_DOMAIN/.snoty containing string 'SNOTY')
SSH-verification: (Create a user named 'snoty', with pw: '$YOUR_CONTACT_EMAIL') Not implemented yet
Pre shared key-verification

WebHook URL (not required)
You need to implement handling:
Request: POST WEBHOOK_URL?e=pair
BODY JSON payload: { username: 'XXX', password: 'YOUR SITE PW, you should validate it against the one you get after you click register' }
You should return JSON.
Return A: { token: '+4712345678' }.
Return B: { token: 'post@email.com' }.
Return C: { token: null, type: 'email/sms' } if you want to manage sending this code your self. The 'type' is where you sent the code to the user.
Return X: { token: false } if the username provided is invalid.
NB: You do not need to save this if you provide the service with a real code. We only do this to verify that the username exists.
NB2: If you provide the code yourself, you need to implement request POST WEBHOOK_URL?e=pair_validation BODY: code=CLIENT_INPUT where you respond with JSON: { valid: boolean }


URL:


Site password (if you are updating an existing site config)

Sending to devices

Site details
Domain


Password (Received when registering site)


Notification
Title


Message


TTL (seconds)

To - Client token
You received client token upon client subscription on your webhook. You can also poll a list using /sites/subscriptions


Requires verification? (code or biometric verification before launching action url/opening 2fa)


Display type:
Webview (shows action url in webview)
Twofactor (shows 2fa view and pings the action url when answered)

Action url
Used to deliver 2fa answers, and is the src for webview. None is valid, you then need to poll on /response/{id}, example below

Documentation

I'm providing some examples instead

Examples

Webhook backend:

PHP
$fh = fopen('2fa_log.txt', 'a');
fwrite($fh, json_encode(['get' => $_GET, 'post' => $_POST]) . PHP_EOL);

$event = isset($_GET['e']) ? $_GET['e'] : null;
$post  = !empty($_POST) ? $_POST : [];

$mySavedSitePassword = 'SECRET_SITE_PASSWORD';

if ($event !== null) {
    $sitePassword = $post['password'];
}

// Send invalid pairing attempt (bad username)
if ($event === 'pair') {
    $username = $post['username'];
    // Lookup username.

    // Return response.
    // Let snoty send code by SMS.
    //echo json_encode(['token' => '92209598']);

    // Let snoty send email with code.
    echo json_encode(['token' => 'post@robertsvendsen.no']);

    // Let me handle sending the code.
    // sendCode($username);
    // echo json_encode(['token' => null, 'type' => 'email']);
    // echo json_encode(['token' => null, 'type' => 'sms']);
}

if ($event === 'subscribed') {
    $token    = $post['client_token'];
    $username = $post['username'];

    // Register this token to the user. You need it to send 2FA requests.
    // registerToken($username, $token);
}

// This is needed if you send the pairing code youself.
if ($event === 'pair_validation') {
    $username = $post['username'];
    $code     = $post['code'];

    // Lookup user.
    $savedUserCode = '123456';
    $isValid       = $code === $savedUserCode;
    echo json_encode(['status' => $isValid]);
}

    



Sending webview to client

PHP
$apiUrl = 'https://snoty.robertsvendsen.no/send';

$sitePassword = file_get_contents('./password.php');
$receiverClientToken = 'fb75f7c2bd7020ffeefc8ca5cb7386cc';
$loginUsername = $argv[1];
$loginPassword = $argv[2];
$loginIP = isset($argv[3]) ? $argv[3] : '';

echo json_encode(curl_post($apiUrl, [
    'domain' => gethostname(),
    'client_token' => $receiverClientToken,
    'password' => md5($sitePassword . time()),
    'ttl' => 60,
    'title' => 'A new movie is available',
    'display_type' => 'webview',
    'message' => 'Avengers 4: Apocalypse is available in cinemas',
    'action_url' => 'https://www.imdb.com/title/tt0458339/videoplayer/vi2912787481?ref_=tt_ov_vi',
    'image' => 'https://m.media-amazon.com/images/M/MV5BMTYzOTc2NzU3N15BMl5BanBnXkFtZTcwNjY3MDE3NQ@@._V1_UX182_CR0,0,182,268_AL_.jpg',
]));

function curl_post($url, array $post = NULL, array $options = array())
{
    $defaults = array(
        CURLOPT_POST => 1,
        CURLOPT_HEADER => 0,
        CURLOPT_URL => $url,
        CURLOPT_FRESH_CONNECT => 1,
        CURLOPT_RETURNTRANSFER => 1,
        CURLOPT_FORBID_REUSE => 1,
        CURLOPT_TIMEOUT => 4,
        CURLOPT_POSTFIELDS => http_build_query($post)
    );

    $ch = curl_init();
    curl_setopt_array($ch, ($options + $defaults));
    if( ! $result = curl_exec($ch))
    {
        trigger_error(curl_error($ch));
    }
    curl_close($ch);
    return $result;
}

    



Sending 2FA request

PHP
$apiUrl = 'https://ononeapp.com/send';

$sitePassword = file_get_contents('./password.php');
$receiverClientToken = 'fb75f7c2bd7020ffeefc8ca5cb7386cc';
$loginUsername = $argv[1];
$loginPassword = $argv[2];
$loginIP = isset($argv[3]) ? $argv[3] : '';

echo json_encode(curl_post($apiUrl, [
    'domain' => gethostname(),
    'client_token' => $receiverClientToken,
    'password' => md5($sitePassword . time()),
    'ttl' => 60,
    'title' => 'Login verification',
    'display_type' => '2fa',
    'message' => 'A login is awaiting action',
    //'action_url' => 'https://sdev.robertsvendsen.no/2fa?e=test&answer=y',
    'login_username' => $loginUsername,
    'login_password' => $loginPassword,
    'login_ip' => $loginIP,
    //'login_date' => 'date formatted string of any kind',
]));

function curl_post($url, array $post = NULL, array $options = array())
{
    $defaults = array(
        CURLOPT_POST => 1,
        CURLOPT_HEADER => 0,
        CURLOPT_URL => $url,
        CURLOPT_FRESH_CONNECT => 1,
        CURLOPT_RETURNTRANSFER => 1,
        CURLOPT_FORBID_REUSE => 1,
        CURLOPT_TIMEOUT => 4,
        CURLOPT_POSTFIELDS => http_build_query($post)
    );

    $ch = curl_init();
    curl_setopt_array($ch, ($options + $defaults));
    if( ! $result = curl_exec($ch))
    {
        trigger_error(curl_error($ch));
    }
    curl_close($ch);
    return $result;
}

    




Polling sites/subscriptions (getting client tokens)

PHP
$apiUrl = 'https://ononeapp.com/sites/subscriptions/';
$id = isset($argv[1]) ? $argv[1] : '';
$apiUrl .= $id;

var_dump(curl_post($apiUrl, [
    'password' => md5(file_get_contents('./password.php') . time()),
    'domain' => gethostname(),
]));

function curl_post($url, array $post = NULL, array $options = array())
{
    $defaults = array(
        CURLOPT_POST => 1,
        CURLOPT_HEADER => 0,
        CURLOPT_URL => $url,
        CURLOPT_FRESH_CONNECT => 1,
        CURLOPT_RETURNTRANSFER => 1,
        CURLOPT_FORBID_REUSE => 1,
        CURLOPT_TIMEOUT => 4,
        CURLOPT_POSTFIELDS => http_build_query($post)
    );

    $ch = curl_init();
    curl_setopt_array($ch, ($options + $defaults));
    if( ! $result = curl_exec($ch))
    {
        trigger_error(curl_error($ch));
    }
    curl_close($ch);
    return $result;
}
    




Polling responses (getting responses from app without webhook)

PHP
$apiUrl = 'https://ononeapp.com/response/';
$id = $argv[1];
$apiUrl .= $id;

// create curl resource
$ch = curl_init();

// set url
curl_setopt($ch, CURLOPT_URL, $apiUrl);
curl_setopt($ch, CURLOPT_TIMEOUT, 300);
curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 5);

//return the transfer as a string
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);

// $output contains the output string
$output = curl_exec($ch);

// close curl resource to free up system resources
curl_close($ch);

echo json_encode($output);