Ionic + Phonegap - add offline database with authorization - javascript

I've recently made simple app using ionic framework (also with phonegap android/ios). I needed to store some user data in database. I found out about firebase and successfully integrated it in my app. I've let user log in anonymously or by google. In my case each user should see only his content so I've added rules - and they work. However my app needs to run without internet connection on mobile platforms (android/ios) and firebase doesn't provide full offline storage (only temponary) in javascript (persistent storage is for now implemented in native only). So basically I hit the wall. I've been told about CouchDB/PouchDB and it indeed support offline storage and sync with remote server. I've created an account on cloudant, but then I remembered I need user authorization - as I said user needs to have only his own data. So let's say guest have only local database and user logged in with google have database also on remote server so he can switch device and work with the same (his own) data. But I can't find such tool in CouchDB (cloudant). Is there any other solution, which works with ionic smoothly and provides:
offline database,
authorization with google,
remote database with sync (with local db) after successful authorization,
remote database is hosted on some server (may be paid, but trial needed) - i don't want to worry about server - client only
?
TL;DR: I'm looking for a hosting service working on ionic/angularjs/cordova with remote database + local database and synchronisation between them.

What is wrong with using MongoDB it is by far the easiest to integrate with Express/Node.js using the npm mongoose. It's really popular and great way to store data and its similar to firebase.
For example:
var Express = require('express'),
app = Express.router();
app.route('/api/auth/signup').post(users.signup);
Schema Example:
var mongoose = require('mongoose'),
Schema = mongoose.Schema
var UserSchema = new Schema({
firstName: {
type: String,
trim: true,
default: '',
required: 'Please fill in your first name'//message given if you fail to provide the required field.
},
lastName: {
type: String,
trim: true,
default: '',
required: 'Please fill in your last name'
},
displayName: {
type: String,
trim: true
},
email: {
type: String,
unique: 'Email already exists. Sign In?',
lowercase: true,
trim: true,
default: '',
validate: [validateLocalStrategyEmail, 'Please fill a valid email address'],//create a custom Email Validation strategy
required: 'Please enter a valid business Email'
},
});
Sample Controller:
exports.signup = function (req, res) {
// For security measurement we remove the roles from the req.body object
delete req.body.roles;
// Init user and add missing fields
var user = new User(req.body);
user.provider = 'local';
user.displayName = user.firstName + ' ' + user.lastName;
// Then save the user
user.save(function (err) {
if (err) {
return res.status(400).send({
message: errorHandler.getErrorMessage(err)
});
} else {
// Remove sensitive data before login
user.password = undefined;
user.salt = undefined;
var jwtToken = authentication.signToken(user);
res.json({ user: user, token: jwtToken });
}
});
};
You can find some of this on MEAN.js Github.
But you must use LocalStorage. JSON web tokens for your ionic app if you want it to work on an iPad/iPhone.

The best bet is to use PouchDB which works with local storage (works on ionic/cordova) and will sync to a remote couchdb server with just a few lines of code:
var localDB = new PouchDB('mylocaldb');
var remoteDB = new PouchDB('http://localhost:5984/myremotedb');
localDB.sync(remoteDB);
The only tricky bit is the remote server with authentication. CouchDB can handle oauth out of the box, but on a per-database case, meaning you need to create a database per user, which is what many people do, but that's quite involved. Do a search for CouchDB on npmjs.com and see what comes up.
Alternatively, you can wrap your CouchDB behind a node server like Express, and handle auth in Express before passing the requests through.
It's not an easy task, and there's a reason Firebase charges a small fortune once you start racking up usage. If you're not big on server-side stuff, a simpler approach might be to use meteor or hoodie.

Related

Picture missing from credential token with GSI

I have been using the new GIS library with the One Tap UX. I have followed all the steps outlined in the setup guide and the authentication works as expected, in fact I like the new flow; Nonetheless I am experiencing a very peculiar issue. For some reason, the picture property is missing from the credential response.
A couple of things to note are:
This is an internal application, which means it is only being used by the google workspace users of the respective domain.
This happens with all users whose profile picture is set using the Admin Directory API.
What I have in the front end is the following:
const promptParent = "signInBox";
const gsiInitializeConfig = {
client_id: '4xxyy55zzz.apps.googleusercontent.com',
callback: handleCredentialResponse,
prompt_parent_id: promptParent,
auto_select: true,
cancel_on_tap_outside: false
};
let idClient;
const doSignIn = ()=>{
idClient = google.accounts;
idClient.id.initialize(gsiInitializeConfig);
idClient.id.prompt();
}
const handleCredentialResponse = (response)=>{
const {credential} = response;
//send idToken to the backend for verification
});
When the DOMContent is loaded, I programatically invoke the doSignIn function. And the one tap shows and it works great. It returns the idToken which then I send to the backend for verification. In the backend I am using express and I have the following:
const token = getTokenFromBody();
if(!token){ return unauthenticated(res); }
const auth2Client = new OAuth2Client(GOOGLE_CLIENT_ID);
const ticket = await auth2Client.verifyIdToken({
audience: GOOGLE_CLIENT_ID,
idToken: token
}).catch(error=>{
console.error(`Failed to verify google id token: ${error}`);
unauthorized(res, "Invalid grant");
});
if(!ticket){ return; }
const ticketPayload = ticket.getPayload();
console.log("ticketPayload", JSON.stringify(ticketPayload));
The logged object for the ticketPayload above looks like this:
{
iss: "https://accounts.google.com",
nbf: 1378,
aud: "44xxyy55zz.apps.googleusercontent.com",
sub: "1559234417",
hd: "domain.com",
email: "user#domain.com",
email_verified: true,
azp: "44xxyy55zz.apps.googleusercontent.com",
name: "User Name",
given_name: "User",
family_name: "Name",
iat: 1664828678,
exp: 1664832278,
jti: "f0549d2544c905sadfcbc13110"
}
That is the response for all the users in the domain, however, for my user, whose photo was set using the page "https://myaccount.google.com", the response is the following:
{
iss: "https://accounts.google.com",
nbf: 1378,
aud: "44xxyy55zz.apps.googleusercontent.com",
sub: "1559234417",
hd: "domain.com",
email: "user#domain.com",
email_verified: true,
azp: "44xxyy55zz.apps.googleusercontent.com",
name: "User Name",
picture: "https://lh3.googleusercontent.com/a-/N5pZpE-zbJUg3=s96-c", // <-----!!!
given_name: "User",
family_name: "Name",
iat: 1664828678,
exp: 1664832278,
jti: "f0549d2544c905sadfcbc13110"
}
In comparisson with the old google sign in library, this behavior is different. How can I get the picture property for all users in the domain?
I reached out to Google Workspace support were they educated me with the following:
Admin-set user profile picutres (either set by an Admin via the Admin Console itself, or by the Admin SDK API call) are private and are not returned in the credential response of the Google Sign In flow.
The reason for that is that when an administrator sets a profile photo to a user's account, the photo becomes visible only to users in the organization and to external users they use Google Chat with. In contrast, (and only if users are allowed to manage their own profile photo) user-set photos are public by default. That is explained under the "Where a user's photo appears section of this support article.
This behavior cannot be changed for privacy reasons. An admin-set photo is neither public information, nor is set by the user, hence is not available in the token.
Although the solution provided makes sense, there is something that I feel is wrong. The fact that the old google sign in library never presented this behavior makes me feel that way. Why should the new sign in library present this behavior now? It is really absurd.

Firebase: how to send reset-link through email template sent from nodemailer?

I implemented sending emails via nodemailer.
Now when I create new user, that new user get "welcome email".
Problem is cus that "welcome email" should contain option for
resetting password.
How to add Firebase Resetting link in nodemailer email template?
This is my Email Template code for nodemailer
const output = `
<p>You have access to the Church Mutual Assignment Tool.</p>
<p>Follow this link to create new password for your account ${userRecord.email}:</p>
<a href="${resetPasswordLink}">
${resetPasswordLink}
</a>
<p>Thanks,</p>
<p>Your Church Mutual Assignment Tool team</p>
`
let message = {
from: 'nyik6nntutmq3vz6#ethereal.email',
to: `${user.email}`,
subject: 'Welcome to the Church Mutual Assignment Tool',
text: 'Plaintext version of the message',
html: output
}
This is my Nodemailer code:
var mailer = require('nodemailer')
var mailConfig = {
host: 'smtp.ethereal.email',
port: 587,
auth: {
user: 'nyik6nntutmq3vz6#ethereal.email',
pass: '3cbRjkZdPquDqA725s'
}
}
var transporter = mailer.createTransport(mailConfig)
module.exports = transporter
The Admin SDK now has some methods that allow you to do just this exact thing. Check out the docs on the email action links, specifically the "Generate password reset email link" section.
// Admin SDK API to generate the password reset link.
const email = 'user#example.com';
admin.auth().generatePasswordResetLink(email, actionCodeSettings)
.then((link) => {
// Do stuff with link here
})
.catch((error) => {
// Some error occurred.
});
Full disclosure - I haven't actually used any of those functions, and I'm a little concerned that the page in question refers a lot to mobile apps - so you might have to pass it the mobile app config.
const actionCodeSettings = {
// URL you want to redirect back to. The domain (www.example.com) for
// this URL must be whitelisted in the Firebase Console.
url: 'https://www.example.com/checkout?cartId=1234',
// This must be true for email link sign-in.
handleCodeInApp: true,
iOS: {
bundleId: 'com.example.ios'
},
android: {
packageName: 'com.example.android',
installApp: true,
minimumVersion: '12'
},
// FDL custom domain.
dynamicLinkDomain: 'coolapp.page.link'
};
On the other hand, the page also says these features provide the ability to:
Ability to customize how the link is to be opened, through a mobile
app or a browser, and how to pass additional state information, etc.
Which sounds promising, allowing it to open in the browser... but if you are developing for web - and the function errors out when not provided iOS/Android information... then I'm afraid you'll have to do it the old fashioned approach and create your own implementation... but I'm leaning towards this .generatePasswordResetLink should work for you.

Nativescript {N} oAuth plugin - Retrieve User Facebook Data

Context
I'm attempting to create an Android app with Nativecript using JavaScript. On the first page, it asks the user to connect with Facebook, and I intend to verify whether or not an account exists with their email address.
Tools
I'm using the nativescript-oauth package to handle the OAuth connection to Facebook. I'm working on a Windows 10 machine via command line.
Code
app.js
var tnsOAuthModule = require("nativescript-oauth");
var facebookInitOptions = TnsOAuthOptionsFacebook = {
clientId: 'REDACTED',
clientSecret: 'REDACTED',
scope: ['email']
};
tnsOAuthModule.initFacebook(facebookInitOptions);
application.start({ moduleName: "views/start/start" });
start.js
//...
var tnsOAuthModule = require("nativescript-oauth");
//...
exports.fbConnect = function(){
console.log("Facebook Connect button tapped");
tnsOAuthModule.login()
.then(()=>{
console.log('logged in');
var token = tnsOAuthModule.accessToken();
console.log("FB Auth token: " + token);
console.log(JSON.stringify(tnsOAuthModule));
})
.catch((er)=>{
console.log(er);
});
console.log("Login sucessful");
}
What goes wrong
The above outputs the following:
JS: Facebook Connect button tapped
...
JS: logged in
JS: FB Auth token: EAAC50oamJosBAF1F3lrGAOntENgSAZA40w4iE3rNOLP1W_REDACTED_Cb7yS9ZB1Ro4qhLroOMwZD
JS: {"instance":{"tokenResult":{"accessToken":"EAAC50oamJosBAF1F3lrGAOntENgSAZA40w4iE3rNOLP1W_REDACTED_Cb7yS9ZB1Ro4qhLroOMwZD","accessTokenExpiration":"2017-03-24T18:27:04.176Z","refreshTokenExpiration":"2017-03-24T18:27:04.176Z"},"credentials":{"authority":"https://www.facebook.com/dialog","tokenEndpointBase":"https://graph.facebook.com","authorizeEndpoint":"/oauth","tokenEndpoint":"/v2.3/oauth/access_token","clientId":"REDACTED","clientSecret":"REDACTED","redirectUri":"https://www.facebook.com/connect/login_success.html","scope":"email"}}}
JS: Application started successfully
As you can see, I successfully authorise the Facebook app and retrieve a working access key and can parse the object that is returned - however I'm trying to retrieve the users' email address. I can see that the "email" is within the scope.
Question
How can I use the nativescript-oauth plugin, or the data from the above object, to retrieve the users' email address, as defined the in scope?
Resources
Nativescript homepage - https://www.nativescript.org/
nativescript-oauth GitHub page - https://github.com/alexziskind1/nativescript-oauth
Nativescript official release of OAuth plugin - https://www.nativescript.org/blog/introducing-the-nativescript-oauth-plugin
You must change the scope to get more details(add "user_friends" to get their friend's list, add "public_profile" for profile info)
var facebookInitOptions = TnsOAuthOptionsFacebook = {
clientId: 'REDACTED',
clientSecret: 'REDACTED',
scope: ['email', 'user_friends', 'public_profile']
};
Lastly, in your "App review" section of your facebook developer page, ensure those scope fields are active and shown(they will have a green dot next to them with description of data). You might need to make the app live/start a submission to get approval first if the above code doesn't work.

User authentication without back-end

I have a simple Backbone.js app with User model with different roles and I use json-server to emulate some backend basics. I need to make a basic authentication -- i.e. I need my User to be able to login and save his session somewhere (for that he wouldn't need to sign in each time he refreshes his browser). I've got a db.json file where I already have some users:
{
"users": [
{
"login": "admin",
"password": "password",
"role": "admin",
"id": 1
}
]
}
and here is my User model:
var User = Backbone.Model.extend({
defaults: {
login: "",
password: "",
role: ""
},
// Updated
url: function () {
return "http://localhost:3000/users?login=" +
this.attributes.login + "&password=" + this.attributes.password;
}
});
I don't get quite good how could I manage authentication (i.e. entering login and password in form and storing the user session without proper backend). I thought about making a token field in my User model and filling in in each time user signs in and saving it in cookies, but I don't get how could I do that either way. I would be very grateful if someone could help me with this task.
ADDDED This is my login function in my view:
signIn: function () {
var login = $('#js-login').val();
var password = $('#js-password').val();
if (login && password) {
var currentUser = new User({
login: login,
password: password
});
currentUser.fetch({
success: function () {
console.log(currentUser.toJSON());
},
error: function (err) {
console.log(err);
}
});
}
}
But instead of finding a user from my json-server it just creates a new user with all empty attributes except of values of #js-login and #js-password input fields
ADDED I guess I should find my users by the query in url above in my collection, but I don't actually get how I would manage that
Repo with my project
This is simplified flow for your app:
Each time user open your website, check his cookies.
If cookies contain user info (saved username, password), check match with the info in your DB. If matched, go to home page. Otherwise, clear cookies, go to login page
If cookies not contain user info, go to login page
In login page, after user success logged in, save user info to cookies for next time check.
You can use some mechanism to encode user info (tokens, encryption...) to secure info stored in cookies/sessions. But store authentication DB in client is really weak security point. Sample code below:
Model:
var User = Backbone.Model.extend({
url: function () {
return "users?login" + this.attributes.login + "&password=" + this.attributes.password;
},
isAdmin: function () {
return (this.get("role") == "admin");
}
});
In your view:
// Load username password from cookie (just simple example)
var username = $.cookie("username"),
password = $.cookie("password");
if (username && password) {
var userModel = new User({
login: username,
password: password
});
userModel.fetch({
success: function () {
if (userModel.isAdmin) {
// e.g. go to admin page
} else {
// e.g. go to normal user page
}
// Save to cookie/session here
},
error: function () {
// Go to login page
}
});
} else {
// Go to login page
}
About cookie, you can refer How do I set/unset cookie with jQuery?
About getting username/password input form, you can just use simple jquery selector (very easy to google for it, e.g. https://www.formget.com/jquery-login-form/)
Here you can refer to this plugin that uses mostly the jquery functions as mentioned in the documentation here
I would not be going into much detail as the documentaion is quite clear.
This refers to the authentication with the jquery
Now IF you want to authenticate the user using backbone.js
if the route came back with {loggedIn: false} the backbone router would send the user to the login/register pages only. But if it came back with a users profile information then it would obviously mean he had a session.
wire up $.ajax to respond to 401 (Unauthorized) status codes.
Also to mention as stated in this stackoverflow thread
Hope it may be able to help you a bit.
Here is the step by step guide to authenticate with backbone.js

Parse Login in node.js - Login successful but 'There is no current user'

I'm having trouble interacting with my Parse data in node.js. I'm able to login successfully, but Parse.User.current() returns null. After running the below code, I'd like to query data that has ACL read/write only for that user. Currently, that query returns empty, but if I change that data to public read/write, I can see the results of the query output in the terminal.
Here is my node.js code:
Prompt.get([{
name: 'username',
required: true}, {
name: 'password',
hidden: true}], function (err, result) {
if (err) {
console.log('Error: ' + err);
} else {
Parse.User.logIn(result.username, result.password, {
success: function(user) {
console.log('LOGGED IN');
console.log(user);
console.log(Parse.Session.current());
console.log(Parse.User.current());
... (query happens below this)
And my console output:
prompt: username: pablo
prompt: password:
LOGGED IN
ParseUser { _objCount: 0, className: '_User', id: 'EXyg99egkv' }
ParsePromise {
_resolved: false,
_rejected: true,
_resolvedCallbacks: [],
_rejectedCallbacks: [],
_error: 'There is no current user.' }
null
Thanks in advance.
Is this not a usecase for Parse.User.become()? From the parse docs:
If you’ve created your own authentication routines, or otherwise
logged in a user on the server side, you can now pass the session
token to the client and use the become method. This method will ensure
the session token is valid before setting the current user.
Parse.User.become("session-token-here").then(function (user) {
// The current user is now set to user.
}, function (error) {
// The token could not be validated.
});
I had similar problems and found this Parse blog that explains the issue:
Also in Cloud Code, the concept of a method that returns the current user makes sense, as it does in JavaScript on a web page, because there’s only one active request and only one user. However in a context like node.js, there can’t be a global current user, which requires explicit passing of the session token. Version 1.6 and higher of the Parse JavaScript SDK already requires this, so if you’re at that version, you’re safe in your library usage.
You can execute queries with user credentials in a node.js environment like this:
query.find({ sessionToken: request.user.getSessionToken() }).then(function(data) {
// do stuff with data
}, function(error) {
// do stuff with error
});
If you wish to validate that token before using it, here's an explanation of how you could go about doing that:
one way would be to query for an object known to be only readable by the user. You could have a class that stores such objects, and have each one of them use an ACL that restricts read permissions to the user itself. If running a find query over this class returns 0 objects with a given sessionToken, you know it's not valid. You can take it a step further and also compare the user object id to make sure it belongs to the right user.
Session tokens cannot be queried even with the master key.

Categories

Resources