Compare commits
5 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
a5e6caf8fe | ||
|
af1f125866 | ||
|
0c91860454 | ||
|
bdff9462d3 | ||
|
8f8b50d630 |
@ -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)
|
||||
...
|
||||
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_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
|
||||
|
@ -51,7 +51,7 @@ class SQRL
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function authNonce(): array
|
||||
public static function authNonce(int $size=100): array
|
||||
{
|
||||
$url = config('sqrl.url');
|
||||
|
||||
@ -67,7 +67,7 @@ class SQRL
|
||||
'check_state_on'=>$route,
|
||||
'sqrl_url'=>$sqrl_url,
|
||||
'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),
|
||||
];
|
||||
}
|
||||
|
||||
|
@ -52,6 +52,7 @@ class Nonce
|
||||
$o->can = SQRL::base64_encode_url($can);
|
||||
$o->save();
|
||||
|
||||
Log::debug(sprintf('NUT [%s] created for (%s)',$o->nonce,$o->ip));
|
||||
return $o;
|
||||
}
|
||||
|
||||
@ -70,7 +71,6 @@ class Nonce
|
||||
return NULL;
|
||||
}
|
||||
|
||||
Log::debug(sprintf('A: %s, B: %s',$o->created_at->diffInMinutes(Carbon::now()),config('sqrl.nonce_age')));
|
||||
// Delete the old nonce
|
||||
if ($o->created_at->diffInMinutes(Carbon::now()) > config('sqrl.nonce_age')) {
|
||||
$o->delete();
|
||||
|
@ -18,8 +18,8 @@ use Leenooks\SQRL\SQRL as SQRLAuth;
|
||||
* Class SQRLController
|
||||
* @package Leenooks\SQRL
|
||||
* @todo CHECK THAT WE ARE RECEIVING BACK WHAT WE GIVE TO THE CLIENT
|
||||
* * QUERY: = sqrl url, eg: sqrl://domain/api/sqrl?nut=84cc3ef3b58b01dbe22931d1ceabdd6be2c27a481516755757c53b0162287bb8
|
||||
* * IDENT: = RESPONSE TO QUERY
|
||||
* + QUERY: = sqrl url, eg: sqrl://domain/api/sqrl?nut=84cc3ef3b58b01dbe22931d1ceabdd6be2c27a481516755757c53b0162287bb8
|
||||
* + IDENT: = RESPONSE TO QUERY
|
||||
* @todo JOB TO DELETE OLD NONCES
|
||||
*/
|
||||
|
||||
@ -35,7 +35,29 @@ class SQRLController extends Controller
|
||||
{
|
||||
// Validate the nonce if it has been given.
|
||||
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
|
||||
@ -79,13 +101,13 @@ class SQRLController extends Controller
|
||||
|
||||
$decode_request = SQRL::decodeData($validatedData);
|
||||
$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) {
|
||||
Log::error('API:Nonce not valid',['n'=>$validatedData['nut'],'tif'=>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')]);
|
||||
$response = SQRLAuth\Response::problem($sqrl_nonce->nonce,SQRL::tifcode('COMMAND_FAILED'));
|
||||
|
||||
@ -100,11 +122,12 @@ class SQRLController extends Controller
|
||||
} else {
|
||||
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-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) {
|
||||
case 'auth':
|
||||
@ -138,6 +161,8 @@ class SQRLController extends Controller
|
||||
|
||||
// If the nonce is old or doesnt exist.
|
||||
if (! $o) {
|
||||
Log::debug(sprintf('isReady: Invalid Nonce [%s]',$request->get('nut')));
|
||||
|
||||
return response()->json([
|
||||
'isReady'=>FALSE,
|
||||
'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
|
||||
if ($o->ip !== $request->ip()) {
|
||||
Log::debug(sprintf('isReady: IP Mismatch [%s] != [%s]',$o->ip,$request->ip));
|
||||
|
||||
return response()->json([
|
||||
'isReady'=>FALSE,
|
||||
'msg' => 'IP Mismatch',
|
||||
@ -154,13 +181,17 @@ class SQRLController extends Controller
|
||||
|
||||
// Has the nonce be validated
|
||||
if ($o->verified != 1) {
|
||||
Log::debug(sprintf('isReady: Not Verified [%s]',$o->verified));
|
||||
|
||||
return response()->json([
|
||||
'isReady'=>FALSE,
|
||||
'msg'=>'Not Ready'
|
||||
],200);
|
||||
],404);
|
||||
}
|
||||
|
||||
if ($o->pubkey && $o->pubkey->disabled) {
|
||||
Log::debug(sprintf('isReady: SQRL Disabled [%s]',$o->pubkey));
|
||||
|
||||
return response()->json([
|
||||
'isReady'=>FALSE,
|
||||
'msg'=>'SQRL disabled for user'
|
||||
@ -169,6 +200,8 @@ class SQRLController extends Controller
|
||||
|
||||
switch ($o->type) {
|
||||
case 'auth':
|
||||
Log::debug(sprintf('isReady: Authenticated [%s]',$o->pubkey));
|
||||
|
||||
return response()->json([
|
||||
'isReady'=>TRUE,
|
||||
'msg'=>'SQRL authenticated',
|
||||
@ -180,10 +213,12 @@ class SQRLController extends Controller
|
||||
}
|
||||
|
||||
} else {
|
||||
Log::debug(sprintf('isReady: Not Nut?',$request->get('nut')));
|
||||
|
||||
return response()->json([
|
||||
'isReady'=>FALSE,
|
||||
'msg'=>'Not Found!'
|
||||
],404);
|
||||
],200);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -8,7 +8,7 @@ if (app() instanceof \Illuminate\Foundation\Application) {
|
||||
Route::get('/login','SQRLController@auth')->name('login');
|
||||
|
||||
// Perform login
|
||||
Route::post('/login','SQRLController@@login');
|
||||
Route::post('/login','SQRLController@login');
|
||||
});
|
||||
|
||||
Route::group(['prefix'=>'api','namespace'=>'Leenooks\SQRL'], function() {
|
||||
|
@ -78,78 +78,7 @@
|
||||
</div>
|
||||
</body>
|
||||
|
||||
<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>
|
||||
@include('sqrl::login_js')
|
||||
|
||||
<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>
|
||||
|
72
src/views/login_js.blade.php
Normal file
72
src/views/login_js.blade.php
Normal 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>
|
Loading…
x
Reference in New Issue
Block a user