Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ dist/

*.log

package-lock.json
.bundle*
.DS_Store
.idea
156 changes: 141 additions & 15 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,22 +20,143 @@ Basically it's a rewrite of [ChilliLibrary.js](http://dev.coova.org/svn/coova-ch
`npm install chilli-pepper`, then:

```js
var Pepper = require('chilli-pepper');

var pepper = Pepper({
host: '192.168.1.1',
port: 3990
});

pepper.logon('john', 'd0E', function(err, data) {

if (data.clientState === 1) {
// User is now logged in
}

// ...
<script type="text/javascript">

var pepper = Pepper({
host: 'app.paywifigo.me',
port: 4990,
interval: 5000,
ssl: true,
// host the below php file in a server.
// uamservice: 'chilli-uamservice-pap-chap.php', // local server
uamservice: 'https://app.paywifigo.me/chilli-uamservice-pap-chap.php' // cloud server
});
// enable debug
localStorage.debug = '*';

// copy and paste within browser developer console to play around with


pepper.logon('testdev', 'testdev', {protocol: 'CHAP'}, function (err, data) {
if (err) {
console.log(err);
} else {
if (data.clientState === 1) {
console.log("hooray you are logged in");
console.log(pepper.data.userurl);
} else {
console.log(data);
}
}
});
</script>
```

});
### Advanced Example using sweet alert library
```js
let loginUsername = "john";
let loginPassword = "supersecret";
pepper.logon(loginUsername, loginPassword, {protocol: 'CHAP'}, function (err, result) {
// debugger
if (err) {
if (err.message.includes('Cannot find a challenge')) {
Swal.fire({
imageUrl: "loading2.gif",
showConfirmButton: false,
allowOutsideClick: false,
title: 'Logging In with credentials: ',
text: `username: ${loginUsername} & password: ${loginPassword} `
});
window.location.href = "http://10.1.0.1:3990/logoff";
}
else if (err.message && err.message.includes('uamservice response is invalid (missing "chap" field)') || err.message && err.message.includes('Timeout')) {
// debugger
console.error("You must be connected to our prepaid hotspot locations. Please contact support or try again by refreshing the page.");
swal.close();
// const url = new URL(window.location);
// url.search = ""; // clear query string
// window.history.replaceState({}, document.title, url.toString());
// window.location.reload()
alert("You must be connected to our prepaid hotspot locations. Please contact support or try again by refreshing the page.");
return "You must be connected to our prepaid hotspot locations. Please contact support or try again by refreshing the page.";
} else {
const url = new URL(window.location);
url.search = ""; // clear query string
window.history.replaceState({}, document.title, url.toString());
window.location.reload()
console.error('Unhandled error:', err.message);
swal.close();
return err.message;
}
// console.error('Login error:', err);
// return err.message;
}
else if (result.clientState === 1) {
// add a subroutine that confirms the client State before redirecting in order to reduce false positives.
Swal.fire({
imageUrl: "loading2.gif",
showConfirmButton: false,
allowOutsideClick: false,
title: 'Successfully Logged IN',
text: `username: ${loginUsername} & password: ${loginPassword} `
});
console.log('Hooray! You are logged in');
console.log('User URL:', result.redir.originalURL);
// alert(data.message + 'Internet is Active . Redirecting you to Google.com');
// Stop polling once logged in

swal.close();
if (result.redir.originalURL.includes('.1:3990/logoff')) {
window.location.href = "http://google.com";
} else if (result.redir.originalURL.includes('.1:3990')) {
window.location.href = "http://bing.com";
} else if (result.redir.originalURL.includes('captive.apple')) {
window.location.href = "http://google.com";
} else if (result.redir.originalURL.includes('connecttest.com')) {
window.location.href = "http://google.com";
} else if (result.redir.originalURL.includes('connectivitycheck.gstatic.com')) {
window.location.href = "http://yahoo.com";
} else if (result.redir.originalURL.includes('msftncsi')) {
window.location.href = "http://yahoo.com";
} else if (result.redir.originalURL.includes('generate_204')) {
window.location.href = "http://yahoo.com";
} else if (result.redir.originalURL.includes('airport.us')) {
window.location.href = "http://yahoo.com";
} else {
window.location.href = result.redir.originalURL;
}

return "Hooray! You are logged in";
}
else {
// debugger

console.log('Login response:', result);
swal.close();
let timerIntervalerror;
Swal.fire({
title: "Activating Account Failed",
text: result.message,
timer: 6000,
timerProgressBar: true,
didOpen: () => {
Swal.showLoading();
},
willClose: () => {
document.getElementById('loginPassword').value = "";
clearInterval(timerIntervalerror);
}
}).then((result) => {
/* Read more about handling dismissals below */
if (result.dismiss === Swal.DismissReason.timer) {
document.getElementById('loginPassword').value = "";
console.log("No Active Subscription");
}
});

return result.message;
}
});
```

### Global
Expand Down Expand Up @@ -185,6 +306,11 @@ Then go to `http://localhost:4000`
- write more tests
- add test coverage info

## BUG Fixes & UPDATES
- Computation of chap password locally is *NOT* supported because it requires the *RADSECRET* to Encrypt the resulting CHAP Password ("NEED to STORE RADSECRET within the PAGE ***SECURITY RISK***)
- CHAP password and PAP password are computed by the PHP script are returned in json format.
- UAM Service **MUST** be provided in order for the coovachilli authentication to be successful


## License

Expand Down
46 changes: 46 additions & 0 deletions chilli-uamservice-pap-chap.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
<?php

/*
* Sample api call from chillipepper
* https://app.paywifigo.me/chilli-uamservice-pap-chap.php?null?timeout=5000&prefix=__Pepper&callback=__Pepper
*/

# Shared secret used to encrypt challenge with. Prevents dictionary attacks.
# You should change this to your own shared secret.
$uamsecret = "radpass";
//$password = 'testdev';
/*
* set HS_UAMSECRET=chilliradiusSecret = In /etc/chilli/defaults or /etc/chilli/config
* UAM SERVICE should reply with a JSON response containing
* - CHAP logon : CHAP-Password X0Red with UAM SECRET
* - PAP logon : Password XORed with UAM SECRET
*/


if ((isset($_GET['password']) && isset($_GET['challenge'])) || (isset($_POST['password']) && isset($_POST['challenge']))) {
$challenge = $_GET['challenge'] ?? $_POST['challenge'];
$password = $_GET['password'] ?? $_POST['password'];

$hexchal = pack("H32", $challenge);

//CHAP-Password & PAP-Password X0Red with UAM SECRET
$newchal = pack("H*", md5($hexchal . $uamsecret));

// CHAP-Password - Generated from XORED packed Chilli-Challenge
$response = md5("\0" . $password . $newchal);


//- PAP logon : Password XORed with UAM SECRET
$newpwd = pack("a32", $password);
$pappassword = implode("", unpack("H32", ($newpwd ^ $newchal)));
header('application/json');
$array = ['pap' => $pappassword, 'chap' => $response];
$json_data = json_encode($array);
if (isset($_GET['callback'])) {
$callback = $_GET['callback'];
echo "$callback($json_data)";
} else {
echo $json_data;
}

}
74 changes: 74 additions & 0 deletions coova.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
<!--copy this file to /etc/chilli/www-->
<!--feature: saves the userurl to a cookie-->
<html>
<head>
<title>Coova</title>
<!-- redirecting... either by meta tag or javascript -->
<script type="text/javascript" language="Javascript">
//<!--
function getURLParam(n) {
var href = window.location.href;
var idx = href.indexOf("?");
if (idx > -1) {
var qs = href.substr(idx + 1);
var params = new URLSearchParams(qs);
if (params.has(n)) {
return decodeURIComponent(params.get(n));
}
}
return null;
}

var loginUrl;
function redirect() {
if (loginUrl) {
window.location = loginUrl;
} else {
alert("Login URL not found!");
}
return false;
}

window.onload = function() {
loginUrl = getURLParam("loginurl");
if (loginUrl) {
// Dynamically set meta refresh URL to loginurl
document.querySelector("meta[http-equiv='refresh']").setAttribute("content", `7; URL=${loginUrl}`);
setTimeout(redirect, 5000);
}
}
//-->
</script>
<meta http-equiv="refresh" content="7; URL=/prelogin"> <!-- fallback -->
</head>
<body style="margin: 0pt auto; height:100%;">
<div style="width:100%;height:80%;position:fixed;display:table;">
<p style="display: table-cell; line-height: 2.5em;
vertical-align:middle;text-align:center;color:grey;">
<a href="#" onclick="javascript:return redirect();">
<img src="coova.jpg" alt="Coova Logo" border="0" height="39" width="123"/>
</a><br>
<small><img src="wait.gif" alt="Loading"/> redirecting...</small>
</p>
<br><br>
</div>
<script>
// Function to get a cookie by name
function getCookie(name) {
const match = document.cookie.match(new RegExp('(^| )' + name + '=([^;]+)'));
return match ? match[2] : null;
}

// Function to set a cookie with expiration
function setCookie(name, value, days) {
const expires = new Date();
expires.setTime(expires.getTime() + (days * 24 * 60 * 60 * 1000));
document.cookie = name + "=" + value + ";expires=" + expires.toUTCString() + ";path=/";
}
setCookie("chilli", data.country, 20); // Store country for 20 days
const storedCountry = getCookie("chilli");
</script>
</body>
</html>

Empty file added copy
Empty file.
Loading