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)
|
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
|
||||||
|
@ -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),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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();
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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() {
|
||||||
|
@ -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>
|
||||||
|
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