I'm trying to build my own connector for the first time. I'm getting the following error:
Sorry, we failed to submit your credentials because there was an error in the connector in processing the credentials.
Here is my code so far:
function getAuthType() {
var cc = DataStudioApp.createCommunityConnector();
return cc.newAuthTypeResponse()
.setAuthType(cc.AuthType.USER_TOKEN)
.setHelpUrl('https://api-doc-help-url/authentication')
.build();
}
function validateCredentials(userName, token) {
var rawResponse = UrlFetchApp.fetch('https://api/v2/stuff/' + userName , {
method: 'GET',
headers: {
'Authorization': userName + ':' + token
},
muteHttpExceptions: true
});
return rawResponse.getResponseCode() === 200;
}
function isAuthValid() {
var userProperties = PropertiesService.getUserProperties();
var userName = userProperties.getProperty('dscc.username');
var token = userProperties.getProperty('dscc.token');
return validateCredentials(userName, token);
}
function setCredentials(request) {
var creds = request.userToken;
var username = creds.username;
var token = creds.token;
var validCreds = checkForValidCreds(username, token);
if (!validCreds) {
return {
errorCode: 'INVALID_CREDENTIALS'
};
}
var userProperties = PropertiesService.getUserProperties();
userProperties.setProperty('dscc.username', username);
userProperties.setProperty('dscc.token', token);
return {
errorCode: 'NONE'
};
}
I'm not sure how all this work exactly. I know that I need to pass the Authorization in the header with the following value userName:token
When deploying my manifest and clicking the Google Data Studio link I'm redirected to the select connector data studio interface. The error appear when I'm trying to submit Usernam and Token.
checkForValidCreds is not defined inside the function setCredentials. which is causing the error
Related
I've followed the guide error-handling for handling back an error to the users from the setCredentials method. However, even when using the provided example method to throw an error to the user, I still get the generic error on all my reports:
This is the default code I used for setting up the setCredentials function looker-studio auth connector.
function setCredentials(request) {
try {
Logger.log('setCredentials/try');
var creds = request.userPass;
var username = creds.username;
var password = creds.password;
var validCreds = validateCredentials(username, password).validCreds;
if (!validCreds) {
return {
errorCode: 'INVALID_CREDENTIALS'
};
};
var userProperties = PropertiesService.getUserProperties();
userProperties.setProperty('connector.username', username);
userProperties.setProperty('connector.password', password);
return {
errorCode: 'NONE'
};
} catch(e) {
Logger.log('setCredentials/catch');
DataStudioApp.createCommunityConnector()
.newUserError()
.setDebugText('Error setting credentials. Exception details: ' + e)
.setText('I want to show a custom error message')
.throwException();
}
}
This is the toast message I want to change
I tried to throw an error before returning INVALID_CREDENTIALS so it falls in the catch. However, I don't see changes in the text.
function setCredentials(request) {
try {
Logger.log('setCredentials/try');
var creds = request.userPass;
var username = creds.username;
var password = creds.password;
var validCreds = validateCredentials(username, password).validCreds;
if (!validCreds) {
throw new Error('Only to fall to the catch');
};
var userProperties = PropertiesService.getUserProperties();
userProperties.setProperty('connector.username', username);
userProperties.setProperty('connector.password', password);
return {
errorCode: 'NONE'
};
} catch(e) {
Logger.log('setCredentials/catch');
throw new Error('This message appears but it doesn't overwritte the default message');
}
}
(I have admin set to true)
If I throw again a new Error in the catch before the DataStudioApp newUserError I can see that 2 error toasts appear. A default one and mine, however this is not the intended result. What I want is to edit the default error message and I don't know if it's possible.
I'm having trouble figuring out why can't I see a prompt to enter API key. I can connect directly without any authentication. Why is the API key ignored?
auth.js file:
function getAuthType() {
return {
type: 'KEY'
};
}
function validateKey(key) {
var url = 'http://myapi.com/endpoint?api_key=' + key;
var options = {
"method": "post",
"contentType":"application/json"
};
var response = JSON.parse(
UrlFetchApp.fetch(url, options)
);
Logger.log(response.data.length > 0)
return response.data.length > 0;
}
function isAuthValid() {
var userProperties = PropertiesService.getUserProperties();
var key = userProperties.getProperty('dscc.key');
return validateKey(key);
}
function resetAuth() {
var userProperties = PropertiesService.getUserProperties();
userProperties.deleteProperty('dscc.key');
}
function setCredentials(request) {
var key = request.key;
var userProperties = PropertiesService.getUserProperties();
userProperties.setProperty('dscc.key', key);
return {
errorCode: 'NONE'
};
}
function isAdminUser() {
return false;
}
The Logger.log output:
I was using a http url. We've moved our API to https and the problem is solved. Data Studio doesn't show any error messages and skips the auth step. This is very strange.
Edit: A month later, while reviewing the document I noticed that Data Studio is already asking for us an https url.
Each prefix must use https://, not http://. source
I'm getting a "You do not have permission to view this directory or page." error when I try to LoginAsync with an access token and MobileServiceAuthenticationProvider.WindowsAzureActiveDirectory. This works with the equivalent form with MobileServiceAuthenticationProvider.MicrosoftAccount. I'm not sure why this isn't working. Is there a configuration I'm missing?
var msaProvider = await WebAuthenticationCoreManager.FindAccountProviderAsync(
"https://login.microsoft.com",
"https://login.microsoftonline.com/3dd13bb9-5d0d-dd2e-9d1e-7a966131bf85");
string clientId = "6d15468d-9dbe-4270-8d06-a540dab3252f";
WebTokenRequest request1 = new WebTokenRequest(msaProvider, "User.Read", clientId);
request1.Properties.Add("resource", "https://graph.microsoft.com");
WebTokenRequestResult result =
await WebAuthenticationCoreManager.RequestTokenAsync(request1);
if (result.ResponseStatus == WebTokenRequestStatus.Success)
{
var token = result.ResponseData[0].Token;
var token1 = new JObject
{
{ "access_token", token }
};
var user = await App.mobileServiceClient.LoginAsync(
MobileServiceAuthenticationProvider.WindowsAzureActiveDirectory, token1);
I was able to get MSAL.NET to work for this per code below. The key is the { resourceId + "/user_impersonation" } scope.
PublicClientApplication pca = new PublicClientApplication(clientId)
{
RedirectUri = redirectUri
};
string[] scopes = { resourceId + "/user_impersonation" };
var users = await pca.GetAccountsAsync();
var user = users.FirstOrDefault();
AuthenticationResult msalar = await pca.AcquireTokenAsync(
scopes, user, UIBehavior.ForceLogin, "domain_hint=test.net");
payload = new JObject
{
["access_token"] = msalar.AccessToken
};
mobileServiceClient.LoginAsync(MobileServiceAuthenticationProvider.WindowsAzureActiveDirectory, payload);
Reference: https://github.com/AzureAD/microsoft-authentication-library-for-dotnet/issues/660#issuecomment-433831737
I am following this auth0 tutorial for a react/express + jwt webpage.
Everything seems ok. Login/logout, accessing secure page all good.
Except I can bypass the login with a fake token. If I just go to jwt.io and generate a token that hasn't expired yet, I can access the secure page without actually logging in. What am I missing here?
This is the part of the code that handles authentication:
./utils/AuthService.js
const ID_TOKEN_KEY = 'id_token';
const ACCESS_TOKEN_KEY = 'access_token';
const CLIENT_ID = 'auht0 client id';
const CLIENT_DOMAIN = 'foo.eu.auth0.com';
const REDIRECT = 'http://localhost:3000/callback';
const SCOPE = 'openid profile ';
const AUDIENCE = 'https://foo.eu.auth0.com/api/v2/';
var auth = new auth0.WebAuth({
clientID: CLIENT_ID,
domain: CLIENT_DOMAIN
});
export function login() {
auth.authorize({
responseType: 'token id_token',
redirectUri: REDIRECT,
audience: AUDIENCE,
scope: SCOPE
});
}
export function logout() {
clearIdToken();
clearAccessToken();
browserHistory.push('/');
}
export function requireAuth(nextState, replace) {
if (!isLoggedIn()) {
replace({pathname: '/'});
}
}
export function getIdToken() {
return localStorage.getItem(ID_TOKEN_KEY);
}
export function getAccessToken() {
return localStorage.getItem(ACCESS_TOKEN_KEY);
}
function clearIdToken() {
localStorage.removeItem(ID_TOKEN_KEY);
}
function clearAccessToken() {
localStorage.removeItem(ACCESS_TOKEN_KEY);
}
// Helper function that will allow us to extract the access_token and id_token
function getParameterByName(name) {
let match = RegExp('[#&]' + name + '=([^&]*)').exec(window.location.hash);
return match && decodeURIComponent(match[1].replace(/\+/g, ' '));
}
// Get and store access_token in local storage
export function setAccessToken() {
let accessToken = getParameterByName('access_token');
localStorage.setItem(ACCESS_TOKEN_KEY, accessToken);
}
// Get and store id_token in local storage
export function setIdToken() {
let idToken = getParameterByName('id_token');
localStorage.setItem(ID_TOKEN_KEY, idToken);
}
export function isLoggedIn() {
const idToken = getIdToken();
return !!idToken && !isTokenExpired(idToken);
}
function getTokenExpirationDate(encodedToken) {
const token = decode(encodedToken);
if (!token.exp) { return null; }
const date = new Date(0);
date.setUTCSeconds(token.exp);
return date;
}
function isTokenExpired(token) {
const expirationDate = getTokenExpirationDate(token);
return expirationDate < new Date();
}
The part you've posted just stores / retrieves the token from the local storage.
The authentication itself is handled on the server side
secret: jwks.expressJwtSecret({
cache: true,
rateLimit: true,
jwksRequestsPerMinute: 5,
// YOUR-AUTH0-DOMAIN name e.g prosper.auth0.com
jwksUri: "https://{YOUR-AUTH0-DOMAIN}/.well-known/jwks.json"
}),
// This is the identifier we set when we created the API
audience: '{YOUR-API-AUDIENCE-ATTRIBUTE}',
issuer: '{YOUR-AUTH0-DOMAIN}',
algorithms: ['RS256']
});
The sever-side funtionality MUST check the JWT token signature. Without the private key you won't be able to generate a valid signature of the JWT token.
If you really can bypass the authentication, it means the server has serious security issue (the signature is not properly validated). Maybe for test/demo services it is not implemented.
In my project I am writing e2e tests in node.js and I have a test firebase I am using. So I create a token in node before each describe in the test runs and then I send it to the front end(angular.js) and then I use the authWithCustomToken function to authenticate the person.
The problem is for some reason it isn't even calling the function because I put a console.log statement in the callback and every time my code runs it enters the if $location.search condition but the console.log doesn't print out anything. I dont seem to know what the problem is.
var Firebase = require('firebase');
var FirebaseTokenGenerator = require('firebase-token-generator');
var rootRef = new Firebase('https://xxxxx');
var data = require('./data_helper.js');
rootRef.child('users').set(data.users[0]);
var credentials = {
nonAdmin: {
uid: 'google',
email: 'xxxx'
},
admin: {
uid: 'google',
email: 'xxxxx'
}
};
var logInAndThen = function(options) {
var secret = 'sdmdfmdsjwdsjwjwwewewe';
var tokenGenerator = new FirebaseTokenGenerator(secret);
var token = tokenGenerator.createToken(credentials[options.userType || 'admin']);
browser.get('/login?token=' + token);
var alertDiv = by.className('alert');
//browser.wait(function(){});
var waitOnFirebase = browser.wait(function() {
return browser.isElementPresent(alertDiv);
});
waitOnFirebase.then(function(data) {
console.log('-------', data);
options.cb(data);
});
};
module.exports = logInAndThen;
--------- FRONT END ANGULAR CODE PUT IN APPLICATION.RUN---------------------
if($location.search().token) {
console.log(Refs.root.toString());
Refs.root.authWithCustomToken($location.search().token, function(err, authData) {
console.log(err,authData);
}, {scope: 'email'});
}
I would appreciate it if someone could help me with this
Try getting the token like this (put this in your .run):
var loc = $location.path();
if(loc.search('login?token') > 0) {
token = loc.splice(13)
//now incorporate the 'token' into whatever auth functions you need to.
}
Not entirely sure if this is the most technically 'correct' way of grabbing the token, but it should work for you.