Why is NEAR requesting authorization when I call a change method on the contract? - nearprotocol

I have a change method on my NEAR AssemblyScript Contract, which I'm calling from my front end (browser) code. The user is already signed in and can read the view methods on the contract. It's only after trying to call the change method that the user is prompted with the NEAR Wallet requesting authorization.
How can I call the change method without receiving the prompt to request permission again?

You can also see some examples here:
basic github.com/Learn-NEAR/starter--near-api-js
advanced github.com/Learn-NEAR/starter--near-api-js
There's basically 2 interfaces here:
// pass the contract name (as you did)
wallet.requestSignIn('dev-1634098284641-40067785396400');
// pass an object with contract and empty list of methods
wallet.requestSignIn({
contractId: 'dev-1634098284641-40067785396400',
methodNames: []
});
// add a list of methods to constrain what you can call
wallet.requestSignIn({
contractId: 'dev-1634098284641-40067785396400',
methodNames: ['write']
});

I'm answering my own question, because I spent some time trying to figure it out.
When I was calling the function requestSignIn() on the wallet, I also had to specify the contract name.
import {
connect,
WalletConnection
} from 'near-api-js';
...
const near = await connect(nearConfig);
const wallet = new WalletConnection(near);
wallet.requestSignIn('dev-xxxx-yyyy'); // Need to pass the correct contract name to be able to call change methods on behalf of the user without requesting permission again

Related

Can view methods be used for cross contract calls? (Getting error HostError(ProhibitedInView { method_name: "promise_batch_create"})

I've been working on cross contract calls and simulation testing. I gnereally have things working when I use function calls but I'm seeing a wasm execution error "HostError(ProhibitedInView { method_name: "promise_batch_create") when trying to use a 'view' to call a method that generates a cross contract call to get its information.
contract1 has a method (indirect_num_entries) that uses a cross contract call to a method (num_entries) in contract2 that returns acount (with no modifications)
const contract1 = new nearAPI.Contract(
account1, // the account object that is connecting
"example-contract.testnet",
{
// name of contract you're connecting to
viewMethods: ["indirect_num_entries"], // view methods do not change state but usually return a value
changeMethods: ["indirect_add_entry"], // change methods modify state
sender: account, // account object to initialize and sign transactions.
}
);
and for contract2
const contract2 = new nearAPI.Contract(
account2, // the account object that is connecting
"example-contract.testnet",
{
// name of contract you're connecting to
viewMethods: ["num_entries"], // view methods do not change state but usually return a value
changeMethods: ["add_entry"], // change methods modify state
sender: account, // account object to initialize and sign transactions.
}
);
All of that works correctly via the Javascript SDK for both adding and getting the number of entries.
However I ran into a problem while trying to create a simulation test and found that trying to use view on indirect_num_entries caused an error:
let x : u64 = view!(contract1.indirect_num_entries()).unwrap_json(); //error
caused the following runtime error:
An error occurred
Error: Querying [object Object] failed: wasm execution failed with error: FunctionCallError(HostError(ProhibitedInView { method_name: "promise_batch_create" })).
Playing around a bit I found that replacing the view! with a call! worked -- i.e.
let x : u64 = call!(root, contract1.indirect_num_entries()).unwrap_json(); // works
I also found that if I use the near-cli I see the same behavior i.e.
near call contract1 indirect_num_entries "{}" --accountI contract1 (works)
near view contract1 indirect_num_entries (errors)
whereas for views directly to the contract2 work just fine
near view contract2 num_entries (works)
let x : u64 = view!(contract2.num_entries()).unwrap_json(); // works
Is this as expected and view is only valid for methods that access (not modify) the local contract data and shouldn't be used for methods that issue a cross contract call? (Or am I doing something incorrectly?)
I can certainly imagine that there could be reasons for needing to use a call (e.g. perhaps the request for a cross contract call needs to be signed) but I don't recall anything/haven't found anything that seems to describes this. Is there a description or a explanation somewhere?
Thanks!
This article explains the issue
https://docs.near.org/docs/develop/contracts/as/intro#view-and-change-functions
view calls do not provide the same context as a change call
the reason is because view calls are unsigned. no sender has signed a transaction to make the call. this is useful, not requiring a signature, because some use cases are intended for casual browsing or frequent reading of data like browsing an NFT collection or rendering a dashboard
change calls require a signed transaction so they include context like sender and other details
cross-contract calls require gas and this gas should be charged to some account. without a signed transaction (in a view call) then there is no one to charge for this gas
this might make you wonder: "but don't view calls cost gas too?" ... yes, they do, and today the RPC node providers are subsidizing these calls but that may change in the future

How to integrate Google Picker with new Google Identity Services JavaScript library

Because of the known issue described in here (https://developers.google.com/identity/sign-in/web/troubleshooting) I want to update my application to be using the new gsi sign-in that uses less cookies than the previous versions and therefore might have the solution for the mentioned error...
My problem is that there's little to no documentation on how to integrate google picker with the new gsi.
I used to use gapi for some picker-related code like even loading the library gapi.load('picker', () => {}). The migration doc says to replace the apis.google.com/js/api.js with the new gsi url, and a lot of other methods such as googleAuth.signIn or gapi.client.init are now to be deprecated by 2023. But then:
How to load picker without gapi available? Or gapi still needs to be imported but will not contain any sign-in related methods?
How will I pass apiKey and scopes to be able to init googlePicker?
For methods such as GoogleAuth.isSignedIn docs simply states "Remove. A user's current sign-in status on Google is unavailable. Users must be signed-in to Google for consent and sign-in moments." what does that even mean? I need to check if user is signed in in order to not show again the popup every time they want to upload a file from gPicker...
Before, we used to have a access_token on the callback of a reloadAuthResponse or a signIn, now how do we get the token?
Sorry for the question being confusing, I'm very confused with everything. Any input helps, thanks!
I came across https://developers.google.com/identity/oauth2/web/guides/use-token-model through: How to use scoped APIs with (GSI) Google Identity Services
I changed our code to load this script: https://accounts.google.com/gsi/client, and then modified the our "authorize" function (see below) to use window.google.accounts.oauth2.initTokenClient instead of window.gapi.auth2.authorize to get the access_token.
Note that the callback has moved from the second argument of the window.gapi.auth2.authorize function to the callback property of the first argument of the window.google.accounts.oauth2.initTokenClient function.
After calling tokenClient.requestAccessToken() (see below), the callback passed to window.gapi.auth2.authorize is called with an object containing access_token.
const authorize = () =>
- new Promise(res => window.gapi.auth2.authorize({
- client_id: GOOGLE_CLIENT_ID,
- scope: GOOGLE_DRIVE_SCOPE
- }, res));
+ new Promise(res => {
+ const tokenClient = window.google.accounts.oauth2.initTokenClient({
+ client_id: GOOGLE_CLIENT_ID,
+ scope: GOOGLE_DRIVE_SCOPE,
+ callback: res,
+ });
+ tokenClient.requestAccessToken();
+ });
The way access_token is used was not changed:
new window.google.picker.PickerBuilder().setOAuthToken(access_token)
#piannone is correct, adding to their answer:
You'll still need to load 'client' code, as you're using authentication. That means you'll still include https://apis.google.com/js/api.js in your list of scripts. Only don't load 'auth2'. So, while you won't do:
gapi.load('auth2', onAuthApiLoad);
gapi.load('picker', onPickerApiLoad);
you will need to:
gapi.load('client', onAuthApiLoad);
gapi.load('picker', onPickerApiLoad);
(this is instead of directly loading https://accounts.google.com/gsi/client.js I guess.)

Slack API: select conversation members while filtering out bots without n+1

I need to select all members of a conversation who are not bots. It appears the way to do this is to first call conversations.members and then for each member call users.info. Using the slack ruby client, that boils down to this:
client = Slack::Web::Client.new(token: "MY-OAUTH-TOKEN")
# returns an array of user ids
response = client.conversations_members(channel: "#some-channel", limit: 500)
member_ids = response.members
members = member_ids.reject do |member_id|
# returns a https://api.slack.com/types/user object
user = client.users_info(user: member_id)
user["user"]["is_bot"] == true
end
This obviously presents an n+1 problem. I'm wondering if I've overlooked a better API method to call, or an API method argument that could help with this, whether via slack-ruby-client, or just via the vanilla API methods.
Unfortunately, Currently Slack does not have a single API call solution to your problem statement.

Meteor 0.5.9: replacement for using Session in a server method?

So, I was attempting to do something like the following:
if(Meteor.isServer){
Meteor.methods({connect_to_api: function(vars){
// get data from remote API
return data;
}});
}
if(Meteor.isClient){
Template.myTpl.content = function(){
Meteor.call('connect_to_api', vars, function(err,data){
Session.set('placeholder', data);
});
return Session.get('placeholder');
};
}
This seemed to be working fine, but, of course, now breaks in 0.5.9 as the Session object has been removed from the server. How in the world do you now create a reactive Template that uses a server-only (stuff we don't want loading on the client) method call and get data back from that Method call. You can't put any Session references in the callback function because it doesn't exist on the server, and I don't know of any other reactive data sources available for this scenario.
I'm pretty new to Meteor, so I'm really trying to pin down best-practices stuff that has the best chance of being future-proof. Apparently the above implementation was not it.
EDIT: To clarify, this is not a problem of when I'm returning from the Template function. This is a problem of Session existing on the server. The above code will generate the following error message on the server:
Exception while invoking method 'connect_to_api' ReferenceError: Session is not defined
at Meteor.methods.connect_to_api (path/to/file.js:#:#)
at _.extend.protocol_handlers.method.exception ... etc etc
Setting the session in the callback seems to work fine, see this project I created on github: https://github.com/jtblin/meteor_session_test. In this example, I return data in a server method, and set it in the session in the callback.
There are 2 issues with your code:
1) Missing closing brace placement in Meteor.methods. The code should be:
Meteor.methods({
connect_to_api: function(vars) {
// get data from remote API
return data;
}
});
2) As explained above, you return the value in the session, before the callback is completed, i.e. before the callback method had the time to set the session variable. I guess this is why you don't see any data in the session variable yet.
I feel like an idiot (not the first time, not the last). Thanks to jtblin for showing me that Session.set does indeed work in the callback, I went back and scoured my Meteor.method function. Turns out there was one spot buried in the code where I was using Session.get which was what was throwing the error. Once I passed that value in from the client rather than trying to get it in the method itself, all was right with the world.
Oh, and you can indeed order things as above without issue.

Simple Claims Transformation for an RP-STS in Geneva Framework

After reading the MSDN article (http://msdn.microsoft.com/en-us/magazine/2009.01.genevests.aspx) on implementing a Custom STS using the Microsoft Geneva Framework I am a bit puzzled about one of the scenarios covered there. This scenario is shown in figure 13 of the above referenced article.
My questions are around how does the RP initiate the call to the RP-STS in order to pass on the already obtained claims from the IP-STS? How does the desired method DeleteOrder() get turned into a Claim Request for the Action claim from the RP-STS which responds with the Action claim with a value Delete which authorizes the call? I also think the figure is slightly incorrect in that the interaction between the RP-STS and the Policy Engine should have the Claims and arrows the other way around.
I can see the structure but it's not clear what is provided by Geneva/WCF and what has to be done in code inside the RP, which would seem a bit odd since we could not protect the DeleteOrder method with a PrincipalPermission demand for the Delete "permission" but would have to demand a Role first then obtain the fine-grained claim of the Delete Action after that point.
If I have missed the point (since I cannot find this case covered easily on the Web), then apologies!
Thanks in advance.
I asked the same question on the Geneva Forum at
http://social.msdn.microsoft.com/Forums/en/Geneva/thread/d10c556c-1ec5-409d-8c25-bee2933e85ea?prof=required
and got this reply:
Hi Dokie,
I wondered this same thing when I read that article. As I've pondered how such a scenario would be implemented, I've come up w/ two ideas:
The RP is actually configured to require claims from the RP-STS; the RP-STS requires a security token from the IP-STS. As a result, when the subject requests a resource of the RP, it bounces him to the RP-STS who bounces him to the IP-STS. After authenticating there, he is bounced back to the RP-STS, the identity-centric claims are transformed into those necessary to make an authorization decision and returned to the RP.
The RP is configured to have an interceptor (e.g., an AuthorizationPolicy if it's a WCF service) that grabs the call, sees the identity-centric claims, creates an RST (using the WSTrustClient), passes it to the RP-STS, that service expands the claims into new one that are returned to the RP, and the RP makes an authorization decision.
I've never implemented this, but, if I were going to, I would explore those two ideas further.
HTH!
Regards,
Travis Spencer
So I will try option 2 first and see if that works out then formulate an answer here.
I've got situation one working fine.
In my case AD FS is the Identity Service and a custom STS the Resource STS.
All webapp's use the same Resource STS, but after a user visits an other application the Identity releated claims are not addad again by the AD FS since the user is already authenticated. How can I force or request the basic claims from the AD FS again?
I've created a call to the AD FS with ActAs, now it returns my identification claims.
Remember to enable a Delegation allowed rule for the credentials used to call the AD FS.
string stsEndpoint = "https://<ADFS>/adfs/services/trust/2005/usernamemixed";
var trustChannelFactory = new WSTrustChannelFactory(new UserNameWSTrustBinding(SecurityMode.TransportWithMessageCredential), stsEndpoint);
trustChannelFactory.Credentials.UserName.UserName = #"DELEGATE";
trustChannelFactory.Credentials.UserName.Password = #"PASSWORD";
trustChannelFactory.TrustVersion = TrustVersion.WSTrustFeb2005;
//// Prepare the RST.
//var trustChannelFactory = new WSTrustChannelFactory(tokenParameters.IssuerBinding, tokenParameters.IssuerAddress);
var trustChannel = (WSTrustChannel)trustChannelFactory.CreateChannel();
var rst = new RequestSecurityToken(RequestTypes.Issue);
rst.AppliesTo = new EndpointAddress(#"https:<RPADDRESS>");
// If you're doing delegation, set the ActAs value.
var principal = Thread.CurrentPrincipal as IClaimsPrincipal;
var bootstrapToken = principal.Identities[0].BootstrapToken;
// The bootstraptoken is the token received from the AD FS after succesfull authentication, this can be reused to call the AD FS the the users credentials
if (bootstrapToken == null)
{
throw new Exception("Bootstraptoken is empty, make sure SaveBootstrapTokens = true at the RP");
}
rst.ActAs = new SecurityTokenElement(bootstrapToken);
// Beware, this mode make's sure that there is no certficiate needed for the RP -> AD FS communication
rst.KeyType = KeyTypes.Bearer;
// Disable the need for AD FS to crypt the data to R-STS
Scope.SymmetricKeyEncryptionRequired = false;
// Here's where you can look up claims requirements dynamically.
rst.Claims.Add(new RequestClaim(ClaimTypes.Name));
rst.Claims.Add(new RequestClaim(ClaimTypes.PrimarySid));
// Get the token and attach it to the channel before making a request.
RequestSecurityTokenResponse rstr = null;
var issuedToken = trustChannel.Issue(rst, out rstr);
var claims = GetClaimsFromToken((GenericXmlSecurityToken)issuedToken);
private static ClaimCollection GetClaimsFromToken(GenericXmlSecurityToken genericToken)
{
var handlers = FederatedAuthentication.ServiceConfiguration.SecurityTokenHandlers;
var token = handlers.ReadToken(new XmlTextReader(new StringReader(genericToken.TokenXml.OuterXml)));
return handlers.ValidateToken(token).First().Claims;
}

Resources