My requirement
Read the token from Redis which is not reserved for application(it's application value is not set).
Update this non-reserved token with given application name and Other threads should not get same token for reservation.
static async reserveNewTokens(indextype: string, numberOfNewTokensForReservation: number, appname: string) {
mgLogger.debug(this._module, "reserveTokens()", "start");
for (let index = 0; index < numberOfNewTokensForReservation; index++) {
const nonReserverdToken = await LicenseDbManager.getNonReserverdToken(indextype);
LicenseDbManager.reserveNewForProject(nonReserverdToken.documents[0].id, appname)
}
mgLogger.debug(this._module, "reserveNewTokens()", "end");
}
After execution of above code I observed that same token is override with two application names. Code is executed on subscription of Redis pub/sub channel. Please let me know how can I solve this issue, I am using node-redis.
Instead of using EXISTS/GET to check if the token does not exists and only then set it, you can use SET with the NX (stands for not exists) modifier:
let reply;
do {
reply = await client.set(generateTokenKey(), 'value', {
NX: true
});
} while (reply === null)
Related
I am coding a project similar to patreon.com and my goal is to let multiple members create their own subscription plans and sell it.
I came across a firebase extension for stripe payments - https://firebase.google.com/products/extensions/stripe-firestore-stripe-payments
The problem with this extension is that I can only create a 1 premium membership that adds custom claims to the auth object and I can validate it like this:
export default async function isUserPremium(): Promise<boolean> {
await auth.currentUser?.getIdToken(true);
const decodedToken = await auth.currentUser?.getIdTokenResult();
return decodedToken?.claims?.stripeRole ? true : false;
}
That means that even if I have 100 different subscriptions, I can only attach single boolean value, which is useless.
I went to the source code and I found this snippet:
// Update their custom claims
if (role) {
try {
// Get existing claims for the user
const { customClaims } = await admin.auth().getUser(uid);
// Set new role in custom claims as long as the subs status allows
if (['trialing', 'active'].includes(subscription.status)) {
logs.userCustomClaimSet(uid, 'stripeRole', role);
await admin
.auth()
.setCustomUserClaims(uid, { ...customClaims, stripeRole: role });
} else {
logs.userCustomClaimSet(uid, 'stripeRole', 'null');
await admin
.auth()
.setCustomUserClaims(uid, { ...customClaims, stripeRole: null });
}
} catch (error) {
// User has been deleted, simply return.
return;
}
}
I don't fully understand this code, but I think this is where the boolean value is assigned.
Would it be possible to somehow edit this source code, so that instead of boolean value, I could store subscription plan ids in Array , so that in the front end I could validate and allow customer to access users content only if he has active plan in that array
?
I'm trying to issue some Fabtokens to users and then use them in various scenarios such as transferring, redeeming, etc. I follow the Node SDK documentation here: https://fabric-sdk-node.github.io/master/tutorial-fabtoken.html
This is how they do the Fabtoken operations:
// create a TokenClient instance from client
const tokenclient = client.newTokenClient(mychannel);
// create a transaction ID for "issuer"
const txId = client.newTransactionID();
// create two parameters for issue, one for user1 and one for user2
const param1 = {
owner: user1.getIdentity().serialize(),
type: 'USD',
quantity: '500',
};
const param2 = {
owner: user2.getIdentity().serialize(),
type: 'EURO',
quantity: '300',
};
// create the token request for issue
const issueRequest = {
params: [param1, param2],
txId: txId,
};
// issuer calls issue method to issue tokens to user1 and user2
const result = await tokenClient.issue(issueRequest);
And then use a different tokenClient to list the tokens of user 1:
const user1Tokenclient = client1.newTokenClient(mychannel);
// user1 lists tokens
const mytokens = await user1TokenClient.list();
// iterate the tokens to get token id, type, and quantity for each token
for (const token of tokens) {
// get token.id, token.type, and token.quantity
// token.id will be used for transfer and redeem
}
It's mentioned on the Node SDK's Client class page here: https://fabric-sdk-node.github.io/master/Client.html that switching userContexts with the same client instance is an anti-pattern and not recommended since client instances are stateful.
As they suggest, I create my client instances with different user contexts. This is how I create my clients, set their user context and create my tokenClient instances:
const adminClient = new Fabric_Client();
const admin = await adminClient.createUser(user_opts);
adminClient.setUserContext(admin, true);
let adminConfig = {
admin: admin,
adminClient: adminClient,
adminTokenClient: adminClient.newTokenClient(channel)
}
const server = await serverClient.createUser(server_opts);
serverClient.setUserContext(server, true);
let serverConfig = {
server: server,
serverClient: serverClient,
serverTokenClient: serverClient.newTokenClient(channel)
}
Later on, I'm using these config objects to issue some tokens to different users. How I issue tokens to my server account from my issuer (admin) account:
const txId = adminConfig.adminClient.newTransactionID();
let issueQuery = {
tokenClient: adminConfig.adminTokenClient,
txId: txId,
channel: channel,
params: []
}
for(let i=0; i < 3; ++i) {
let param = {
owner: serverConfig.server.getIdentity().serialize(),
type: 'test',
quantity: '1'
}
issueQuery.params.push(param);
}
let issueTx = await waitForIssue(issueQuery);
This successfully issue three tokens to the server as expected. The problem is that when I try to access to the tokens of my server like the example they provide using a similar code:
let server_tokens = await serverConfig.serverTokenClient.list();
for (let server_token of server_tokens) {
console.log(server_token.id);
}
Result is just empty and I don't get any error messages. However, when I check the transaction using queryTransaction(txId) for the token issue transaction I generate, I can see that owner of the issued tokens in that transaction is the server and that's how I can be sure that I can successfully issue the tokens to the server. Is there any other way to check the tokens of my server? Or shouldn't I use a different client and user context per each user as they suggest? Because, previously I was able to see the tokens of the server when I used a single client and single user context to issue and list tokens. But this approach caused me problems when I was trying to transfer my tokens asynchronously.
As far as I know FabTokens are removed from the Master branch of Fabric 2.0 now as described in these links:
https://gerrit.hyperledger.org/r/c/fabric/+/32979
https://lists.hyperledger.org/g/fabric/topic/fabtoken/34150195?p=,,,20,0,0,0::recentpostdate%2Fsticky,,,20,2,0,34150195
I would expect the tutorial and the information in the Fabric docs to be removed in due course.
I'm trying to build an application in such a way that NodeJS serves as the backend with all business logic exposing JSON REST services to be consumed by the angular 4 app which is nothing but a dumb client. So far so good, however I'm having a hard time figuring the Session management.
I found that token based authentication is a way to go since you might one day be serving mobile apps however, I have a problem: if I go with JSONWebToken strategy on the server side with token expiration set to half an hour, then my client will need to re authenticate it self after half an hour which doesn't seem like a good fit, because then it may force the user to sign in again that is already working on the client application which is not how any web app works. Should I also need to maintain session management at Angular level and auto sign in if my token expires on server but then it violates the principle of a dumb client or I should scrap it altogether implement sessions at NodeJS it self? Another thing is if I implement the WebTokenStrategy I found that for every request that comes from the client I'll be making a trip to database to verify a user which I can cache in session if I'm doing session management on NodeJS.
Last thing that I have a hard time figuring out is okay I can secure my resources on NodeJS but then I also need my routes and pages to be served depending on user rights in my client application, should I also store this information in the NodeJS database and serve by the same API server but I think this again violates the single responsibility principle or should there be another database for this client site route and user management.
Can someone suggest a good approach and if possible with examples?
Thanks.
No a JSON web token do not required a trip to the database since you encode the information you want on the payload. However you can implement a redis strategy if you want to be able to revoke them (For right changes for example). The signature part will be used by your server to ensure the authenticity (thanks to your server-side JWT secret).
You can also choose the expiration time you want. But if you want to limit it to 30 minutes you can also implement a renew strategy. (Ask for a new token before the old one will expire soon : The server will just deliver a new token with the same data encode. For the front end renew strategy you can use such a lib :
'use strict';
/**
* Helper class to decode and find JWT expiration.
*/
class JwtHelper {
urlBase64Decode(str) {
let output = str.replace(/-/g, '+').replace(/_/g, '/');
switch (output.length % 4) {
case 0: { break; }
case 2: { output += '=='; break; }
case 3: { output += '='; break; }
default: {
throw 'Illegal base64url string!';
}
}
return this.b64DecodeUnicode(output);
}
// credits for decoder goes to https://github.com/atk
b64decode(str) {
let chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=';
let output = '';
str = String(str).replace(/=+$/, '');
if (str.length % 4 == 1) {
throw new Error("'atob' failed: The string to be decoded is not correctly encoded.");
}
for (
// initialize result and counters
let bc = 0, bs, buffer, idx = 0;
// get next character
buffer = str.charAt(idx++);
// character found in table? initialize bit storage and add its ascii value;
~buffer && (bs = bc % 4 ? bs * 64 + buffer : buffer,
// and if not first of each 4 characters,
// convert the first 8 bits to one ascii character
bc++ % 4) ? output += String.fromCharCode(255 & bs >> (-2 * bc & 6)) : 0
) {
// try to find character in table (0-63, not found => -1)
buffer = chars.indexOf(buffer);
}
return output;
}
// https://developer.mozilla.org/en/docs/Web/API/WindowBase64/Base64_encoding_and_decoding#The_Unicode_Problem
b64DecodeUnicode(str) {
return decodeURIComponent(Array.prototype.map.call(this.b64decode(str), (c) => {
return '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2);
}).join(''));
}
decodeToken(token) {
let parts = token.split('.');
if (parts.length !== 3) {
throw new Error('JWT must have 3 parts');
}
let decoded = this.urlBase64Decode(parts[1]);
if (!decoded) {
throw new Error('Cannot decode the token');
}
return JSON.parse(decoded);
}
getTokenExpirationDate(token) {
let decoded;
decoded = this.decodeToken(token);
if (!decoded.hasOwnProperty('exp')) {
return null;
}
let date = new Date(0); // The 0 here is the key, which sets the date to the epoch
date.setUTCSeconds(decoded.exp);
return date;
}
isTokenExpired(token, offsetSeconds) {
let date = this.getTokenExpirationDate(token);
offsetSeconds = offsetSeconds || 0;
if (date == null) {
return false;
}
// Token expired?
return !(date.valueOf() > (new Date().valueOf() + (offsetSeconds * 1000)));
}
}
const jwtHelper = new JwtHelper();
const decodedData = jwtHelper.decodeToken('eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ');
console.log(decodedData)
I am in the process of migrating a node.js application to Firebase v3.
In v2 I was using FirebaseTokenGenerator to generate custom tokens. It requires an apiToken, which is inconsistent with the way that Firebase v3 works in node, and I see there is now a 'createCustomToken' method on the firebase.auth service so I am assuming that I should now use that.
The issue is that this method appears to accept only 'uid' and 'developerClaims' as parameters, where FirebaseTokenGenerator also accepted an options object which included an 'expires' attribute.
Is there a way to give the token generated by 'createCustomToken' an expiry date?
Update
Reference: https://groups.google.com/forum/#!topic/firebase-talk/Ezy3RDNNRAs
Once they login using the custom token, the Firebase exchanged Id
token is long lived and is automatically refreshed. You don't need to
mint a new custom token on each request. You can verify the Firebase
Id token using the backend server libraries and as long as it is
valid, you don't to sign in the user again.
So it looks like the generated token is temporary and used to retrieve an id token (internally) with
FIRAuth.auth()?.signInWithCustomToken(customToken)
From then on the client should be good.
With Firebase 3.0.4 Currently No.
From the nodejs module source code it looks like the jwt expiresIn is set at 1 hour. This is unacceptable for mobile app users (as long as they're logged in their key should be fine). Hope this is fixed asap since it blocks us from upgrading our sdk
FirebaseTokenGenerator.prototype.createCustomToken = function(uid, developerClaims) {
if (typeof uid !== 'string' || uid === '') {
throw new Error('First argument to createCustomToken() must be a non-empty string uid');
} else if (uid.length > 128) {
throw new Error('First argument to createCustomToken() must a uid with less than or equal to 128 characters');
} else if (typeof developerClaims !== 'undefined' && (typeof developerClaims !== 'object' || developerClaims === null || developerClaims instanceof Array)) {
throw new Error('Optional second argument to createCustomToken() must be an object containing the developer claims');
}
var jwtPayload = {};
if (typeof developerClaims !== 'undefined') {
jwtPayload.claims = {};
for (var key in developerClaims) {
/* istanbul ignore else */
if (developerClaims.hasOwnProperty(key)) {
if (BLACKLISTED_CLAIMS.indexOf(key) !== -1) {
throw new Error('Developer claim "' + key + '" is reserved and cannot be specified');
}
jwtPayload.claims[key] = developerClaims[key];
}
}
}
jwtPayload.uid = uid;
return jwt.sign(jwtPayload, this.serviceAccount.private_key, {
audience: FIREBASE_AUDIENCE,
expiresIn: ONE_HOUR_IN_SECONDS,
issuer: this.serviceAccount.client_email,
subject: this.serviceAccount.client_email,
algorithm: ALGORITHM
});
};
Update the below won't work due to this comment
"exp The time, in seconds, at which the token expires. It can be at a maximum 3600 seconds later than iat."
Firebase token max life span is 1 hour.
The solution appears to be generating our own token
Use a JWT library
You can create a custom token suitable for authenticating with Firebase by using any JWT creation library. Create a JWT that includes the following claims and is signed using RS256.
JWT claims
iss Your project's service account email address
sub Your project's service account email address
aud https://identitytoolkit.googleapis.com/google.identity.identitytoolkit.v1.IdentityToolkit
iat The current time, in seconds
exp The time, in seconds, at which the token expires. It can be at a maximum 3600 seconds later than iat.
uid The unique identifier of the signed-in user (must be a string, between 1-36 characters long)
claims (optional) Custom claims to include in the Security Rules auth variable.
An example of a token generating function that should meet the above criteria:
var ALGORITHM = 'RS256';
// List of blacklisted claims which cannot be provided when creating a custom token
var BLACKLISTED_CLAIMS = [
'acr', 'amr', 'at_hash', 'aud', 'auth_time', 'azp', 'cnf', 'c_hash', 'exp', 'iat', 'iss', 'jti',
'nbf', 'nonce'
];
var FIREBASE_AUDIENCE = 'https://identitytoolkit.googleapis.com/google.identity.identitytoolkit.v1.IdentityToolkit';
function generateFirebaseToken(serviceAccount, uid, expiresIn, developerClaims) {
var jwtPayload = {};
if (typeof developerClaims !== 'undefined') {
jwtPayload.claims = {};
for (var key in developerClaims) {
if (developerClaims.hasOwnProperty(key)) {
if (BLACKLISTED_CLAIMS.indexOf(key) !== -1) {
throw new Error('Developer claim "' + key + '" is reserved and cannot be specified');
}
jwtPayload.claims[key] = developerClaims[key];
}
}
}
jwtPayload.uid = uid;
return jwt.sign(jwtPayload, serviceAccount.private_key, {
audience: FIREBASE_AUDIENCE,
expiresIn: expiresIn,
issuer: serviceAccount.client_email,
subject: serviceAccount.client_email,
algorithm: ALGORITHM
});
}
Reference: firebase docs
I'm using the node-xmpp module to connect to a XMPP server and join a group chat. Connecting to the server, setting the presence, joining the room and reading out messages works so far. But I want to receive the userlist of the room too.
The XMPP protocol requires to send a presence stanza when the client enters the room (http://xmpp.org/extensions/xep-0045.html#enter-pres). But how can I now parse it in node?
My code currently looks like this:
var xmpp = require('node-xmpp');
// Create the XMPP Client
var cl = new xmpp.Client({
jid: jid,
password: password,
reconnect: true
});
// Do things when online
cl.on('online', function() {
util.log("We're online!");
// Set client's presence
cl.send(new xmpp.Element('presence', { type: 'available' }).c('show').t('chat'));
cl.send(new xmpp.Element('presence', { to: room_jid+'/'+room_nick }).c('x', { xmlns: 'http://jabber.org/protocol/muc' }).c('history', {seconds: 1}));
// Send keepalive
setInterval(function() {
cl.send(' ');
}, 30000);
cl.on('stanza', function(stanza) {
// always log error stanzas
if (stanza.attrs.type == 'error') {
util.log('[error] ' + stanza);
return;
}
// ignore everything that isn't a room message
if (!stanza.is('message') || !stanza.attrs.type == 'chat') {
return;
}
var body = stanza.getChild('body');
// message without body is probably a topic change
if (!body) {
return;
}
// Extract username
var from, room, _ref;
_ref = stanza.attrs.from.split('/'), room = _ref[0], from = _ref[1];
var message = body.getText();
// Log topics and messages to the console
if(!from) {
util.log('Topic: ' + message);
} else {
util.log('[' + from + ']: ' + message);
}
});
});
I already tried triggering presence by using
if(stanza.is('presence')) {}
within the cl.on('stanza') part but it doesn't work.
UPDATE: I'm describing a new method now which doesn't require the client to send requests.
Background: When the client joins a group chat, the server returns presence stanzas which contain information about the connected users to the group chat.
cl.on('stanza', function(stanza) {
// always log error stanzas
if (stanza.attrs.type == 'error') {
util.log('[error] ' + stanza);
return;
}
if(stanza.is('presence')){
// We are only interested in stanzas with <x> in the payload or it will throw some errors
if(stanza.getChild('x') !== undefined) {
// Deciding what to do based on the xmlns attribute
var _presXmlns = stanza.getChild('x').attrs.xmlns;
switch(_presXmlns) {
// If someone is joining or leaving
case 'http://jabber.org/protocol/muc#user':
// Get the role of joiner/leaver
_presRole = stanza.getChild('x').getChild('item').attrs.role;
// Get the JID of joiner/leaver
_presJID = stanza.getChild('x').getChild('item').attrs.jid;
// Get the nick of joiner/leaver
_presNick = stanza.attrs.from.split('/')[1];
// If it's not none, this user must be joining or changing his nick
if(_presRole !== 'none') {
// We are now handling the data of joinging / nick changing users. I recommend to use an in-memory store like 'dirty' [https://github.com/felixge/node-dirty] to store information of the users currentliy in the group chat.
} else {
// We are now handling the data of leaving users
}
break;
}
return;
}
return;
}
OLD METHOD
I previously described a method how to query the server for current users in the group chat. By maintaining a store where all user traffic (joining, leaving, nick changing) is stored, this is no longer required. However you could still use it to make sure the data is consistent by issues like a presence stanza was not delivered to the client correctly. That's the reason it's still described below:
To request a list with users connected to the room, you need to perform the following actions:
First send a request to the server and ask for the user list:
cl.send(new xmpp.Element('iq', {from: jid, to: room_jid, type: 'get' }).c('query', { xmlns: 'http://jabber.org/protocol/disco#items' }));
then listen for iq-stanzas, parse them and populate an array with the data:
// Catching the requested user list
if(stanza.is('iq')){
// Fetching usernames from return data (data structure: http://xmpp.org/extensions/xep-0045.html#example-12)
var _items = stanza.getChild('query').getChildren('item');
var users = new Array();
for(var i = 0; i<_items.length; i++) {
// We are building an object here to add more data later
users[i] = new Object();
users[i]['name'] = _items[i].attrs.name;
}
console.log(util.inspect(users, {depth: null, colors: true}));
return;
}
This will provide you with a user list. To request unique JIDs you have to probe every user. To keep the list up to date, you should remove users when they leave and add + probe when they join.