Compare commits

...

5 Commits

Author SHA1 Message Date
Deon George
a5e6caf8fe Enable resize QRCode and calling js independantly of login 2022-09-30 23:58:18 +10:00
Deon George
af1f125866 Complete authentication return pubkey 2020-08-18 22:27:24 +10:00
Deon George
0c91860454 Fix for when clients dont present opts 2020-08-18 19:59:34 +10:00
Deon George
bdff9462d3 Minor items 2020-08-14 14:38:51 +10:00
Deon George
8f8b50d630 Some logging and return 404 when not ready 2020-08-12 23:18:59 +10:00
7 changed files with 124 additions and 88 deletions

View File

@ -26,7 +26,7 @@ You'll then need to configure the following:
DB_CONNECTION=sqlite # Your databsae configuration (must the same as SQRL_DATABASE below) DB_CONNECTION=sqlite # Your databsae configuration (must the same as SQRL_DATABASE below)
... ...
SQRL_DATABASE=sqlite # Points to the SQRL database connection SQRL_DATABASE=sqlite # Points to the SQRL database connection
SQRL_URL_LOGIN=https://site/sqrl/login # URL to your login page (not used with LUMEN SQRL_URL_LOGIN=https://site/sqrl/login # URL to the page after successful authentication
SQRL_KEY_DOMAIN=site # URL to yours SQRL Server without http:// and https:// SQRL_API_ROUTE=/index.php/api/sqrl # Route to SQRL Server API SQRL_KEY_DOMAIN=site # URL to yours SQRL Server without http:// and https:// SQRL_API_ROUTE=/index.php/api/sqrl # Route to SQRL Server API
SQRL_NONCE_MAX_AGE_MINUTES=5 # Max age in minutes of the valid nonce SQRL_NONCE_MAX_AGE_MINUTES=5 # Max age in minutes of the valid nonce
SQRL_NONCE_SALT=RANDOM # Generate a random salt value to calculate the nonce SQRL_NONCE_SALT=RANDOM # Generate a random salt value to calculate the nonce

View File

@ -51,7 +51,7 @@ class SQRL
* *
* @return array * @return array
*/ */
public static function authNonce(): array public static function authNonce(int $size=100): array
{ {
$url = config('sqrl.url'); $url = config('sqrl.url');
@ -67,7 +67,7 @@ class SQRL
'check_state_on'=>$route, 'check_state_on'=>$route,
'sqrl_url'=>$sqrl_url, 'sqrl_url'=>$sqrl_url,
'sqrl_url_encoded'=>self::base64_encode_url(sprintf('%s&can=%s',$sqrl_url,$o->can)), 'sqrl_url_encoded'=>self::base64_encode_url(sprintf('%s&can=%s',$sqrl_url,$o->can)),
'qrcode'=>SQRL::qrCode($sqrl_url,100), 'qrcode'=>SQRL::qrCode($sqrl_url,$size),
]; ];
} }

View File

@ -52,6 +52,7 @@ class Nonce
$o->can = SQRL::base64_encode_url($can); $o->can = SQRL::base64_encode_url($can);
$o->save(); $o->save();
Log::debug(sprintf('NUT [%s] created for (%s)',$o->nonce,$o->ip));
return $o; return $o;
} }
@ -70,7 +71,6 @@ class Nonce
return NULL; return NULL;
} }
Log::debug(sprintf('A: %s, B: %s',$o->created_at->diffInMinutes(Carbon::now()),config('sqrl.nonce_age')));
// Delete the old nonce // Delete the old nonce
if ($o->created_at->diffInMinutes(Carbon::now()) > config('sqrl.nonce_age')) { if ($o->created_at->diffInMinutes(Carbon::now()) > config('sqrl.nonce_age')) {
$o->delete(); $o->delete();

View File

@ -18,8 +18,8 @@ use Leenooks\SQRL\SQRL as SQRLAuth;
* Class SQRLController * Class SQRLController
* @package Leenooks\SQRL * @package Leenooks\SQRL
* @todo CHECK THAT WE ARE RECEIVING BACK WHAT WE GIVE TO THE CLIENT * @todo CHECK THAT WE ARE RECEIVING BACK WHAT WE GIVE TO THE CLIENT
* * QUERY: = sqrl url, eg: sqrl://domain/api/sqrl?nut=84cc3ef3b58b01dbe22931d1ceabdd6be2c27a481516755757c53b0162287bb8 * + QUERY: = sqrl url, eg: sqrl://domain/api/sqrl?nut=84cc3ef3b58b01dbe22931d1ceabdd6be2c27a481516755757c53b0162287bb8
* * IDENT: = RESPONSE TO QUERY * + IDENT: = RESPONSE TO QUERY
* @todo JOB TO DELETE OLD NONCES * @todo JOB TO DELETE OLD NONCES
*/ */
@ -35,7 +35,29 @@ class SQRLController extends Controller
{ {
// Validate the nonce if it has been given. // Validate the nonce if it has been given.
if ($request->get('nut')) { if ($request->get('nut')) {
Log::debug(sprintf('Got a NUT [%s]',$request->get('nut')));
//Get the user by the original nonce
$o = SQRLAuth\Nonce::check($request->get('nut'),'orig_nonce');
Log::debug(sprintf('User [%s]',serialize($o ? $o->getAttributes() : NULL)));
if ($o && $o->verified) {
if ($o->pubkey && ! $o->pubkey->disabled)
// For JSON we just need the SQRL login
return $request->expectsJson() ? $o->pubkey->public_key : $o->pubkey;
else
return response()->json([
'isReady'=>FALSE,
'msg'=>'Not Verified'
],404);
} else {
return response()->json([
'isReady'=>FALSE,
'msg'=>'Not Ready'
],404);
}
} }
// If this laravel, check if the user has been logged in // If this laravel, check if the user has been logged in
@ -79,13 +101,13 @@ class SQRLController extends Controller
$decode_request = SQRL::decodeData($validatedData); $decode_request = SQRL::decodeData($validatedData);
$sqrl_nonce = SQRLAuth\Nonce::checkNonceValid($validatedData['nut']); $sqrl_nonce = SQRLAuth\Nonce::checkNonceValid($validatedData['nut']);
$tif = in_array('noiptest',Arr::get($decode_request,'client.opt')) ? 0 : SQRL::tifcode('IP_MATCH'); $tif = in_array('noiptest',Arr::get($decode_request,'client.opt',[])) ? 0 : SQRL::tifcode('IP_MATCH');
if (! $sqrl_nonce) { if (! $sqrl_nonce) {
Log::error('API:Nonce not valid',['n'=>$validatedData['nut'],'tif'=>SQRL::tifcode('CLIENT_FAILURE')]); Log::error('API:Nonce not valid',['n'=>$validatedData['nut'],'tif'=>SQRL::tifcode('CLIENT_FAILURE')]);
$response = SQRLAuth\Response::problem($validatedData['nut'],SQRL::tifcode('CLIENT_FAILURE')); $response = SQRLAuth\Response::problem($validatedData['nut'],SQRL::tifcode('CLIENT_FAILURE'));
} elseif (($sqrl_nonce->ip !== $request->ip()) && (! in_array('noiptest',Arr::get($decode_request,'client.opt')))) { } elseif (($sqrl_nonce->ip !== $request->ip()) && (! in_array('noiptest',Arr::get($decode_request,'client.opt',[])))) {
Log::error('API::IP Doesnt Match',['n'=>$validatedData['nut'],'tif'=>SQRL::tifcode('COMMAND_FAILED')]); Log::error('API::IP Doesnt Match',['n'=>$validatedData['nut'],'tif'=>SQRL::tifcode('COMMAND_FAILED')]);
$response = SQRLAuth\Response::problem($sqrl_nonce->nonce,SQRL::tifcode('COMMAND_FAILED')); $response = SQRLAuth\Response::problem($sqrl_nonce->nonce,SQRL::tifcode('COMMAND_FAILED'));
@ -100,11 +122,12 @@ class SQRLController extends Controller
} else { } else {
foreach (['ver','cmd'] as $y) foreach (['ver','cmd'] as $y)
Log::debug(sprintf('API-client-%s [%s]',str_pad($y,5,' '),Arr::get($decode_request,'client.'.$y))); Log::debug(sprintf('API-client-%s [%s]',str_pad($y,5,' '),Arr::get($decode_request,'client.'.$y)));
Log::debug(sprintf('API-client-opt [%s]',join('|',Arr::get($decode_request,'client.opt'))));
Log::debug(sprintf('API-client-idk [%s]',base64_encode(Arr::get($decode_request,'client.idk'))));
Log::debug(sprintf('API-server [%s]',serialize(Arr::get($decode_request,'server'))));
Log::debug(sprintf('API-type [%s]',$sqrl_nonce->type)); Log::debug(sprintf('API-client-opt [%s]',join('|',Arr::get($decode_request,'client.opt',[]))));
Log::debug(sprintf('API-client-idk [%s]',base64_encode(Arr::get($decode_request,'client.idk'))));
Log::debug(sprintf('API-server [%s]',serialize(Arr::get($decode_request,'server'))));
Log::debug(sprintf('API-type [%s]',$sqrl_nonce->type));
switch ($sqrl_nonce->type) { switch ($sqrl_nonce->type) {
case 'auth': case 'auth':
@ -138,6 +161,8 @@ class SQRLController extends Controller
// If the nonce is old or doesnt exist. // If the nonce is old or doesnt exist.
if (! $o) { if (! $o) {
Log::debug(sprintf('isReady: Invalid Nonce [%s]',$request->get('nut')));
return response()->json([ return response()->json([
'isReady'=>FALSE, 'isReady'=>FALSE,
'msg'=>'Invalid Nonce, or Nonce expired' 'msg'=>'Invalid Nonce, or Nonce expired'
@ -146,6 +171,8 @@ class SQRLController extends Controller
// Validate the IP matches - since the request would come from the same device client // Validate the IP matches - since the request would come from the same device client
if ($o->ip !== $request->ip()) { if ($o->ip !== $request->ip()) {
Log::debug(sprintf('isReady: IP Mismatch [%s] != [%s]',$o->ip,$request->ip));
return response()->json([ return response()->json([
'isReady'=>FALSE, 'isReady'=>FALSE,
'msg' => 'IP Mismatch', 'msg' => 'IP Mismatch',
@ -154,13 +181,17 @@ class SQRLController extends Controller
// Has the nonce be validated // Has the nonce be validated
if ($o->verified != 1) { if ($o->verified != 1) {
Log::debug(sprintf('isReady: Not Verified [%s]',$o->verified));
return response()->json([ return response()->json([
'isReady'=>FALSE, 'isReady'=>FALSE,
'msg'=>'Not Ready' 'msg'=>'Not Ready'
],200); ],404);
} }
if ($o->pubkey && $o->pubkey->disabled) { if ($o->pubkey && $o->pubkey->disabled) {
Log::debug(sprintf('isReady: SQRL Disabled [%s]',$o->pubkey));
return response()->json([ return response()->json([
'isReady'=>FALSE, 'isReady'=>FALSE,
'msg'=>'SQRL disabled for user' 'msg'=>'SQRL disabled for user'
@ -169,6 +200,8 @@ class SQRLController extends Controller
switch ($o->type) { switch ($o->type) {
case 'auth': case 'auth':
Log::debug(sprintf('isReady: Authenticated [%s]',$o->pubkey));
return response()->json([ return response()->json([
'isReady'=>TRUE, 'isReady'=>TRUE,
'msg'=>'SQRL authenticated', 'msg'=>'SQRL authenticated',
@ -180,10 +213,12 @@ class SQRLController extends Controller
} }
} else { } else {
Log::debug(sprintf('isReady: Not Nut?',$request->get('nut')));
return response()->json([ return response()->json([
'isReady'=>FALSE, 'isReady'=>FALSE,
'msg'=>'Not Found!' 'msg'=>'Not Found!'
],404); ],200);
} }
} }
} }

View File

@ -8,7 +8,7 @@ if (app() instanceof \Illuminate\Foundation\Application) {
Route::get('/login','SQRLController@auth')->name('login'); Route::get('/login','SQRLController@auth')->name('login');
// Perform login // Perform login
Route::post('/login','SQRLController@@login'); Route::post('/login','SQRLController@login');
}); });
Route::group(['prefix'=>'api','namespace'=>'Leenooks\SQRL'], function() { Route::group(['prefix'=>'api','namespace'=>'Leenooks\SQRL'], function() {

View File

@ -78,78 +78,7 @@
</div> </div>
</body> </body>
<script> @include('sqrl::login_js')
var syncQuery = window.XMLHttpRequest ? new window.XMLHttpRequest() : new ActiveXObject('MSXML2.XMLHTTP.3.0');
var encodedSqrlUrl = false, sqrlScheme = true;
var gifProbe = new Image(); // create an instance of a memory-based probe image
var localhostRoot = 'http://localhost:25519/'; // the SQRL client listener
const poll = 500;
// define our load-success function
gifProbe.onload = function() {
sqrlScheme = false; // prevent retriggering of the SQRL QR code.
document.location.href = localhostRoot+encodedSqrlUrl;
};
// define our load-failure function
gifProbe.onerror = function() {
setTimeout(function() {
gifProbe.src = localhostRoot+Date.now()+'.gif';
},250);
};
// Poll to see if authentication has proceeded
function pollForNextPage() {
if (document.hidden) { // before probing for any page change, we check to
setTimeout(pollForNextPage,poll); // see whether the page is visible. If the user is
return; // not viewing the page, check again in 5 seconds.
}
syncQuery.open('GET','{{ $check_state_on }}'); // the page is visible, so let's check for any update
syncQuery.onreadystatechange = function() {
if (syncQuery.readyState === 4 ) {
if (syncQuery.status === 200 ) {
var response = JSON.parse(syncQuery.response);
if (response.isReady) {
document.location.href = response.nextPage;
} else {
if (response.msg === "Invalid Nonce, or Nonce expired"
|| response.msg === "IP Mismatch"
|| response.msg === "SQRL disabled for user")
{
var div = document.getElementById('error_message');
div.innerHTML = "<string>"+response.msg+"</strong><br><small>QR Code needs to be refresh - reload the page.<small>";
div.removeAttribute("hidden");
} else {
setTimeout(pollForNextPage,poll);
}
}
} else {
setTimeout(pollForNextPage,poll);
}
}
};
// Send our request to check authenticated status
syncQuery.send();
}
// if we have an encoded URL to jump to, initiate our GIF probing before jumping
function sqrlLinkClick(e) {
encodedSqrlUrl = e.getAttribute('encoded-sqrl-url');
if (encodedSqrlUrl) {
gifProbe.onerror();
}
}
pollForNextPage();
</script>
<script src="https://code.jquery.com/jquery-3.4.1.slim.min.js" integrity="sha384-J6qa4849blE2+poT4WnyKhv5vZF5SrPo0iEjwBvKU7imGFAV0wwj1yYfoRSJoZ+n" crossorigin="anonymous"></script> <script src="https://code.jquery.com/jquery-3.4.1.slim.min.js" integrity="sha384-J6qa4849blE2+poT4WnyKhv5vZF5SrPo0iEjwBvKU7imGFAV0wwj1yYfoRSJoZ+n" crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/npm/popper.js@1.16.0/dist/umd/popper.min.js" integrity="sha384-Q6E9RHvbIyZFJoft+2mJbHaEWldlvI9IOYy5n3zV9zzTtmI3UksdQRVvoxMfooAo" crossorigin="anonymous"></script> <script src="https://cdn.jsdelivr.net/npm/popper.js@1.16.0/dist/umd/popper.min.js" integrity="sha384-Q6E9RHvbIyZFJoft+2mJbHaEWldlvI9IOYy5n3zV9zzTtmI3UksdQRVvoxMfooAo" crossorigin="anonymous"></script>

View File

@ -0,0 +1,72 @@
<script>
var syncQuery = window.XMLHttpRequest ? new window.XMLHttpRequest() : new ActiveXObject('MSXML2.XMLHTTP.3.0');
var encodedSqrlUrl = false, sqrlScheme = true;
var gifProbe = new Image(); // create an instance of a memory-based probe image
var localhostRoot = 'http://localhost:25519/'; // the SQRL client listener
const poll = 500;
// define our load-success function
gifProbe.onload = function() {
sqrlScheme = false; // prevent retriggering of the SQRL QR code.
document.location.href = localhostRoot+encodedSqrlUrl;
};
// define our load-failure function
gifProbe.onerror = function() {
setTimeout(function() {
gifProbe.src = localhostRoot+Date.now()+'.gif';
},250);
};
// Poll to see if authentication has proceeded
function pollForNextPage() {
if (document.hidden) { // before probing for any page change, we check to
setTimeout(pollForNextPage,poll); // see whether the page is visible. If the user is
return; // not viewing the page, check again in 5 seconds.
}
syncQuery.open('GET','{{ $check_state_on }}'); // the page is visible, so let's check for any update
syncQuery.onreadystatechange = function() {
if (syncQuery.readyState === 4 ) {
if (syncQuery.status === 200 ) {
var response = JSON.parse(syncQuery.response);
if (response.isReady) {
document.location.href = response.nextPage;
} else {
if (response.msg === "Invalid Nonce, or Nonce expired"
|| response.msg === "IP Mismatch"
|| response.msg === "SQRL disabled for user")
{
var div = document.getElementById('error_message');
div.innerHTML = "<string>"+response.msg+"</strong><br><small>QR Code needs to be refresh - reload the page.<small>";
div.removeAttribute("hidden");
} else {
setTimeout(pollForNextPage,poll);
}
}
} else {
setTimeout(pollForNextPage,poll);
}
}
};
// Send our request to check authenticated status
syncQuery.send();
}
// if we have an encoded URL to jump to, initiate our GIF probing before jumping
function sqrlLinkClick(e) {
encodedSqrlUrl = e.getAttribute('encoded-sqrl-url');
if (encodedSqrlUrl) {
gifProbe.onerror();
}
}
pollForNextPage();
</script>