Spotify API 1.x get user session - spotify

Trying Spotify API 1.x.
My manifest
"Dependencies": {
"api": "1.20.1",
"views": "1.24.1"
}
Having problem getting the current session with the new spotify API. Session Docs
After a while I got the user information with this:
require(['$api/models','$api/models#User','$api/models#Session'], function(models) {
var user = models.User.fromURI('spotify:user:#');
user.load('username', 'name').done(function(u) {
userUid = u.identifier;
});
});
But the Session doesn't have the load method (getting throw error) and when looking at the models.Session I can't se any values??? :(

I changed on manifest.json API version to:
"api": "1.3.0"
Now I can get information from the session, for example my country:
var userCountry = models.session.country;

I find the Spotify documentation a bit misleading regarding Sessions. There are a number of things I have found out by trial-and-error and Googling, rather than from the docs.
Like #Backer says, change the API version to 1.3.0 (or higher, when available). Please note you have to restart Spotify for this to take effect.
Then you can access the Session object like this (here, "session" must be lower case):
models.session.load('product','connection','device','user').done(function(s){
console.log('sess:',s)
});
The User object will be part of this, but it won't be populated with properties unless you load them. Here is an example of retrieving a subset of properties from Session and User:
require([
'$api/models','$api/models#Session'
], function(models) {
app.user = {};
models.session.load('product','connection','device','user').done(function(sess){
sess.user.load('name', 'username', 'subscribed').done(function(user){
app.user.name = user.name; // string
app.user.username = user.username; // string
app.user.subscribed = user.subscribed; // boolean
});
app.user.connection = sess.connection; // string
app.user.country = sess.country; // string ISO-2
app.user.device = sess.device; // string
app.user.language = sess.language; // string ISO-2
app.user.product = sess.product; // string
});
});
The entire Session object:
Session
_done: 65535
_listening: true
_ob: Object
_obcount: 1
_requestArgs: Array[0]
_requestName: "session_event_wait"
capabilities: Object
connecting: false
connection: "wlan"
country: "SE"
developer: true
device: "desktop"
incognito: false
language: "en"
online: true
product: "open"
resolution: 1
streaming: "enabled"
testGroup: 000
user: User
_done: 255
currentUser: true
identifier: "longcodehere"
image: "http://profile-images.scdn.co/artists/default/anotherlongcodehere"
images: Array[2]
name: "My Name"
subscribed: false
uri: "spotify:user:#"
username: "myusername"
__proto__: c
__proto__: c

Related

Webauthn - Windows Hello authenticatorSelection is not working

I am just developing a sample node js application to play around webauthn on Windows 10.
challenge: challenge,
rp: {
name: "Example CORP",
id : "localhost"
},
user: {
id: new Uint8Array(16),
name: "jdoe#example.com",
displayName: "John Doe"
},
pubKeyCredParams: [
{
type: "public-key",
alg: -7
}
],authenticatorSelection: {
authenticatorAttachment: "platform" //cross-platform is working fine
},
timeout: 60000
};
const credential = navigator.credentials.create({
publicKey: publicKey
});
I do get back the following error and I am not seeing any modal window of Windows Hello.
login:32 publicKey.authenticatorSelection.userVerification was not set to any value in Web Authentication navigator.credentials.create() call. This defaults to 'preferred', which is probably not what you want. If in doubt, set to 'discouraged'. See https://chromium.googlesource.com/chromium/src/+/master/content/browser/webauth/uv_preferred.md for details
Are there any additional params I am missing ?
--
Siva
You didn't define userVerification property in the authenticatorSelection object.
from the W3.org:
Let userVerification be the effective user verification requirement for the assertion:
is set to required
Let userVerification be true.
is set to discouraged
Let userVerification be false.
is set to preferred
If the authenticator
is capable of user verification
Let userVerification be true.
if the authenticator not capable of user verification
Let userVerification be false.
authenticatorSelection: {
authenticatorAttachment: "platform",
userVerification: "required"
},

YubiKey + Webauth: userHandle is always null

When I authenticate using WebAuthn and my YubiKey, the response.userHandle property is always null. That is the user id and displayName that I registered the credential with does not get returned. Is this becuase of something I am doing wrong during the registration / authentication process:
async function register() {
const publicKeyCredentialCreationOptions = {
challenge: Uint8Array.from("this-is-a-test", (c) => c.charCodeAt(0)),
rp: {
name: "Webauthn Test",
id: "localhost",
},
user: {
id: Uint8Array.from("a1b2c3d4e5f6", (c) => c.charCodeAt(0)),
name: "just-a-test",
displayName: "MrUser",
},
pubKeyCredParams: [{ alg: -7, type: "public-key" }],
authenticatorSelection: {
authenticatorAttachment: "cross-platform",
},
timeout: 60000,
attestation: "direct",
};
const credential = await navigator.credentials.create({
publicKey: publicKeyCredentialCreationOptions,
});
}
This is the code I use to authenticate:
async function authenticate() {
const publicKeyCredentialRequestOptions = {
challenge: Uint8Array.from("test", (c) => c.charCodeAt(0)),
allowCredentials: [
{
id: credentialId,
type: "public-key",
transports: ["usb", "ble", "nfc"],
},
],
timeout: 60000,
};
const assertion = await navigator.credentials.get({
publicKey: publicKeyCredentialRequestOptions,
});
console.log(assertion);
}
What I end up with is:
{
rawId: ArrayBuffer(64),
id: "U-nitqhlORmmdltp7TLO3i18KNoWsSebFyrtc3OIRvcktvwlz-dJZCA1_1gxXrNHzqReU7xGAHdfVP75N2aJSw",
response: {
authenticatorData: ArrayBuffer(37) {}
clientDataJSON: ArrayBuffer(101) {}
signature: ArrayBuffer(71) {}
userHandle: null
}
type: "public-key"
}
As you can see: userHandle is null. Can anyone tell me why?
The userHandle can be null depending on which type of WebAuthn credential the relying party requested to be created.
The default WebAuthn behavior will create a non-discoverable credential and the userHandle returned in the assertion will be null. No data is stored on the authenticator for this type of credential so there is nothing to return.
To create a WebAuthn client-side discoverable credential, a.k.a. resident key, you must set the requireResidentKey member to true. This will store credential data on the authenticator and will return the userHandle in the assertion. Refer to the AuthenticatorSelectionCriteria in the W3C WebAuthn spec for the details.
Here is an example:
authenticatorSelection: {
authenticatorAttachment: "cross-platform",
requireResidentKey: true
},
See Yubico's WebAuthn Dev Guide to learn more about resident keys and the userHandle.
I have tried to understand what you are dealing with. I played with https://u2f.bin.coffee/ to get a feeling for the data flow. As a result of authentication I have received a response like:
Got response:
{
"keyHandle": "F74UNCdNv1d43zw7hqxYgkjR3O6dcevopiSb3jrcB3rMFRUM486LbsVExJD0R3ESC5MCb3zeFGdxvS3ksZ7sCA",
"clientData": "eyJ0eXAiOiJuYXZpZ2F0b3IuaWQuZ2V0QXNzZXJ0aW9uIiwiY2hhbGxlbmdlIjoiTXpPTjhXRHpvSDlhZHU0bTk5YWF0ZyIsIm9yaWdpbiI6Imh0dHBzOi8vdTJmLmJpbi5jb2ZmZWUiLCJjcm9zc09yaWdpbiI6ZmFsc2UsImV4dHJhX2tleXNfbWF5X2JlX2FkZGVkX2hlcmUiOiJkbyBub3QgY29tcGFyZSBjbGllbnREYXRhSlNPTiBhZ2FpbnN0IGEgdGVtcGxhdGUuIFNlZSBodHRwczovL2dvby5nbC95YWJQZXgifQ",
"signatureData": "AQAAAAUwRAIgEqi5POKKUraU97W3vbfn34DSWqXwiZwEi5g9QPPtS6MCIBbLYW1_b3aRjHQivSRZQUAfBobx6CZnQ0_VVvuu1LJJ"
}
Now I assume the keyHandle here is your authenticatorData, the clientData here is your clientDataJSON and that signatureData is your signature. Whatever this userHandle is you are missing, it does not seem to be required.
Look at this picture too:
If the userHandle were the handle, the authentication would not work with a null value. But it does if I understand your example correctly.
So I believe you are dealing with a field that is reserved for future purposes or other flows than that which you need at the moment.

My mongodb/mongoose query conditions exclude themselves. Can you help me fixing that?

When user submits a content into my node.js app, he puts four things: start_date, his facebook username, text content and a flag, whether the content should be visible to everyone or only to his facebook friends.
When user fetches content, he can get the public content from other users and the content from his facebook friends (even when they submitted their content only for their friends).
I created a mongoose query that works correctly in this scenario. I do it like this:
var query = Test.find({})
if(startDate != undefined) {
var startDate = new Date(req.param('startDate'));
query = query.where('updated_at').gte(startDate);
}
if (friends != undefined) {
var friendsSplitted = friends.split(",");
for(var i=0; i<friendsSplitted.length; i++) {
query = query.or([{ 'facebook_username': friendsSplitted[i] }]);
}
}
query = query.where('hidden').equals(false);
if (publicFlag != undefined && publicFlag === "true") {
query = query.or({friends_only: false});
}
With that code above, when user queries only for content from his friends (that might be private or public), he POSTs startDate, an array of his friends, a flag hidden set to false and publicFlag set to false. A query is constructed:
Mongoose: test.find({ updated_at: { '$gte': new Date("Sat, 15 Oct 2011
00:00:00 GMT")}, hidden: false, '$or': [ { facebook_username:
'XXXXXXXXXXXXXX' }, { facebook_username: 'YYYYYYYYYYYYYYY' } ] }) {
fields: undefined }
User can also query not only his friends (private or public) content, but also public content of everyone else. For that, he POSTs startDate, an array of his friends, a flag hidden set to false and publicFlag set to true. A query is constructed:
Mongoose: test.find({ updated_at: { '$gte': new Date("Sat, 15 Oct 2011
00:00:00 GMT")}, hidden: false, '$or': [ { facebook_username:
'XXXXXXXXXXXXXX' }, { facebook_username: 'YYYYYYYYYYYYYYY' }, {
friends_only: false } ] }) { fields: undefined }
Above code works fine.
I want to add another case, when user can fetch content with specific hashtags.
Basically, when user selects this option, he can see other users' content that only includes those hashtags. That content though is his fb friends posts (private or public) and other people's posts (that is public).
For that I thought about adding this condition to my original node.js code:
if (hashtagsInput != undefined) {
var hashtags = hashtagsInput.split(",");
for(var i=0; i<hashtags.length; i++) {
query = query.or([{ 'hashtags': hashtags[i] }]);
}
}
but this solution does not work properly.
When user wants to fetch (private and public) content of his friends and public content of others - but only that one that contains one of the hashtags - he POSTs startDate, an array of his friends, a flag hidden set to false, publicFlag set to true and an array of his hashtags. When he does it, it creates a query:
Mongoose: test.find({ updated_at: { '$gte': new Date("Sat, 15 Oct 2011
00:00:00 GMT")}, '$or': [ { hashtags: 'test1' }, { hashtags: 'test2'
}, { facebook_username: 'XXXXXXXXXXXX' }, { facebook_username:
'YYYYYYYYYYYYYYYYYY' }, { friends_only: false } ], deleted: false }) {
fields: undefined }
Basically I want to limit the general query only to specific hashtags.
This query does not return correct data. Now I'm not a mongoose specialist and I've spent couple hours yesterday of trying to figure it out, but I think the problem is that it returns the data that either contains one of the hashtags OR its author is one of my facebook friend.
I would like to fix this query so that - in case user POSTs hashtags - it fetches the content that is public and private of his friends and public of everyone, but only that one that contains at least one of those hashtags. And when user does not provide the hashtags - it should work as it was, fetching all private and public content of his friends and public of everyone.
Can you please help me fix my node.js code so that it creates a correct mongoose query?
The below code will work as you want
var query= {};
query.$and = [];
// and condition on start date
if(startDate != undefined) {
var startDate = new Date(req.param('startDate'));
query.$and.push({"updated_at":{$gte: startDate}});
}
// and condition on hastags
if (hashtagsInput != undefined) {
var hashtags = hashtagsInput.split(",");
query.$and.push({"hashtags":{$in: hashtags}});
}
// and condition on hidden
query.$and.push({"hidden":false});
// creating a OR condition for facebook friends and public flag
var friend_query = {};
friend_query.$or = [];
if (friends != undefined) {
var friendsSplitted = friends.split(",");
friend_query.$or.push({"facebook_username":{$in: friendsSplitted}});
}
if (publicFlag != undefined && publicFlag === "true") {
friend_query.$or.push({friends_only: false});
}
//Merging facebook friend condition with other condition with AND operator.
query.$and.push(friend_query);
var finalquery = Test.find(query)
Sorry I use to create mongodb query directly in Json format, it is little different than you are doing. This will work as you mentioned you want in question.
If it don't solve your problem tell me.

loopbackjs: Attach a model to different datasources

I have defined several models that use a Datasource "db" (mysql) for my environment.
Is there any way to have several datasources attached to those models, so I would be able to perform REST operations to different databases?
i.e:
GET /api/Things?ds="db"
GET /api/Things?ds="anotherdb"
GET /api/Things (will use default ds)
As #superkhau pointed above, each LoopBack Model can be attached to a single data-source only.
You can create (subclass) a new model for each datasource you want to use. Then you can either expose these per-datasource models via unique REST URLs, or you can implement a wrapper model that will dispatch methods to the correct datasource-specific model.
In my example, I'll show how to expose per-datasource models for a Car model that is attached to db and anotherdb. The Car model is defined in the usual way via common/models/car.json and common/models/car.js.
Now you need to define per-datasource models:
// common/models/car-db.js
{
"name": "Car-db",
"base": "Car",
"http": {
"path": "/cars:db"
}
}
// common/models/car-anotherdb.js
{
"name": "Car-anotherdb",
"base": "Car",
"http": {
"path": "/cars:anotherdb"
}
}
// server/model-config.json
{
"Car": {
"dataSource": "default"
},
"Car-db": {
"dataSource": "db"
},
"Car-anotherdb": {
"dataSource": "anotherdb"
}
}
Now you have the following URLs available:
GET /api/Cars:db
GET /api/Cars:anotherdb
GET /api/Cars
The solution outlined above has two limitations: you have to define a new model for each datasource and the datasource cannot be selected using a query parameter.
To fix that, you need a different approach. I'll again assume there is a Car model already defined.
Now you need to create a "dispatcher".
// common/models/car-dispatcher.json
{
"name": "CarDispatcher",
"base": "Model", //< important!
"http": {
"path": "/cars"
}
}
// common/models/car-dispatcher.js
var loopback = require('loopback').PersistedModel;
module.exports = function(CarDispatcher) {
Car.find = function(ds, filter, cb) {
var model = this.findModelForDataSource(ds);
model.find(filter, cb);
};
// a modified copy of remoting metadata from loopback/lib/persisted-model.js
Car.remoteMethod('find', {
isStatic: true,
description: 'Find all instances of the model matched by filter from the data source',
accessType: 'READ',
accepts: [
{arg: 'ds', type: 'string', description: 'Name of the datasource to use' },
{arg: 'filter', type: 'object', description: 'Filter defining fields, where, orderBy, offset, and limit'}
],
returns: {arg: 'data', type: [typeName], root: true},
http: {verb: 'get', path: '/'}
});
// TODO: repeat the above for all methods you want to expose this way
Car.findModelForDataSource = function(ds) {
var app = this.app;
var ds = ds && app.dataSources[ds] || app.dataSources.default;
var modelName = this.modelName + '-' + ds;
var model = loopback.findModel(modelName);
if (!model) {
model = loopback.createModel(
modelName,
{},
{ base: this.modelName });
}
return model;
};
};
The final bit is to remove Car and use CarDispatcher in the model config:
// server/model-config.json
{
"CarDispatcher": {
dataSource: null,
public: true
}
}
By default, you can only attach data sources on a per-model basis. Meaning you can attach each model to a different data source via datasources.json.
For your use case, you will to add a remote hook to each endpoint you want for multiple data sources. In your remote hook, you will do something like:
...
var ds1 = Model.app.dataSources.ds1;
var ds2 = Model.app.dataSources.ds2;
//some logic to pick a data source
if (context.req.params...
...
See http://docs.strongloop.com/display/LB/Remote+hooks for more info.
For anyone still looking for a working answer to this, the solution for switching databases on the fly was to write a middleware script that examined the request path and then created a new DataSource connector, passing in a variable based on the req.path variable. For example, if the request path is /orders, then "orders" as a string would be saved in a variable, then we attached a new Datasource, passing in that variable for "orders". Here's the complete working code.
'use strict';
const DataSource = require('loopback-datasource-juggler').DataSource;
const app = require('../server.js');
module.exports = function() {
return function datasourceSelector(req, res, next) {
// Check if the API request path contains one of our models.
// We could use app.models() here, but that would also include
// models we don't want.
let $models = ['offers', 'orders', 'prducts'];
// $path expects to be 'offers', 'orders', 'prducts'.
let $path = req.path.toLowerCase().split("/")[1];
// Run our function if the request path is equal to one of
// our models, but not if it also includes 'count'. We don't
// want to run this twice unnecessarily.
if (($models.includes($path, 0)) && !(req.path.includes('count'))) {
// The angular customer-select form adds a true value
// to the selected property of only one customer model.
// So we search the customers for that 'selected' = true.
let customers = app.models.Customer;
// Customers.find() returns a Promise, so we need to get
// our selected customer from the results.
customers.find({"where": {"selected": true}}).then(function(result){
// Called if the operation succeeds.
let customerDb = result[0].name;
// Log the selected customer and the timestamp
// it was selected. Needed for debugging and optimization.
let date = new Date;
console.log(customerDb, $path+req.path, date);
// Use the existing veracore datasource config
// since we can use its environment variables.
let settings = app.dataSources.Veracore.settings;
// Clear out the veracore options array since that
// prevents us from changing databases.
settings.options = null;
// Add the selected customer to the new database value.
settings.database = customerDb;
try {
let dataSource = new DataSource(settings);
// Attach our models to the new database selection.
app.models.Offer.attachTo(dataSource);
app.models.Order.attachTo(dataSource);
app.models.Prduct.attachTo(dataSource);
} catch(err) {
console.error(err);
}
})
// Called if the customers.find() promise fails.
.catch(function(err){
console.error(err);
});
}
else {
// We need a better solution for paths like '/orders/count'.
console.log(req.path + ' was passed to datasourceSelector().');
}
next();
};
};

Serialize ids when saving emberjs model

I have an emberjs application backed by a nodejs server and mongodb. Currently my database is sending documents with an '_id' field. I have the followign code to force Ember to treat '_id' as the primary key:
App.ApplicationSerializer = DS.RESTSerializer.extend({
primaryKey: '_id'
});
On the other hand i have two models related by a 'hasMany' relationship as such:
App.Player = DS.Model.extend({
name: DS.attr('string'),
thumbLink: DS.attr('string'),
activeGame: DS.belongsTo('game', { async: true }),
email: DS.attr('string'),
firstName: DS.attr('string'),
lastName: DS.attr('string'),
admin: DS.attr('boolean')
});
App.Game = DS.Model.extend({
name: DS.attr('string'),
active: DS.attr('boolean'),
players: DS.hasMany('player', { async: true })
});
The problem is that when i try to save the model ( this.get('model').save() )on my controller the ids are not serialized and the result is ember sending the following:
{"game":{"name":"Indie/Rock","active":false,"players":[],"_id":"53cbbf43daa978983ee0b101"}}
As you can see the players array is empty, and as a result, the server is saving that empty array which in fact is not correct. I am aware that it is possible to use { embedded: true } on the models and return the models with embedded documents from the server, but i want to preserve the async feature.
I have tried to extend the game serializer from EmbeddedRecordsMixing as the following:
App.GameSerializer = DS.ActiveModelSerializer
.extend(DS.EmbeddedRecordsMixin)
.extend({
attrs: {
players: {serialize: 'ids', deserialize: 'ids'},
}
});
But when i do so i get the following error from ember even though the ApplicationSerializer is suppossedly telling Ember to user _id as primary key:
Assertion Failed: Error: Assertion Failed: You must include an `id` for App.Game in a hash passed to `push`
My question is if it is possible to maintain the async features of ember-data while being able to serialize the document with the ids on it's relation and using _id as a primary key.
Thank you.
Ember Data is stupid in this aspect, if it's a ManyToOne relationship, it only includes the id from the belongsTo side. Honestly I've had it on my bucket list to submit a PR, but time is limited.
https://github.com/emberjs/data/commit/7f752ad15eb9b9454e3da3f4e0b8c487cdc70ff0#commitcomment-4923439
App.ApplicationSerializer = DS.RESTSerializer.extend({
serializeHasMany: function(record, json, relationship) {
var key = relationship.key;
var payloadKey = this.keyForRelationship ? this.keyForRelationship(key, "hasMany") : key;
var relationshipType = RelationshipChange.determineRelationshipType(record.constructor, relationship);
if (relationshipType === 'manyToNone' || relationshipType === 'manyToMany'
|| relationshipType === 'manyToOne') { // This is the change
json[payloadKey] = get(record, key).mapBy('id');
// TODO support for polymorphic manyToNone and manyToMany relationships
}
},
});

Resources