store auth token in OS keyring, not in local storage
This commit is contained in:
parent
d4e572a2b6
commit
4376c34678
9 changed files with 142 additions and 135 deletions
|
@ -13,6 +13,10 @@ cd ..
|
|||
# build ui
|
||||
cd ui
|
||||
npm install
|
||||
|
||||
# necessary to ensure native Node modules (e.g. keytar) are built against the correct version of Node)
|
||||
node_modules\.bin\electron-rebuild
|
||||
|
||||
node extractLocals.js
|
||||
node_modules\.bin\node-sass --output dist\css --sourcemap=none scss\
|
||||
node_modules\.bin\webpack
|
||||
|
|
|
@ -45,6 +45,13 @@ if [ "$FULL_BUILD" == "true" ]; then
|
|||
python "$BUILD_DIR/set_version.py"
|
||||
fi
|
||||
|
||||
libsecret="libsecret-1-dev"
|
||||
if [ $LINUX -a -z "$(dpkg-query --show --showformat='${Status}\n' "$libsecret" 2>/dev/null | grep "install ok installed")" ]; then
|
||||
# this is needed for keytar, which does secure password/token management
|
||||
sudo apt-get install "$libsecret"
|
||||
fi
|
||||
|
||||
|
||||
[ -d "$ROOT/dist" ] && rm -rf "$ROOT/dist"
|
||||
mkdir -p "$ROOT/dist"
|
||||
[ -d "$ROOT/app/dist" ] && rm -rf "$ROOT/app/dist"
|
||||
|
@ -61,6 +68,11 @@ npm install
|
|||
(
|
||||
cd "$ROOT/ui"
|
||||
npm install
|
||||
|
||||
# necessary to ensure native Node modules (e.g. keytar) are built against the correct version of Node)
|
||||
# DEBUG=electron-rebuild node_modules/.bin/electron-rebuild .
|
||||
node_modules/.bin/electron-rebuild "$ROOT/ui"
|
||||
|
||||
node extractLocals.js
|
||||
node_modules/.bin/node-sass --output dist/css --sourcemap=none scss/
|
||||
node_modules/.bin/webpack
|
||||
|
|
|
@ -33,20 +33,20 @@ export function doUserFetch() {
|
|||
dispatch({
|
||||
type: types.USER_FETCH_STARTED,
|
||||
});
|
||||
lbryio.setCurrentUser(
|
||||
user => {
|
||||
lbryio
|
||||
.getCurrentUser()
|
||||
.then(user => {
|
||||
dispatch({
|
||||
type: types.USER_FETCH_SUCCESS,
|
||||
data: { user },
|
||||
});
|
||||
},
|
||||
error => {
|
||||
})
|
||||
.catch(error => {
|
||||
dispatch({
|
||||
type: types.USER_FETCH_FAILURE,
|
||||
data: { error },
|
||||
});
|
||||
}
|
||||
);
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -56,32 +56,27 @@ export function doUserEmailNew(email) {
|
|||
type: types.USER_EMAIL_NEW_STARTED,
|
||||
email: email,
|
||||
});
|
||||
lbryio.call("user_email", "new", { email }, "post").then(
|
||||
() => {
|
||||
lbryio
|
||||
.call("user_email", "new", { email: email, send_verification_email: true }, "post")
|
||||
.catch(error => {
|
||||
if (error.xhr && error.xhr.status == 409) {
|
||||
return lbryio.call("user_email", "resend_token", { email: email, only_if_expired: true }, "post");
|
||||
}
|
||||
throw error
|
||||
})
|
||||
.then(() => {
|
||||
dispatch({
|
||||
type: types.USER_EMAIL_NEW_SUCCESS,
|
||||
data: { email },
|
||||
});
|
||||
dispatch(doUserFetch());
|
||||
},
|
||||
error => {
|
||||
if (
|
||||
error.xhr &&
|
||||
(error.xhr.status == 409 ||
|
||||
error.message == "This email is already in use")
|
||||
) {
|
||||
dispatch({
|
||||
type: types.USER_EMAIL_NEW_EXISTS,
|
||||
data: { email },
|
||||
});
|
||||
} else {
|
||||
dispatch({
|
||||
type: types.USER_EMAIL_NEW_FAILURE,
|
||||
data: { error: error.message },
|
||||
});
|
||||
}
|
||||
}
|
||||
);
|
||||
})
|
||||
.catch(error => {
|
||||
dispatch({
|
||||
type: types.USER_EMAIL_NEW_FAILURE,
|
||||
data: { error: error.message },
|
||||
});
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -111,12 +106,7 @@ export function doUserEmailVerify(verificationToken) {
|
|||
};
|
||||
|
||||
lbryio
|
||||
.call(
|
||||
"user_email",
|
||||
"confirm",
|
||||
{ verification_token: verificationToken, email: email },
|
||||
"post"
|
||||
)
|
||||
.call("user_email", "confirm", { verification_token: verificationToken, email: email }, "post")
|
||||
.then(userEmail => {
|
||||
if (userEmail.is_verified) {
|
||||
dispatch({
|
||||
|
|
165
ui/js/lbryio.js
165
ui/js/lbryio.js
|
@ -1,26 +1,23 @@
|
|||
import { getSession, setSession, setLocal } from "./utils.js";
|
||||
import lbry from "./lbry.js";
|
||||
|
||||
const querystring = require("querystring");
|
||||
const keytar = require("keytar");
|
||||
|
||||
const lbryio = {
|
||||
_accessToken: getSession("accessToken"),
|
||||
_authenticationPromise: null,
|
||||
enabled: true,
|
||||
_authenticationPromise: null,
|
||||
_exchangePromise: null,
|
||||
_exchangeLastFetched: null,
|
||||
};
|
||||
|
||||
const CONNECTION_STRING = process.env.LBRY_APP_API_URL
|
||||
? process.env.LBRY_APP_API_URL.replace(/\/*$/, "/") // exactly one slash at the end
|
||||
: "https://api.lbry.io/";
|
||||
|
||||
const EXCHANGE_RATE_TIMEOUT = 20 * 60 * 1000;
|
||||
|
||||
lbryio._exchangePromise = null;
|
||||
lbryio._exchangeLastFetched = null;
|
||||
lbryio.getExchangeRates = function() {
|
||||
if (
|
||||
!lbryio._exchangeLastFetched ||
|
||||
Date.now() - lbryio._exchangeLastFetched > EXCHANGE_RATE_TIMEOUT
|
||||
) {
|
||||
if (!lbryio._exchangeLastFetched || Date.now() - lbryio._exchangeLastFetched > EXCHANGE_RATE_TIMEOUT) {
|
||||
lbryio._exchangePromise = new Promise((resolve, reject) => {
|
||||
lbryio
|
||||
.call("lbc", "exchange_rate", {}, "get", true)
|
||||
|
@ -46,9 +43,7 @@ lbryio.call = function(resource, action, params = {}, method = "get") {
|
|||
const xhr = new XMLHttpRequest();
|
||||
|
||||
xhr.addEventListener("error", function(event) {
|
||||
reject(
|
||||
new Error(__("Something went wrong making an internal API call."))
|
||||
);
|
||||
reject(new Error(__("Something went wrong making an internal API call.")));
|
||||
});
|
||||
|
||||
xhr.addEventListener("timeout", function() {
|
||||
|
@ -60,7 +55,7 @@ lbryio.call = function(resource, action, params = {}, method = "get") {
|
|||
|
||||
if (!response.success) {
|
||||
if (reject) {
|
||||
let error = new Error(response.error);
|
||||
const error = new Error(response.error);
|
||||
error.xhr = xhr;
|
||||
reject(error);
|
||||
} else {
|
||||
|
@ -81,54 +76,38 @@ lbryio.call = function(resource, action, params = {}, method = "get") {
|
|||
}
|
||||
});
|
||||
|
||||
// For social media auth:
|
||||
//const accessToken = localStorage.getItem('accessToken');
|
||||
//const fullParams = {...params, ... accessToken ? {access_token: accessToken} : {}};
|
||||
lbryio
|
||||
.getAuthToken()
|
||||
.then(token => {
|
||||
const fullParams = { auth_token: token, ...params };
|
||||
|
||||
// Temp app ID based auth:
|
||||
const fullParams = { app_id: lbryio.getAccessToken(), ...params };
|
||||
|
||||
if (method == "get") {
|
||||
xhr.open(
|
||||
"get",
|
||||
CONNECTION_STRING +
|
||||
resource +
|
||||
"/" +
|
||||
action +
|
||||
"?" +
|
||||
querystring.stringify(fullParams),
|
||||
true
|
||||
);
|
||||
xhr.send();
|
||||
} else if (method == "post") {
|
||||
xhr.open("post", CONNECTION_STRING + resource + "/" + action, true);
|
||||
xhr.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
|
||||
xhr.send(querystring.stringify(fullParams));
|
||||
} else {
|
||||
reject(new Error(__("Invalid method")));
|
||||
}
|
||||
if (method == "get") {
|
||||
xhr.open("get", CONNECTION_STRING + resource + "/" + action + "?" + querystring.stringify(fullParams), true);
|
||||
xhr.send();
|
||||
} else if (method == "post") {
|
||||
xhr.open("post", CONNECTION_STRING + resource + "/" + action, true);
|
||||
xhr.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
|
||||
xhr.send(querystring.stringify(fullParams));
|
||||
} else {
|
||||
reject(new Error(__("Invalid method")));
|
||||
}
|
||||
})
|
||||
.catch(reject);
|
||||
});
|
||||
};
|
||||
|
||||
lbryio.getAccessToken = () => {
|
||||
const token = getSession("accessToken");
|
||||
return token ? token.toString().trim() : token;
|
||||
lbryio.getAuthToken = () => {
|
||||
return keytar.getPassword("LBRY", "auth_token").then(token => {
|
||||
return token ? token.toString().trim() : null;
|
||||
});
|
||||
};
|
||||
|
||||
lbryio.setAccessToken = token => {
|
||||
setSession("accessToken", token ? token.toString().trim() : token);
|
||||
lbryio.setAuthToken = token => {
|
||||
return keytar.setPassword("LBRY", "auth_token", token ? token.toString().trim() : null);
|
||||
};
|
||||
|
||||
lbryio.setCurrentUser = (resolve, reject) => {
|
||||
lbryio
|
||||
.call("user", "me")
|
||||
.then(data => {
|
||||
resolve(data);
|
||||
})
|
||||
.catch(function(err) {
|
||||
lbryio.setAccessToken(null);
|
||||
reject(err);
|
||||
});
|
||||
lbryio.getCurrentUser = () => {
|
||||
return lbryio.call("user", "me");
|
||||
};
|
||||
|
||||
lbryio.authenticate = function() {
|
||||
|
@ -144,48 +123,56 @@ lbryio.authenticate = function() {
|
|||
});
|
||||
});
|
||||
}
|
||||
|
||||
if (lbryio._authenticationPromise === null) {
|
||||
lbryio._authenticationPromise = new Promise((resolve, reject) => {
|
||||
lbry
|
||||
.status()
|
||||
.then(response => {
|
||||
let installation_id = response.installation_id;
|
||||
|
||||
if (!lbryio.getAccessToken()) {
|
||||
lbryio
|
||||
.call(
|
||||
"user",
|
||||
"new",
|
||||
{
|
||||
language: "en",
|
||||
app_id: installation_id,
|
||||
},
|
||||
"post"
|
||||
)
|
||||
.then(function(responseData) {
|
||||
if (!responseData.id) {
|
||||
reject(
|
||||
new Error("Received invalid authentication response.")
|
||||
);
|
||||
}
|
||||
lbryio.setAccessToken(installation_id);
|
||||
lbryio.setCurrentUser(resolve, reject);
|
||||
})
|
||||
.catch(function(error) {
|
||||
/*
|
||||
until we have better error code format, assume all errors are duplicate application id
|
||||
if we're wrong, this will be caught by later attempts to make a valid call
|
||||
*/
|
||||
lbryio.setAccessToken(installation_id);
|
||||
lbryio.setCurrentUser(resolve, reject);
|
||||
});
|
||||
} else {
|
||||
lbryio.setCurrentUser(resolve, reject);
|
||||
lbryio
|
||||
.getAuthToken()
|
||||
.then(token => {
|
||||
if (!token || token.length > 60)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// check that token works
|
||||
return lbryio
|
||||
.getCurrentUser()
|
||||
.then(() => { return true; })
|
||||
.catch(() => { return false; });
|
||||
})
|
||||
.catch(reject);
|
||||
.then((isTokenValid) => {
|
||||
if (isTokenValid) {
|
||||
return;
|
||||
}
|
||||
|
||||
let app_id;
|
||||
|
||||
return lbry
|
||||
.status()
|
||||
.then(status => {
|
||||
// first try swapping
|
||||
app_id = status.installation_id;
|
||||
return lbryio.call("user", "token_swap", {auth_token: "", app_id: app_id}, "post");
|
||||
})
|
||||
.catch((err) => {
|
||||
if (err.xhr.status == 403) {
|
||||
// cannot swap. either app_id doesn't exist, or app_id already swapped. pretend its the former and create a new user. if we get another error, then its the latter
|
||||
return lbryio.call("user", "new", {auth_token: "", language: "en", app_id: app_id}, "post");
|
||||
}
|
||||
throw err;
|
||||
})
|
||||
.then(response => {
|
||||
if (!response.auth_token) {
|
||||
throw new Error(__("auth_token is missing from response"));
|
||||
}
|
||||
return lbryio.setAuthToken(response.auth_token);
|
||||
});
|
||||
})
|
||||
.then(lbryio.getCurrentUser())
|
||||
.then(resolve, reject);
|
||||
});
|
||||
}
|
||||
|
||||
return lbryio._authenticationPromise;
|
||||
};
|
||||
|
||||
|
|
|
@ -69,7 +69,7 @@ export const selectUserIsVerificationCandidate = createSelector(
|
|||
selectEmailToVerify,
|
||||
selectUser,
|
||||
(isEligible, isApproved, emailToVerify, user) =>
|
||||
(isEligible && !isApproved) || (emailToVerify && user && !user.has_email)
|
||||
emailToVerify && user
|
||||
);
|
||||
|
||||
export const selectUserIsAuthRequested = createSelector(
|
||||
|
|
|
@ -16,11 +16,11 @@
|
|||
"name": "LBRY Inc.",
|
||||
"email": "hello@lbry.io"
|
||||
},
|
||||
"license": "SEE LICENSE IN LICENSE.md",
|
||||
"license": "MIT",
|
||||
"bugs": {
|
||||
"url": "https://github.com/lbryio/lbry-app/issues"
|
||||
},
|
||||
"homepage": "https://github.com/lbryio/lbry-app#readme",
|
||||
"homepage": "https://github.com/lbryio/lbry-app",
|
||||
"dependencies": {
|
||||
"babel-cli": "^6.11.4",
|
||||
"babel-preset-es2015": "^6.13.2",
|
||||
|
@ -28,6 +28,7 @@
|
|||
"from2": "^2.3.0",
|
||||
"jshashes": "^1.0.6",
|
||||
"localforage": "^1.5.0",
|
||||
"keytar": "^4.0.3",
|
||||
"node-sass": "^3.8.0",
|
||||
"rc-progress": "^2.0.6",
|
||||
"react": "^15.4.0",
|
||||
|
@ -54,6 +55,7 @@
|
|||
"babel-preset-es2015": "^6.18.0",
|
||||
"babel-preset-react": "^6.16.0",
|
||||
"babel-preset-stage-2": "^6.18.0",
|
||||
"electron-rebuild": "^1.5.11",
|
||||
"eslint": "^3.10.2",
|
||||
"eslint-config-airbnb": "^13.0.0",
|
||||
"eslint-loader": "^1.6.1",
|
||||
|
@ -64,6 +66,7 @@
|
|||
"i18n-extract": "^0.4.4",
|
||||
"json-loader": "^0.5.4",
|
||||
"lint-staged": "^3.6.0",
|
||||
"node-loader": "^0.6.0",
|
||||
"node-sass": "^3.13.0",
|
||||
"prettier": "^1.4.2",
|
||||
"webpack": "^2.6.1",
|
||||
|
|
23
ui/watch.sh
23
ui/watch.sh
|
@ -7,16 +7,19 @@ set -euo pipefail
|
|||
|
||||
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
|
||||
|
||||
mkdir -p $DIR/dist/css
|
||||
mkdir -p $DIR/dist/js
|
||||
(
|
||||
cd "$DIR"
|
||||
mkdir -p $DIR/dist/css
|
||||
mkdir -p $DIR/dist/js
|
||||
|
||||
if [ ! -d "$DIR/node_modules" ]; then
|
||||
echo "Installing NPM modules"
|
||||
npm install
|
||||
fi
|
||||
if [ ! -d "$DIR/node_modules" ]; then
|
||||
echo "Installing NPM modules"
|
||||
npm install
|
||||
fi
|
||||
|
||||
# run sass once without --watch to force update. then run with --watch to keep watching
|
||||
$DIR/node_modules/.bin/node-sass --output $DIR/../app/dist/css --sourcemap=none $DIR/scss/
|
||||
$DIR/node_modules/.bin/node-sass --output $DIR/../app/dist/css --sourcemap=none --watch $DIR/scss/ &
|
||||
# run sass once without --watch to force update. then run with --watch to keep watching
|
||||
node_modules/.bin/node-sass --output $DIR/../app/dist/css --sourcemap=none $DIR/scss/
|
||||
node_modules/.bin/node-sass --output $DIR/../app/dist/css --sourcemap=none --watch $DIR/scss/ &
|
||||
|
||||
node_modules/.bin/webpack --config webpack.dev.config.js --progress --colors --watch
|
||||
node_modules/.bin/webpack --config webpack.dev.config.js --progress --colors --watch
|
||||
)
|
|
@ -33,6 +33,10 @@ module.exports = {
|
|||
// define an include so we check just the files we need
|
||||
include: PATHS.app
|
||||
},
|
||||
{
|
||||
test: /\.node$/,
|
||||
use: ["node-loader"]
|
||||
},
|
||||
{
|
||||
test: /\.css$/,
|
||||
use: ["style-loader", "css-loader"]
|
||||
|
|
|
@ -41,6 +41,10 @@ module.exports = {
|
|||
// define an include so we check just the files we need
|
||||
include: PATHS.app
|
||||
},
|
||||
{
|
||||
test: /\.node$/,
|
||||
use: ["node-loader"]
|
||||
},
|
||||
{
|
||||
test: /\.css$/,
|
||||
use: ["style-loader", "css-loader"]
|
||||
|
|
Loading…
Reference in a new issue