How to Properly Set Permissions for a Custom Strapi Plugin - plugins

Strapi Version: 4.1.5
Operating System: Debian GNU/Linux 9
Database: PostgreSQL 13
Node Version: v14.16.0
NPM Version: 6.14.11
Yarn Version: v1.22.5
Hi everyone, I can’t seem to find consistent information on how to use permissions with a custom plugin in Strapi. I want to make an endpoint available to my front-end (Next.JS) application, but only when the front-end application has authenticated as a user and using the JWT that is returned from authenticating with Strapi. I keep getting a 401 returned.
Here’s what I’m doing:
I used this page to set up authentication in Strapi. I have a user created in Strapi, and from the front-end, I can authenticate and it returns a JWT token. When I set up collection types to only be accessible with the “authenticated” role, I can access those collection types in the api using this JWT token. So all of that works. The problem is that I can’t get this to work with my custom plugin, and I’m not sure why. I still get a 401 error instead.
Here’s how I set up the permissions:
Based on this page, I initially tried to leverage the isAuthenticated permission that the Users & Permissions plugin provides:
{
method: "GET",
path: "/progress",
handler: "memberProgress.getProgress",
config: {
policies: ['plugins::users-permissions.isAuthenticated']
},
},
Unfortunately, this did not work. The server raised an error, saying that this could not be found. So back on the document linked above, I decided to take the approach of creating my own gloabl permission. I created src/policies/is-authenticated.js with the following contents:
module.exports = (policyContext, config, { strapi }) => {
if (policyContext.state.user) { // if a session is open
// go to next policy or reach the controller's action
return true;
}
return false; // If you return nothing, Strapi considers you didn't want to block the request and will let it pass
};
Then, I modified my plugin’s route as follows:
{
method: "GET",
path: "/progress",
handler: "memberProgress.getProgress",
config: {
policies: ['global::is-authenticated']
},
},
This is all based on that document I linked to. Unfortunately, this still does not work. It seems to find the permission (server doesn’t raise an error about it), but when I try to access my plugin’s endpoint with the JWT token, I just get a 401 error.
Here is how I’m trying to access the endpoint on the front-end:
// VERIFIED, auth works and I get the expected jwt
const strapiAuth = await strapiApiAuth();
if ( strapiAuth && strapiAuth.hasOwnProperty("jwt") ) {
try {
const response = await axios.get(
`${process.env.STRAPI_BACKEND_URL}/member-progress/progress?year=2022&name=&pageSize=10&page=1`,
{
headers: {
Accept: "application/json",
Authorization: `Bearer ${strapiAuth.jwt}`
},
timeout: 500,
}
);
console.log(response);
} catch (error) {
// This is where I land with the 401 error
console.log(error);
}
}

Strapi check if you have a valid jwt by default with "authenticated" role, but you must mark the permission to your custom endpoint in "Settings→User & Permission Plugin→Roles" of admin panel also.

Related

Getting `The OAuth client was not found.` and `invalid client` error when trying to get access token for google cloud logging services in Dart

I'm following through this link: https://developers.google.com/identity/protocols/oauth2/service-account#httprest_1 in order to have my flutter app log to a log bucket in a google cloud project. Currently getting a
{
"error": "invalid_client",
"error_description": "The OAuth client was not found."
}
when I run the code below to get the access token in dart:
var jsonFile =
await File(jsonPath).readAsString();
var map = jsonDecode(jsonFile);
final jwt = JWT(
{
'iss': map['client_email'],
'sub': map['client_email'],
'aud': map['token_uri'],
'iat': (DateTime.now().millisecondsSinceEpoch / 1000).floor(),
'exp':
(DateTime.now().add(Duration(hours: 1)).millisecondsSinceEpoch / 1000)
.floor(),
},
issuer: map['private_key_id'],
);
final token = jwt.sign(SecretKey(map['private_key']));
print(token);
final accessToken = await http.post(
Uri.parse(map['token_uri']),
headers: {
HttpHeaders.contentTypeHeader: 'application/x-www-form-urlencoded',
},
body: {
'grant_type': 'urn:ietf:params:oauth:grant-type:jwt-bearer',
'assertion': token,
},
);
The JSON file is the credentials of a service account with logging admin role in the GCP project.
Invalid client means that the client id or the client secret that you are using are not valid.
As per the official documentation,
When attempting to get an access or refresh token, you will get an
"Invalid client" error if you provide an incorrect OAuth 2.0 Client
Secret. Make sure the client_secret value you're using in access and
refresh token calls is the one for the OAuth 2.0 Client ID being used,
as found in your GCP Credentials page.
Also refer to this SO link Github link for more information.

How to add policy to Keycloak - UI crashes

I'm trying to enable flow when some admin user by some admin client is able to create users and obtain their access tokens to be used for another clients.
I have KeyCloak setup with token exchange and fine grained authz enabled and configured clients. I'm able to login my admin user by REST api, then exchange token. But when I specify audience I got error.
This one returns token but I need token for another client/audience.
http -f POST https://my-keycloak-server.com/auth/admin/realms/my-realm/protocol/openid-connect/token grant_type=urn:ietf:params:oauth:grant-type:token-exchange requested_subject=1a147915-53fe-454d-906a-186fecfa6974 client_id=api-admin client_secret=23a4ecbe-a9e8-448c-b36a-a45fa1082e6e subject_token=eyJhbGeiOiJSUzI1NiIs......
This one is failing with error.
http -f POST https://my-keycloak-server.com/auth/admin/realms/my-realm/protocol/openid-connect/token grant_type=urn:ietf:params:oauth:grant-type:token-exchange requested_subject=1a147915-53fe-454d-906a-186fecfa6974 client_id=api-admin client_secret=23a4ecbe-a9e8-448c-b36a-a45fa1082e6e subject_token=eyJhbGeiOiJSUzI1NiIs...... audience=my-another-client
{
"error": "access_denied",
"error_description": "Client not allowed to exchange"
}
So I tried to setup fine grained auth for target audience client (enabled it in tab, then tried to add policy for my admin user to be able to exchange token) but when I want to add policy that will allow my admin user to perform token exchange I'm stuck on UI error.
When typing policy name I got 404 when Keycloak is looking for name colisions. Afaik 404 in this case shouldn't block form from posting because it is no name collision. Instead I got instantly redirected with error.
https://my-keycloak-server.com/auth/admin/realms/my-realm/clients/1bafa9a4-f7e2-422c-9188-58ea95db32ef/authz/resource-server/policy/search?name=some-name
In the end of the day I can't add any policy in Keycloak. All the time form validation is ending up with crash caused by 404 policy name not found.
I'm using dockerized keycloak 10.0.0
Any ideas?
I hacked it by live editing Angular JS UI script function that performs verification in line 2403.
this.checkNameAvailability = function (onSuccess) {
if (!$scope.policy.name || $scope.policy.name.trim().length == 0) {
return;
}
ResourceServerPolicy.search({
realm: $route.current.params.realm,
client: client.id,
name: $scope.policy.name
}, function(data) {
if (data && data.id && data.id != $scope.policy.id) {
Notifications.error("Name already in use by another policy or permission, please choose another one.");
} else {
onSuccess();
}
});
}
to
this.checkNameAvailability = function (onSuccess) {
onSuccess();
}
And that end up with successfuly added policy. Still looks like it's UI bug.

Google Drive Rest API (V3) - 404 error while accessing private file owned by self

I am trying to download (export) a Google Drive file owned by me (private - not shared with anyone) using release 21.3.0 of the nodejs client for Google API. I have set up a project in Google API Console and received a API key for the Drive API. When I try to access my file from a node program, I get a 'File not found' error. If I make the file public (On - Public on the web), I can access it fine. I have also tried setting up a service account to user server-to-server OAuth2 by following instructions here. I am then using the code in the examples to create a JWT token and use that to request a access token. But even with using that token, I get 'File not found' error when the file is private. Here is the code that I have so far for this functionality -
let key = require(#location_of_json_key_file);
let jwtClient = new google.auth.JWT(
key.client_email,
null,
key.private_key,
['https://www.googleapis.com/auth/drive'],
null
);
jwtClient.authorize(function (err, tokens) {
if (err) {
console.log(err);
return;
}
console.log(tokens);
let fileId = #id_of_file_on_google_drive;
googleDriveApi.files.export(
{
access_token: tokens.access_token, //tried auth: jwtClient as well
fileId: fileId,
mimeType: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
//key: <API_access_key> //Tried with API Access Key
},
{encoding: null},
(err, buffer) => {
if (err) {
//keep getting the error here - Error: File not found: #id_of_file_on_google_drive
console.log(`Error occurred while exporting file from Google Drive - ${err}`);
} else {
//process the file...
}
}
);
});
If the file is owned by my account, should I not be able to access it using service account belonging to the same Google account even when the file is private? Otherwise, to make the Drive files accessible programmatically, you would have to make them public which is not always possible / advisable.
What am I missing here? Thanks for any pointers.

Connecting Aurelia with backend API

Context: I'm starting a new project for my company. It's been many years since I've done some web development and decided to build it using the latest platforms (so I'm a still new to all of this).
Current stack:
Aurelia frontend (running on localhost:9000)
Backend REST API using ExpressJS (running on localhost:8000)
PostGreSQL database running on AWS, providing data for the backend
Question: I can't seem to connect my frontend with my backend properly.
Here is my code:
import {inject} from "aurelia-framework";
import {HttpClient} from "aurelia-http-client";
#inject(HttpClient)
export class Login {
constructor(httpClient){
this.http = httpClient;
}
signIn() {
const url = 'http://localhost:8000/api/user/demo/test';
this.http
.get(url)
.then(data => {
console.log("data");
console.log(data);
})
.catch(error => {
console.log('Error getting ' + url);
console.log(error);
});
};
}
This always end up in the catch block, with a "response: ProgressEvent"
If I put the url in the browser I get a proper JSON:
{"status":"success","data":[],"message":"Retrieved ALL users"}
The code above only works for 'local' content, i.e. localhost:9000. As soon as I need content from somewhere else I get this error. What am I missing?
I think that CORS is not allowing you to access localhost:8000 from localhost:9000. To solve this, you should enable your ExpressJS server to accept CORS requests from localhost:9000 (or all hosts using a wildcard "*").
Look into these resources:
https://enable-cors.org/server_expressjs.html
https://github.com/expressjs/cors
Or search Google for 'expressJS cors'.

LDAP authentication fails on ripple and actual device but not on browser

I'm trying to get an authorization token for an Ionic App from a LDAP service in a remote server.
I can get the auth token when I run the Ionic App in the browser with the command ionic serve and when I use Postman,
BUT it takes lot of time and eventually fails when I debug using ripple for the App or when I test on the phone or tablet.
The error says:
status: 503
statusText: Service Unavailable
data: html code from http://s3.amazonaws.com/heroku_pages/error.html
var deferred = $q.defer();
var req = {
method: 'GET',
url: 'http://host:port/adap?bind=token',
headers: {
Authorization: 'Basic <username>:<password>'
}
};
$http( req )
.then(function(data, status, headers, config) {
console.log(data);
deferred.resolve(data.data);
})
.catch(function(data) {
console.error(data.data);
deferred.reject(err);
});
return deferred.promise;
Does anybody have some hint about this issue?
Thanks in advance
For what you say the service is available, so the problem must be in the app side.
Check IP tables, and check ripple's proxy and set it to none.