I'm trying to get file uploads to work in my Laravel Nova on a Vapor server.
When I try to upload, it keeps showing me a 401 response on /vapor/signed-storage-url.
What am I missing?
This is my Nova Resource:
public function fields(Request $request)
{
return [
VaporFile::make('Attachment')->rules('bail', 'required', function ($attribute, $value, $fail) use ($request) {
if (Storage::size($request->input('vaporFile')[$attribute]['key']) > 1000000) {
return $fail('The document size may not be greater than 1 MB');
}
}),
This is added to my app.js:
window.Vapor = require("laravel-vapor");
This is my registered policy:
class UserPolicy
{
use HandlesAuthorization;
/**
* Determine whether the user can upload files.
*
* #param \App\User $user
* #return mixed
*/
public function uploadFiles(User $user)
{
return true;
}
Looks to me I've done everything correctly.. but it still fails.
Related
According to this documentation, https://developers.google.com/identity/oauth2/web/guides/use-token-model , which says:
Token expiration
By design, access tokens have a short lifetime. If the access token expires prior to the end of the user's session, obtain a new token by calling requestAccessToken() from a user-driven event such as a button press.
This steers us away from using a refresh token. That is Question 0: when should a refresh token be used?
My app is a web app and users stay signed in for a long time -> longer than access token expiry time, which seems to be one hour. To make it a little easier to continually fetch fresh access tokens as they are required, I wrote this code below.
It is intended to enable me to make statements like:
const provider = new GoogleAccessTokenProvider(clientId)
const scopes = ["https://www.googleapis.com/auth/drive.metadata.readonly"]
provider.getTokenWithScopes(scopes)
.then(token => {
console.log(token)
}).catch(e => {
console.log(e)
})
It seems like I have to do a lot of uninteresting stuff in this code, so I have many questions about it, listed below:
Question 1 - It stores the token response from google.accounts.oauth2 in local storage. Is this the kind of thing Google intended us to do - store the access token locally?
Question 2 - It manages token expiry (with a ten second buffer). Again, is this something Google intend us to concern ourselves with? Is there a lib that does this automatically?
Question 3 - If calls requestAccessToken() with no prompt if the access token has the required scopes, but it is expired. This flashes up the Google consent dialog for a second, then it disappears. This seems to me to be where a refresh token would be handy.
Anyway, it works, but this flash up of the consent dialog is not great UX - is this what Google intended us to do?
Question 4 - It calls initTokenClient for every requestAccessToken(), because scopes are bound to this call. Seems odd to me - is this what Google intended us to do?
Anyway, here is the code:
class LocalStorage {
getItem(key: string): string | null {
return window.localStorage.getItem(key)
}
setItem(key: string, value: string): void {
return window.localStorage.setItem(key, value)
}
}
class TokenResponseStorage {
constructor(private readonly localStorage = new LocalStorage()) {
}
getItem(key: string): WrappedGoogleAccessTokenResponse | null {
const value = this.localStorage.getItem(key)
if (value === null) {
return null
}
return JSON.parse(value)
}
setItem(key: string, value: WrappedGoogleAccessTokenResponse): void {
this.localStorage.setItem(key, JSON.stringify(value))
}
}
interface WrappedGoogleAccessTokenResponse {
_type: "wrapped.google.access.token.response"
expiresAtMillis: number
tokenResponse: google.accounts.oauth2.TokenResponse
}
export class GoogleAccessTokenProvider {
private readonly expiryMarginMillis = 1000 * 10 // ten seconds before expiry
constructor(private readonly clientId: string,
private readonly storage = new TokenResponseStorage(),
private readonly storageKey = 'docsndata.google.access.token') {
}
async getTokenWithScopes(scopes: string[]): Promise<string> {
return new Promise(resolve => {
const wrappedToken = this.storage.getItem(this.storageKey)
if (wrappedToken && scopes.every(s => wrappedToken.tokenResponse.scopes.includes(s))) {
if (this._hasSufficientExpiry(wrappedToken)) {
resolve(wrappedToken.tokenResponse.access_token)
} else {
this._promptForToken(scopes, 'none').then(resolve)
}
} else {
this._promptForToken(scopes, 'consent').then(resolve)
}
})
}
private _hasSufficientExpiry(token: WrappedGoogleAccessTokenResponse): boolean {
return token.expiresAtMillis - this.expiryMarginMillis > Date.now()
}
private async _promptForToken(scopes: string[], prompt: "none" | "consent"): Promise<string> {
const that = this
return new Promise(resolve => {
const tokenClient = google.accounts.oauth2.initTokenClient({
client_id: this.clientId,
scope: scopes.join(' '),
callback: function (tokenResponse) {
that._storeTokenResponse(tokenResponse)
resolve(tokenResponse.access_token)
}
})
tokenClient.requestAccessToken({prompt})
})
}
private _storeTokenResponse(tokenResponse: google.accounts.oauth2.TokenResponse) {
const wrappedTokenResponse: WrappedGoogleAccessTokenResponse = {
_type: "wrapped.google.access.token.response",
expiresAtMillis: Date.now() + parseInt(tokenResponse.expires_in) * 1000,
tokenResponse
}
this.storage.setItem(this.storageKey, wrappedTokenResponse)
}
}
I'm trying to build a Microsoft Teams integration for an app, but I'm having some trouble getting a user's email address.
I used the Microsoft Teams extension for VS Code to scaffold a basic app. I'm using the BotFramework v4 (NodeJS) on my server. I'm able to receive requests from Teams and respond to them as well.
To get an user's email address, I am using the TeamsInfo.getMember(context, id) method, where the id is obtained from context.activity.from object. Unfortunately, calling this method results in a RestError: Unknown with a status code of 400.
I'm not sure what I'm missing here. My app is registered with the Azure Active Directory and has the User.Read.All permission. Am I missing something here?
Any help would be appreciated!
For some context, I'm trying to build a Messaging Extension Action Command.
Code:
import {
TurnContext,
TeamsActivityHandler,
CardFactory,
MessagingExtensionAction,
TeamsInfo,
} from 'botbuilder';
export default class TeamsMessagingExtensionsActionBot extends TeamsActivityHandler {
constructor() {
super();
}
// #ts-ignore
handleTeamsMessagingExtensionSubmitAction(
context: TurnContext,
action: MessagingExtensionAction,
) {
switch (action.commandId) {
case 'someCommand':
return handleCommand(context, action);
default:
throw new Error('NotImplemented');
}
}
}
async function handleCommand(
context: TurnContext,
action: MessagingExtensionAction,
) {
const card = CardFactory.heroCard(
'Some Command',
'We have received your command!',
);
const user = await TeamsInfo.getMember(context, context.activity.from.id);
console.log('User:', user);
const attachment = {
contentType: card.contentType,
content: card.content,
preview: card,
};
return {
composeExtension: {
type: 'result',
attachmentLayout: 'list',
attachments: [attachment],
},
};
}
This is the error I get when calling TeamsInfo.getMember(): JSON
I am new in google sheets v4 and I just want to know how can I update my google sheet in v4. I am using Nodejs and following is the code sample link which I am using Method: spreadsheets.values.update
You can use the sample script of the link. In you case, combining Quickstart and the sample script may be useful for you. The sample script is as follows.
In this sample script, the text of sample text is imported to the cell a1 of Sheet1.
Sample script :
var fs = require('fs');
var readline = require('readline');
var google = require('googleapis');
var googleAuth = require('google-auth-library');
// If modifying these scopes, delete your previously saved credentials
// at ~/.credentials/sheets.googleapis.com-nodejs-quickstart.json
var SCOPES = ['https://www.googleapis.com/auth/spreadsheets', 'https://www.googleapis.com/auth/drive'];
var TOKEN_DIR = (process.env.HOME || process.env.HOMEPATH ||
process.env.USERPROFILE) + '/.credentials/';
var TOKEN_PATH = TOKEN_DIR + 'sheets.googleapis.com-nodejs-quickstart.json';
// Load client secrets from a local file.
fs.readFile('client_secret.json', function processClientSecrets(err, content) {
if (err) {
console.log('Error loading client secret file: ' + err);
return;
}
// Authorize a client with the loaded credentials, then call the
// Google Sheets API.
authorize(JSON.parse(content), valuesUpdate);
});
/**
* Create an OAuth2 client with the given credentials, and then execute the
* given callback function.
*
* #param {Object} credentials The authorization client credentials.
* #param {function} callback The callback to call with the authorized client.
*/
function authorize(credentials, callback) {
var clientSecret = credentials.installed.client_secret;
var clientId = credentials.installed.client_id;
var redirectUrl = credentials.installed.redirect_uris[0];
var auth = new googleAuth();
var oauth2Client = new auth.OAuth2(clientId, clientSecret, redirectUrl);
// Check if we have previously stored a token.
fs.readFile(TOKEN_PATH, function(err, token) {
if (err) {
getNewToken(oauth2Client, callback);
} else {
oauth2Client.credentials = JSON.parse(token);
callback(oauth2Client);
}
});
}
/**
* Get and store new token after prompting for user authorization, and then
* execute the given callback with the authorized OAuth2 client.
*
* #param {google.auth.OAuth2} oauth2Client The OAuth2 client to get token for.
* #param {getEventsCallback} callback The callback to call with the authorized
* client.
*/
function getNewToken(oauth2Client, callback) {
var authUrl = oauth2Client.generateAuthUrl({
access_type: 'offline',
scope: SCOPES
});
console.log('Authorize this app by visiting this url: ', authUrl);
var rl = readline.createInterface({
input: process.stdin,
output: process.stdout
});
rl.question('Enter the code from that page here: ', function(code) {
rl.close();
oauth2Client.getToken(code, function(err, token) {
if (err) {
console.log('Error while trying to retrieve access token', err);
return;
}
oauth2Client.credentials = token;
storeToken(token);
callback(oauth2Client);
});
});
}
/**
* Store token to disk be used in later program executions.
*
* #param {Object} token The token to store to disk.
*/
function storeToken(token) {
try {
fs.mkdirSync(TOKEN_DIR);
} catch (err) {
if (err.code != 'EEXIST') {
throw err;
}
}
fs.writeFile(TOKEN_PATH, JSON.stringify(token));
console.log('Token stored to ' + TOKEN_PATH);
}
function valuesUpdate(auth) {
var sheets = google.sheets('v4');
var request = {
// The ID of the spreadsheet to update.
spreadsheetId: 'my-spreadsheet-id', // TODO: Update placeholder value.
// The A1 notation of the values to update.
range: 'Sheet1!a1:a1', // TODO: Update placeholder value.
// How the input data should be interpreted.
valueInputOption: 'RAW', // TODO: Update placeholder value.
resource: {'values': [['sample text']]},
auth: auth,
};
sheets.spreadsheets.values.update(request, function(err, response) {
if (err) {
console.error(err);
return;
}
// TODO: Change code below to process the `response` object:
console.log(JSON.stringify(response, null, 2));
});
}
IMPORTANT :
Please modify my-spreadsheet-id to yours in above script.
This sample script supposes that the script of Quickstart works fine.
After run the script of Quickstart, please remove sheets.googleapis.com-nodejs-quickstart.json once, before run the above sample script. After remove the file, please run the above script. By this, the refresh token with the new scopes is retrieved and it is used for updating values.
If you want to use this script, please use googleapis v24 or less. Because the latest version doesn't work. Because the following error occurs, even if valueInputOption is set.
Error: 'valueInputOption' is required but not specified
I believe that this error will be modified in the near future.
If I misunderstand your question, I'm sorry.
I'm working on a project which uses Google's PHP API Library. I am currently using the scopes with offline access:
https://www.googleapis.com/auth/youtube.force-ssl
https://www.googleapis.com/auth/youtube.upload
But this did not give me access to get the channel title for the authenticated channel.
I then tried adding more scopes:
https://www.googleapis.com/auth/youtube.readonly
https://www.googleapis.com/auth/youtubepartner
https://www.googleapis.com/auth/youtubepartner-channel-audit
But I also had the same result. Is there a scope, that isn't related to YouTube that I am missing?
-
Here is the code that I am using, note that $this->client is an instance of Google_Client:
/**
* Fetch channel title
*
* #return string
*/
public function fetchChannelTitle() {
/** #var \Google_Service_YouTube_Channel $details */
$details = $this->getChannelDetails();
$snippet = $details->getSnippet();
return $snippet->getTitle();
}
/**
* Get channel details
*
* #throws YouTubeException
*/
public function getChannelDetails()
{
if (!$this->access_token) {
throw new YouTubeException('NO_REFRESH_TOKEN');
}
$this->client->setAccessToken($this->access_token);
$parts = 'snippet,contentDetails,statistics';
$params = ['mine' => true];
$response = (new Google_Service_YouTube($this->client))->channels->listChannels(
$parts,
$params
);
return $response->getItems()[0];
}
Don't forget to include this scope
https://www.googleapis.com/auth/youtube
and the field parameter 'title' under snippet.
here's mine using the API explorer :
"title": "Steve Jobs" //that's my title
There is this new Export functionality developed on this application and I'm trying to test it using Behat/Mink.
The issue here is when I click on the export link, the data on the page gets exported in to a CSV and gets saved under /Downloads but I don't see any response code or anything on the page.
Is there a way I can export the CSV and navigate to the /Downloads folder to verify the file?
Assuming you are using the Selenium driver you could "click" on the link and $this->getSession()->wait(30) until the download is finished and then check the Downloads folder for the file.
That would be the simplest solution. Alternatively you can use a proxy, like BrowserMob, to watch all requests and then verify the response code. But that would be a really painful path for that alone.
The simplest way to check that the file is downloaded would be to define another step with a basic assertion.
/**
* #Then /^the file ".+" should be downloaded$/
*/
public function assertFileDownloaded($filename)
{
if (!file_exists('/download/dir/' . $filename)) {
throw new Exception();
}
}
This might be problematic in situations when you download a file with the same name and the browser saves it under a different name. As a solution you can add a #BeforeScenario hook to clear the list of the know files.
Another issue would be the download dir itself – it might be different for other users / machines. To fix that you could pass the download dir in your behat.yml as a argument to the context constructor, read the docs for that.
But the best approach would be to pass the configuration to the Selenium specifying the download dir to ensure it's always clear and you know exactly where to search. I am not certain how to do that, but from the quick googling it seems to be possible.
Checkout this blog: https://www.jverdeyen.be/php/behat-file-downloads/
The basic idea is to copy the current session and do the request with Guzzle. After that you can check the response any way you like.
class FeatureContext extends \Behat\Behat\Context\BehatContext {
/**
* #When /^I try to download "([^"]*)"$/
*/
public function iTryToDownload($url)
{
$cookies = $this->getSession()->getDriver()->getWebDriverSession()->getCookie('PHPSESSID');
$cookie = new \Guzzle\Plugin\Cookie\Cookie();
$cookie->setName($cookies[0]['name']);
$cookie->setValue($cookies[0]['value']);
$cookie->setDomain($cookies[0]['domain']);
$jar = new \Guzzle\Plugin\Cookie\CookieJar\ArrayCookieJar();
$jar->add($cookie);
$client = new \Guzzle\Http\Client($this->getSession()->getCurrentUrl());
$client->addSubscriber(new \Guzzle\Plugin\Cookie\CookiePlugin($jar));
$request = $client->get($url);
$this->response = $request->send();
}
/**
* #Then /^I should see response status code "([^"]*)"$/
*/
public function iShouldSeeResponseStatusCode($statusCode)
{
$responseStatusCode = $this->response->getStatusCode();
if (!$responseStatusCode == intval($statusCode)) {
throw new \Exception(sprintf("Did not see response status code %s, but %s.", $statusCode, $responseStatusCode));
}
}
/**
* #Then /^I should see in the header "([^"]*)":"([^"]*)"$/
*/
public function iShouldSeeInTheHeader($header, $value)
{
$headers = $this->response->getHeaders();
if ($headers->get($header) != $value) {
throw new \Exception(sprintf("Did not see %s with value %s.", $header, $value));
}
}
}
Little modified iTryToDownload() function with using all cookies:
public function iTryToDownload($link) {
$elt = $this->getSession()->getPage()->findLink($link);
if($elt) {
$value = $elt->getAttribute('href');
$driver = $this->getSession()->getDriver();
if ($driver instanceof \Behat\Mink\Driver\Selenium2Driver) {
$ds = $driver->getWebDriverSession();
$cookies = $ds->getAllCookies();
} else {
throw new \InvalidArgumentException('Not Selenium2Driver');
}
$jar = new \Guzzle\Plugin\Cookie\CookieJar\ArrayCookieJar();
for ($i = 0; $i < count($cookies); $i++) {
$cookie = new \Guzzle\Plugin\Cookie\Cookie();
$cookie->setName($cookies[$i]['name']);
$cookie->setValue($cookies[$i]['value']);
$cookie->setDomain($cookies[$i]['domain']);
$jar->add($cookie);
}
$client = new \Guzzle\Http\Client($this->getSession()->getCurrentUrl());
$client->addSubscriber(new \Guzzle\Plugin\Cookie\CookiePlugin($jar));
$request = $client->get($value);
$this->response = $request->send();
} else {
throw new \InvalidArgumentException(sprintf('Could not evaluate: "%s"', $link));
}
}
In project we have problem that we have two servers: one with web drivers and browsers and second with selenium hub. As result we decide to use curl request for fetching headers. So I wrote function which would called in step definition. Below you can find a function which use a standard php functions: curl_init()
/**
* #param $request_url
* #param $userToken
* #return bool
* #throws Exception
*/
private function makeCurlRequestForDownloadCSV($request_url, $userToken)
{
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $request_url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$headers = [
'Content-Type: application/json',
"Authorization: Bearer {$userToken}"
];
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
$output = curl_exec($ch);
$info = curl_getinfo($ch);
$output .= "\n" . curl_error($ch);
curl_close($ch);
if ($output === false || $info['http_code'] != 200 || $info['content_type'] != "text/csv; charset=UTF-8") {
$output = "No cURL data returned for $request_url [" . $info['http_code'] . "]";
throw new Exception($output);
} else {
return true;
}
}
How you can see I have authorization by token. If you want to understand what headers you should use you should download file manual and look request and response in browser's tab network