How to turn on Autorization? - admin-on-rest

"By default, an admin-on-rest app doesn’t require authentication".
I have written an application with AOR and Loopback API, etc, and it works well. Except for one thing, I can't turn on turn on authentication. Any username/password will work, just like in the Demo.
From what I can see all required components load, AuthClient etc., Loopback is configured and is waiting for user authorization requests but never gets any.
I copy/pasted a lot of Demo's parts...
Any hints please?
I use the unchanged authClient from kimkha aor loopback
import storage from './storage';
export const authClient = (loginApiUrl, noAccessPage = '/login') => {
return (type, params) => {
if (type === 'AUTH_LOGIN') {
const request = new Request(loginApiUrl, {
method: 'POST',
body: JSON.stringify(params),
headers: new Headers({ 'Content-Type': 'application/json' }),
});
return fetch(request)
.then(response => {
if (response.status < 200 || response.status >= 300) {
throw new Error(response.statusText);
}
return response.json();
})
.then(({ ttl, ...data }) => {
storage.save('lbtoken', data, ttl);
});
}
if (type === 'AUTH_LOGOUT') {
storage.remove('lbtoken');
return Promise.resolve();
}
if (type === 'AUTH_ERROR') {
const { status } = params;
if (status === 401 || status === 403) {
storage.remove('lbtoken');
return Promise.reject();
}
return Promise.resolve();
}
if (type === 'AUTH_CHECK') {
const token = storage.load('lbtoken');
if (token && token.id) {
return Promise.resolve();
} else {
storage.remove('lbtoken');
return Promise.reject({ redirectTo: noAccessPage });
}
}
return Promise.reject('Unkown method');
};
};

Related

Download an image to React Native from a Laravel server?

I am looking to download an image stored on a server into my React Native app.
I had a function that looked like this:
public function image(Request $request, $id)
{
$company = Company::find($id);
$filePath = storage_path() . '/app/' . $company->image;
return response()->file($filePath);
}
And it returned nothing I could read within the app when I tried the following function:
setCompany = async () => {
let company = await AsyncStorage.getItem('currentCompany');
company = JSON.parse(company);
if (company.image !== null) {
let image = await getCompanyPicture({company_id: company.id});
console.log('Here: ', image);
// This is blank, react native returns a warning about data not being of a readable type
}
this.setState({company});
};
I am able to get the image in base_64 using this method:
public function image(Request $request, $id)
{
$company = Company::find($id);
$file_path = storage_path('/app/' . $company->image);
if (file_exists($file_path)) {
$fileData = file_get_contents($file_path);
$fileEncode = base64_encode($fileData);
return response()->json(['status' => 'success', 'data' => ['file' => $fileEncode, 'file_path' => $file_path]]);
}
return response()->json(['status' => 'failure', 'data' => ['file' => null, 'file_path' => $file_path]]);
}
Here is my Axios method too just in case:
export const sendRequest = async (url, data, token, method) => {
let headers = {
'Content-Type': 'application/json',
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Method': 'POST, GET, DELETE, PUT',
};
if (typeof token !== 'undefined' && token !== 'undefined' && token.length) {
headers.Authorization = 'Bearer ' + token;
}
if (method === 'get' && data) {
url +=
'?' +
Object.keys(data)
.map((value) => {
return value + '=' + data[value];
})
.join('&');
data = null;
}
return await axios({
headers: headers,
method: method ? method : 'post',
url: url,
data: data,
})
.then((response) => {
return response;
})
.then((json) => {
return json.data;
})
.catch((error) => {
console.error(error);
if (
error.message !== 'Network Error' &&
error.response.status !== 500 &&
error.response.status !== 413
) {
return error.response.data;
} else if (error.message === 'Network Error') {
return {
status: 'error',
message: 'Unable to connect to server',
};
} else if (error.response.status === 500) {
return {
status: 'error',
message: 'Internal Server Error',
};
} else if (error.response.status === 413) {
return {
status: 'error',
message: 'The file(s) size is too large',
};
} else {
return {
status: 'error',
message: error.message,
};
}
});
};
If anyone could comment on the performance impact of using base_64 instead of the straight file download that would also be helpful
But ultimately I would like a solution for handling the Laravel response()->file() if possible (which I'll use if base_64 is less efficient)
I'm not sure about RN code syntax, but I've ready code with jQuery+poorJS, which looks like this:
$.ajax({
url: "load-image-url", // URL FOR GET REQUEST
cache:false,
xhr: function() { // ACTUALLY THIS PART CAN BE USED AND CUSTOMIZED BY YOU
let xhr = new XMLHttpRequest();
xhr.responseType= 'blob'
return xhr;
},
success: function(data) {
let url = window.URL || window.webkitURL;
$('#image').attr('src', url.createObjectURL(data));
},
error: function(err) {
// console.log(err);
}
}).fail(function() {
$('#ss_product_image').attr('src', "default-image-url.jpg");
});
In my example I've used GET request (but you can try to modify it and test if you want, honestly IDK about that).
This is the back-end part:
public function image(Request $request, $id)
{
// HERE YOU NEED TO GET YOUR IMAGE (using $id or/and $request params) CONTENT FROM SOMEWHERE YOU WANT
$content = <CONTENT>;
return response()->make($content, 200, [
'Content-Type' => (new \finfo(FILEINFO_MIME))->buffer($content),
'Content-length' => strlen($content),
]);
}
I was able to solve this issue by using rn-blob-fetch.
The files are downloaded into a temp cache which can then be accessed for previewing and saving.
this is my function now:
downloadFiles = async (isReply) => {
let {enquiry, reply} = this.state;
this.setState({isLoading: true});
const token = await AsyncStorage.getItem('userToken');
let filePaths = [];
let fileCount = 0;
let files = enquiry.files;
if (isReply) {
files = reply.files;
}
const dirToSave =
Platform.OS == 'ios'
? RNFetchBlob.fs.dirs.DocumentDir
: RNFetchBlob.fs.dirs.DownloadDir;
new Promise((resolve, reject) => {
for (var i = 0; i < files.length; i++) {
var id = files[i].file_id;
var name = files[i].file.file_name;
var ext = extension(name);
const configOptions = Platform.select({
ios: {
appendExt: ext,
fileCache: true,
title: name,
path: `${dirToSave}/${name}`,
},
android: {
useDownloadManager: true,
notification: true,
mediaScannable: true,
fileCache: true,
title: name,
path: `${dirToSave}/${name}`,
},
});
var mime = content(ext);
let headers = {
'Content-Type': mime,
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Method': 'POST, GET, DELETE, PUT',
Authorization: 'Bearer ' + token,
};
RNFetchBlob.config(configOptions)
.fetch('GET', BASE_API + '/enquiries/files/download/' + id, headers)
.then(async (response) => {
RNFetchBlob.fs.writeFile(
configOptions.path,
response.data,
'base64',
);
filePaths.push({
title: configOptions.title,
path: configOptions.path,
ext: extension(configOptions.title),
mime,
});
fileCount++;
if (fileCount >= files.length) {
resolve('Download Successful!');
}
})
.catch((error) => {
console.log('File Download Error: ', error.message);
reject('Download Failed');
});
}
})
.then((data) => {
this.setState({isLoading: false, filePaths});
})
.catch((error) => {
console.log('Download Promise Error: ', error);
this.setState({isLoading: false});
});
};
previewDocument = (id) => {
let {filePaths} = this.state;
if (Platform.OS == 'ios') {
RNFetchBlob.ios.openDocument(filePaths[id].path);
} else if (Platform.OS == 'android') {
RNFetchBlob.android.actionViewIntent(
filePaths[id].path,
filePaths[id].mime,
);
}
};

How do I get the message using Office.js in a react add-in

I am building a new react Outlook add-in and need to be able to download the current email.
The Office.js API has the getFileAsync method off the Office.context.document object but not the Office.context.mailbox.item object.
also as a requirement this needs to work in both Office online and local installs of Outlook.
In the existing com add-in I had direct access to the mail item.
Here is the code that I currently have to call into the API, but this only retrieves metadata.
/*
https://learn.microsoft.com/en-us/outlook/add-ins/use-rest-api#get-the-item-id
*/
public getMessageViaRest = () => {
const context: Office.AsyncContextOptions & { isRest: boolean } = {
isRest: true
};
Office.context.mailbox.getCallbackTokenAsync(context, (tokenResults) => {
if (tokenResults.status === Office.AsyncResultStatus.Failed) {
this.setState({ error: 'Failed to get rest api auth token' });
return;
}
const apiId: string = Office.context.mailbox.convertToRestId(Office.context.mailbox.item.itemId, 'v2.0');
const apiUrl = Office.context.mailbox.restUrl + '/v2.0/me/messages/' + apiId;
try {
fetch(apiUrl, {
method: 'GET',
headers: new Headers({
Authorization: 'Bearer ' + tokenResults.value
})
}).then((response) => {
response.json().then((body) => {
for (const key in body) {
this.state.details.push({ name: key, value: JSON.stringify(body[key]) });
}
this.forceUpdate();
});
});
} catch (error) {
this.setState({ error: JSON.stringify(error) });
}
});
}
Its not perfect but the REST Api does have an end point that will return the file's EML contents.
public downloadViaRest = () => {
const context: Office.AsyncContextOptions & { isRest: boolean } = {
isRest: true
};
Office.context.mailbox.getCallbackTokenAsync(context, (tokenResults) => {
if (tokenResults.status === Office.AsyncResultStatus.Failed) {
this.setState({ error: 'Failed to get rest api auth token' });
return;
}
const apiId: string = Office.context.mailbox.convertToRestId(Office.context.mailbox.item.itemId, 'v2.0');
const apiUrl = Office.context.mailbox.restUrl + '/v2.0/me/messages/' + apiId + '/$value';
try {
fetch(apiUrl, {
method: 'GET',
headers: new Headers({
Authorization: 'Bearer ' + tokenResults.value
})
}).then((response) => {
response.blob().then((blob) => {
const url = window.URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = 'Message.eml';
a.click();
});
});
} catch (error) {
}
});
}

Google recaptcha v3 always returning error

Following the instructions I get a valid token from my front end (can see in dev tools):
window.grecaptcha
.execute(captchaPkey, { action: 'contact' })
.then((token) => {
// this is what I POST to my API
So in my React front end:
send = (event) => {
event.preventDefault()
this.setState({ busy: true })
window.grecaptcha.ready(() => {
window.grecaptcha
.execute(captchaPkey, { action: 'contact' })
.then((token) => {
// successfully get token
const payload = {
token,
name: this.state.name,
to: this.props.to,
email: this.state.email,
message: this.state.message,
}
// now I'm sending the payload to my API
// My API
update(`${api}/contact/`, {
method: 'POST',
body: JSON.stringify(payload)
}, null)
.then(data => {
this.setState({ busy: false, result: 'Email sent' });
})
.catch(error => {
this.setState({ busy: false, error: error.message });
});
})
})
}
my API controller
async function verifyCaptcha(token) {
return await axios.post('https://www.google.com/recaptcha/api/siteverify', {
secret: process.env.CAPTCHA_PKEY,
response: token
})
}
async function contact({ token, to, name, email, message }) {
const result = await verifyCaptcha(token)
if (!result || !result.data || !result.data.success) {
// always get an error here
throw new Error('Invalid captcha')
}
let targetEmail = 'default#emailaddress'
if (to !== 'admin') {
const user = await User.findOne({ username: to }, { email }).exec()
if (!user) {
throw new Error('User does not exist')
}
targetEmail = user.email
}
// rest of send
}
On my API POST endpoint sends to https://www.google.com/recaptcha/api/siteverify with the body of:
{
secret: process.env.CAPTCHA_PKEY,
response: token
}
Yet I always get "missing-input-response", "missing-input-secret" error. Is this because v3 is new? Still bugs?
Realised in the documentation it states "post params" not post body haha.

react-native fetch with authorization header sometime return 401

I'm facing some issue whereby I sometime will get status code 401 (Unauthorised) from my phone. I'm trying to access to an API from my computer localhost (192.168.0.7).
I've a screen, when I click on a button it will navigate to a page and it will request data through API. And when I go back and navigate to same page again, it sometime will return me code 401.
So if I repeat the same step (navigate and go back) let's say 10 times. I'm getting Unauthorised like 5-7 times.
Below are my code
export function getMyCarpool(param,token) {
return dispatch => {
var requestUrl = _api + 'GetMyProduct?' + param;
fetch(requestUrl, {
method: "get",
headers: new Headers({
'Accept': 'application/json',
'Content-Type': 'application/x-www-form-urlencoded',
'Authorization': 'Bearer ' + token
})
})
.then((request) => {
console.log(request);
if(request.status == 200)
return request.json();
else if(request.status == 401) {
//dispatch(logout());
throw new Error('Unauthorized access.');
}
else
throw new Error('Failed to request, please try again.');
})
.then((response) => {
var message = response.message;
if(response.success == 'true')
dispatch({ message, type: GET_MY_PRODUCT_SUCCESS });
else
dispatch({ message, type: GET_MY_PRODUCT_FAILED });
})
.catch(error => {
var message = error.message;
dispatch({ message, type: GET_MY_PRODUCT_FAILED });
});
}
I've check the token in my phone and also trying to make many request using postman. So I don't think it's server side problem.
I'm using Laravel and using laravel passport for API authentication. I not sure why this happen if I continue to access many time, any help is greatly appreciated.
UPDATE :: I'm trying to capture whether the http request has the token from this link, and I don't get the problem anymore.
It's a healthy mechanism for token expire. Maybe you have your token (access_token) for 5 minutes, then the token expired, you should use refresh_token to regain another new token (access_token).
For code explanation:
async function fetchService(url) {
const reqSetting = {
headers: {
Accept: 'application/json',
Authorization: `Bearer ${Auth.access_token}`,
},
};
const prevRequest = { url, reqSetting };
const resp = await fetch(url, reqSetting);
if (!resp.ok) {
const error = new Error(resp.statusText || 'Request Failed!');
if (resp.status === 401 || resp.status === 400) {
const responseClone = resp.clone();
const errorInfo = await resp.json();
if (errorInfo.error == 'invalid_token') {
// console.log('Token Expired', errorInfo);
try {
await refreshToken();
const response = await fetchService(prevRequest.url);
return response;
} catch (err) {
// handle why not refresh a new token
}
}
return responseClone;
}
error.errorUrl = url;
error.code = resp.status;
throw error;
}
return resp;
}
Where the refresh token function is :
async function refreshToken() {
const url = 'https://example.com/oauth/token';
const data = {
grant_type: 'refresh_token',
refresh_token: Auth.refresh_token,
};
try {
const res = await fetch(url, data);
const data = res.json();
Auth.access_token = data.access_token;
Auth.refresh_token = data.refresh_token;
return true;
} catch (error) {
throw error;
}
}
This fetchService will automatic regain a new token if old expired and then handle old request.
PS.
If you have multiple requests same time, the fetchService will need a little optimization. You'd better choose another regain token strategy like saga.

authClient is not called with the AUTH_ERROR

I'm trying to implement custom rest client build on top of simple fetch.
If 401-403 response received, it must "redirect" app to login page.
According documentation, if 401-403 error received, it will magically calls authClient with the AUTH_ERROR, but it doesn't.
Can someone explain, how to connect it?
I'm trying to call rest client from component: It's simple reimplementation of 'simpleRestClient'
componentDidMount() {
restClient(CREATE, 'api/method', {
CurrentTime: new Date()
})
.then(o =>
{
this.setState({ Msg: Object.values(o.data.ServerTime) });
});
}
restclient implementation:
export const fetchJson = (url, options = {}) => {
const requestHeaders =
options.headers ||
new Headers({
Accept: 'application/json',
});
if (
!requestHeaders.has('Content-Type') &&
!(options && options.body && options.body instanceof FormData)
) {
requestHeaders.set('Content-Type', 'application/json');
}
if (options.user && options.user.authenticated && options.user.token) {
requestHeaders.set('Authorization', options.user.token);
}
return fetch(url, { ...options, headers: requestHeaders })
.then(response =>
response.text().then(text => ({
status: response.status,
statusText: response.statusText,
headers: response.headers,
body: text,
}))
)
.then(({ status, statusText, headers, body }) => {
if (status < 200 || status >= 300) {
return Promise.reject(
new HttpError(
(json && json.message) || statusText,
status,
json
)
);
}
let json;
try {
json = JSON.parse(body);
} catch (e) {
// not json, no big deal
}
return { status, headers, body, json };
});
};
const httpClient = (url, options = {}) => {
if (!options.headers) {
options.headers = new Headers({ Accept: 'application/json' });
}
return fetchJson(url, options);
}
Have you tried rejecting the promise with an Error rather than an HttpError?

Resources