PublicKeyCredential not possible to serialize - javascript

I am implementing FIDO2(WebAuthn) in a Angular application.
I have gotten the PublicKeyCredentialCreationOptions object and seccessfullt register.
But after calling
let response = await navigator.credentials.create({'publicKey': myPublicKeyCredentialCreationOption })
I try to send the response to the server.. But this fails.
When I tried to look at the object in the browser using
console.log(JSON.stringify(response))
I get
{}
as output (?..) but when doing
console.log(response)
I get a object with values in the console...
How should the object get serialized to send to the server?

PublicKeyCredential objects contains ArrayBuffer objects that cannot be serialized as JSON. You could base64 encode these values in your Angular app and decode on the server to get the same byte array back. A helper library to do exactly that for WebAuthn exists: https://github.com/github/webauthn-json

Here's a very simple example for anyone who needs it:
function bufferToBase64url (buffer) {
// modified from https://github.com/github/webauthn-json/blob/main/src/webauthn-json/base64url.ts
const byteView = new Uint8Array(buffer);
let str = "";
for (const charCode of byteView) {
str += String.fromCharCode(charCode);
}
// Binary string to base64
const base64String = btoa(str);
// Base64 to base64url
// We assume that the base64url string is well-formed.
const base64urlString = base64String.replace(/\+/g, "-").replace(
/\//g,
"_",
).replace(/=/g, "");
return base64urlString;
}
...
create publicKeyCredentialCreationOptions
...
navigator.credentials.create({
publicKey: publicKeyCredentialCreationOptions
}).then(credential => {
// credential created
// console.log(credential); <-- check what is output to see what you need to call bufferToBase64url(credential.<...>) on down below
// convert credential to json serializeable
const serializeable = {
authenticatorAttachment: credential.authenticatorAttachment,
id: credential.id,
rawId: bufferToBase64url(credential.rawId),
response: {
attestationObject: bufferToBase64url(credential.response.attestationObject),
clientDataJSON: bufferToBase64url(credential.response.clientDataJSON)
},
type: credential.type
};
const serialized = JSON.stringify(serializeable);
console.log(serialized);
}).catch(err => {
// an error occurred
console.error(err);
});

Related

Data URI to JSON in Javascript?

I am facing a problem where my server app gets a JSON's DataURI and I would like to parse it into a JSON again. How could I do this? I tried different things but nothings seems to work. I tried simply parsing it or encodeURI(data); but still I can't get the original JSON.
This is the Data URI:
data:application/json;base64,ew0KICAgICJtYWx0X3R5cGUiOiAibG9nIiwNCiAgICAibWFsdF9kYXRhIjogIldvdywgdSByIGFsbW9zdCB0aGVyZSA6TyINCn0=
I tried this to encode it too:
var data = 'data:application/json;base64,ew0KICAgICJtYWx0X3R5cGUiOiAibG9nIiwNCiAgICAibWFsdF9kYXRhIjogIldvdywgdSByIGFsbW9zdCB0aGVyZSA6TyINCn0=';
Buffer.from(data.toString('utf8'), 'base64').toString('ascii')
But I get this if I log it on console: u+Zje F- J'm+k0P"&VGEwGR#"&Fvr"#P"&VGEvFF#"%vwrBR"FVw7BFW&R$r P'
The data URI is JSON encoded in Base64. There are two steps to this:
Decode the Base64 (for instance, with the atob function), and
Parse the resulting JSON
For instance (on a browser):
const dataURI = "data:application/json;base64,ew0KICAgICJtYWx0X3R5cGUiOiAibG9nIiwNCiAgICAibWFsdF9kYXRhIjogIldvdywgdSByIGFsbW9zdCB0aGVyZSA6TyINCn0=";
// 29 = length of "data:application/json;base64,"
const json = atob(dataURI.substring(29));
const result = JSON.parse(json);
console.log(result);
Your use of Buffer in your question suggests to me that you may be using Node.js. If so, you'd replace the call to atob with Buffer.from(data, 'base64').toString():
const dataURI = "data:application/json;base64,ew0KICAgICJtYWx0X3R5cGUiOiAibG9nIiwNCiAgICAibWFsdF9kYXRhIjogIldvdywgdSByIGFsbW9zdCB0aGVyZSA6TyINCn0=";
// 29 = length of "data:application/json;base64,"
const json = Buffer.from(dataURI.substring(29), "base64").toString();
const result = JSON.parse(json);
console.log(result);
If you don't mind changing the context into an asynchronous one you could use fetch() to parse the recourse. fetch() is normally used with URLs, but works with data URIs as well (in most browsers).
const dataURI = "data:application/json;base64,ew0KICAgICJtYWx0X3R5cGUiOiAibG9nIiwNCiAgICAibWFsdF9kYXRhIjogIldvdywgdSByIGFsbW9zdCB0aGVyZSA6TyINCn0=";
(async function () {
const response = await fetch(dataURI);
const data = await response.json();
console.log(data);
})();
If you are already using a library to simplify network requests, you could use them as well.
Examples:
jQuery:
const dataURI = "data:application/json;base64,ew0KICAgICJtYWx0X3R5cGUiOiAibG9nIiwNCiAgICAibWFsdF9kYXRhIjogIldvdywgdSByIGFsbW9zdCB0aGVyZSA6TyINCn0=";
(async function() {
const data = await $.getJSON(dataURI);
console.log(data);
})();
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
axios:
const dataURI = "data:application/json;base64,ew0KICAgICJtYWx0X3R5cGUiOiAibG9nIiwNCiAgICAibWFsdF9kYXRhIjogIldvdywgdSByIGFsbW9zdCB0aGVyZSA6TyINCn0=";
(async function() {
const response = await axios.get(dataURI);
console.log(response.data);
})();
<script src="https://cdnjs.cloudflare.com/ajax/libs/axios/0.21.0/axios.min.js"></script>

Issues with identifying and encoding urls

I'm having issues with parsing/manipulating URI:
Problem Statement:
I want to encode f[users.comma] and return it.
[Case-1] I get an url from backend service, encode f[users.comma] and return it.
[Case-2] I get an url from backend service and f[users.comma] is already encoded. So don't double encode and return it.
Expected Output:
`/demo/bigquery/order_items?fields=users.email&f[users.comma]=%22Abbeville%2C+Georgia%22`
Code:
const encodedExample = `/demo/bigquery/order_items?fields=users.email&f[users.comma]=%22Abbeville%2C+Georgia%22` // the last param is encoded
const regularExample2 = `/demo/bigquery/order_items?fields=users.email&f[users.comma]="Abbeville, Georgia"` //
const specialEncode = (url) => {
for (let queryParam of urlObj) {
const [urlKey, urlValue] = queryParam
// Check to see if url contains f[users.comma]
if (urlKey.includes('f[')) {
urlObj.set(urlKey, encodeURI(urlValue))
}
}
return urlObj.toString() // doesn't seem to work
}
I feel like I am going offroad with my approach. I'd appreciate some help here.
Since the backend service returns an encoded or decode url
We can first decode the url from the backend service (this won't produce any exceptions if url is already encoded)
const encodedExample = `/demo/bigquery/order_items?fields=users.email&f[users.comma]=%22Abbeville%2C+Georgia%22` // the last param is encoded
const regularExample2 = `/demo/bigquery/order_items?fields=users.email&f[users.comma]="Abbeville, Georgia"`
const specialEncode = (url) => {
let decodedUrl = decodeURI(url);
let encodedUrl = encodeURI(decodedUrl);
// fix "f[users.comma]" because encodeURI will encode the [ and ] as well
encodedUrl = encodedUrl.replace("f%5Busers.comma%5D", "f[users.comma]")
console.log(encodedUrl);
return encodedUrl;
}
specialEncode(encodedExample); // logs and returns: /demo/bigquery/order_items?fields=users.email&f[users.comma]=%22Abbeville%252C+Georgia%22
specialEncode(regularExample2); // logs and returns: /demo/bigquery/order_items?fields=users.email&f[users.comma]=%22Abbeville%252C+Georgia%22
The code above works fine for both encoded and decoded urls

converting data url to a format that I can use to send to my backend web api and store in database

I am currently getting drag and drop / uploaded images as a data url and displaying them with that url.
What I am now trying to do is send those uploaded images to my backend web api using ASP.Net core to store then in a sqlite database this is a requirement for my application.
Currently I am converting the data url to an arraybuffer using the following code.
async srcToFile(context, asset) {
const files = asset[0].files.fileList;
let results = [];
for (let i = 0; i < files.length; i++) {
const file = files[i];
const data = file.data;
const name = file.name;
const mimeType = file.type;
await fetch(data)
.then(function(res) {
const r = res.arrayBuffer();
console.warn('resource ', r);
return r;
})
.then(function(buf) {
console.warn('buffer: ', [buf]);
const fileData = {data:[buf], name:name, type:mimeType};
results.push(fileData);
console.warn('results of file: ', fileData);
});
}
console.warn(results);
return results;
}
then I put it in an data object to send to my server via axios this is what that data object looks like
const data = {
Name: asset[0].name,
Detail: asset[0].detail,
Files: asset[0].files.fileList
};
When I console out the Files it shows there is Arraybuffer data in it. But when I send it to my server it looks like that data is stripped out of the header call. Cause when I look at the header I no longer have that data in there and I cannot figure out why that is happening.
this is my axios call.
axios.post('https://localhost:5001/api/Assets', data)
.then(res => console.log(res))
.catch(error => console.log(error));
and my back end web api post controller
public async Task<ActionResult> PostAsset([FromBody] AssetSaveRequest request,[FromForm] List<IFormFile> files)
{
foreach (var file in files)
{
if (file.Length > 0)
{
using (var ms = new MemoryStream())
{
file.CopyTo(ms);
var fileBytes = ms.ToArray();
string s = Convert.ToBase64String(fileBytes);
// act on the Base64 data
}
}
}
var assetCreationDto = new AssetCreationDto(request);
//var assetCreationDto = "";
try
{
var asset = _mapper.Map<Asset>(assetCreationDto);
_context.Assets.Add(asset);
//await _context.SaveChangesAsync();
var assetDto = _mapper.Map<AssetDto>(asset);
return CreatedAtAction("GetAsset", new {assetDto.Id}, assetDto);
}
catch (DbUpdateException dbe)
{
var errorCode = ((Microsoft.Data.Sqlite.SqliteException) dbe.InnerException).SqliteErrorCode;
switch (errorCode)
{
case 19:
Console.WriteLine(((Microsoft.Data.Sqlite.SqliteException)dbe.InnerException).Message);
break;
default:
Console.WriteLine("Something went wrong");
break;
}
}
catch (Exception e)
{
Console.WriteLine(e);
throw;
}
return null;
}
That of which I don't know is working because I never get the file data, I do how ever get the Name and the details which come in fine.
I am looking for advice on what I should do here to get this to work. I have tried converting the arraybuffer to base64 string but that does not come out right any help and suggestions would be great to get me back on track with this project .
UPDATE:
I have modified my srcToFile code to give me a file, now I am using axios to send the file and data to the backend working with one file at this time and all im getting in the header now is [object object]. I've tried JSON.stringify on my data like so
const data = JSON.stringify({
Name: asset[0].name,
Detail: asset[0].detail,
Files: asset[0].files.fileList
});
It stringify's the name and detail but wipes out the file and I get nothing on the backend.
I have tested with postman and made several successful posts. but I can't seem to get the correct data from my Vue front end.
that is where I am at now. any suggestions always helps

Not returning anything base64 decode

I try to decrypt a file with crypto-js (in this file there is a long string of encrypted base64).
but I don't get anything back the file is empty and the log too.
const fs = require("fs");
const CryptoJS = require("crypto-js");
fs.writeFile("2pac.txt", decode(), (err) => {
if (err) throw err;
// success case, the file was saved
console.log("Lyric saved!");
});
function decode() {
// INIT
const encoded = fs.readFileSync("./base64.txt", { encoding: "base64" });
// PROCESS
const decoded = CryptoJS.enc.Utf8.stringify(encoded); // decode encodedWord via Utf8.stringify() '75322541'
console.log(decoded);
return decoded;
}
In the console.log I get the test but I don't get anything(even undefined).
Replace this line:
const decoded = CryptoJS.enc.Utf8.stringify(encoded);
with:
const decoded = CryptoJS.enc.Utf8.stringify(CryptoJS.enc.Base64.parse(encoded));
EDIT:
Reading base64 data from file is another problem. The data imported from the file with the encoding option set to base64 does guarantee a string instead of a buffer but it expects the input to be utf-8 encoding the base64 string again (double encoding).
To fix this, change the following:
const encoded = fs.readFileSync("./base64.txt", { encoding: "base64" });
to:
const encoded = fs.readFileSync("./base64.txt").toString();

How to convert JSON string sent from a server into a JavaScript object

I would like to be able to convert the JSON string sent from the server into a JavaScript object on a HMTL page. The raw JSON string data is being displayed, but I would rather display it as a JavaScript object instead.
case '/get_list':
if (req.method == 'POST') {
console.log("POST");
var body = '';
req.on('data', function(data) {
body += data;
console.log("Partial body: " + body);
});
req.on('end', async function() {
console.log("Body: " + body);
var json = JSON.parse(body)
console.log("name is " + json.name) // get name
const {
Client
} = require('pg');
const connectionString = 'postgresql://postgres:password#localhost/app';
const client = new Client({
connectionString: connectionString,
});
await client.connect(); // create a database connection
console.log("user input is " + json.name1);
//Returns the result from the database in a JSON string onto the HTML page
const res3 = await client.query('SELECT name, studentno, proname FROM applications WHERE name =$1 LIMIT 1', [json.name1]);
await client.end();
// json = res2.rows;
json = res3.rows;
var obj = JSON.parse(res3.rows);
var json_str_new = JSON.stringify(json); //working
console.log(obj);
console.log(json_str_new);
res.end(json_str_new);
});
}
break;
Actual results
{"name":"jake","studentno":10001212,"proname":"asdasdas"}
Expected/required results
{
name: 'jake',
studentno: 10001212,
proname: 'asdasdas'
}
If you're planning on using the JSON for anything, as in traversing and reading the data from that, then JSON.parse() is exactly what you need. Pretty-printing is something only useful for humans, so unless you're exclusively using the output for human consumption, the result you have should be fine.
However, if you are going to just show the data, then I would recommend just formatting the output into some HTML/CSS display.
But assuming you are planning on using the data for something, as mentioned before and by others, JSON.parse() is all you need to generate a JS object.

Categories

Resources