fuzes
fuzes

Reputation: 2067

Is credentials.json necessary to use Google gmail api?

In node.js, I want to send gmail by google api. but there are only examples of using credentials.json.

Credentials.json seems to be difficult to push github, difficult to make env, and difficult to use github action secrets.

is there any way call gmail api without credentials.json??? if is there is no way, how can i manage credentials.json??

Upvotes: 4

Views: 2336

Answers (4)

Excalibur
Excalibur

Reputation: 3367

My recommendation is to create a file named credentials.dist.json which replaces the value of client_secret with the string {CLIENT_SECRET}.

credentials.dist.json:

{
  "installed": {
    "client_id": "1234tacosaredelicious5789.apps.googleusercontent.com",
    "project_id": "some-outh-project",
    "auth_uri": "https://accounts.google.com/o/oauth2/auth",
    "token_uri": "https://oauth2.googleapis.com/token",
    "auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs",
    "client_secret": "{CLIENT_SECRET}",
    "redirect_uris": ["http://localhost"]
  }
}

Commit this file to your repo. Add credentials.json to .gitignore to prevent it from being accidentally committed later.

Upon installing this project, have your build tooling or users run the following one-liner to recreate credentials.json:

  SECRET='THE_CLIENT_SECRET'; sed "s|{CLIENT_SECRET}|$SECRET|" credentials.dist.json > credentials.json

The value of SECRET can be stored in a password manager, a k8s secret, an environment variable, etc.

*Note that the leading space will cause most shells to skip saving it to history.

Upvotes: 0

Instead of passing string, you can pass json object

{
   installed: "web",
   client_id: "<idhere>",
   client_secret: "<secrethere>",
   redirect_uris: ''
}

Upvotes: 0

Siner
Siner

Reputation: 531

In nodejs, I wrote code like below.

Instead of exclude credentials.json you need to offer clientId, clientSecret, refreshToken, redirectUrl as other way (ex. environment)

export class GmailService {
  TOKEN_PATH: string = 'token.json';
  SCOPES: string[] = ['https://www.googleapis.com/auth/gmail.send'];
  refreshTokenUrl: string = 'https://oauth2.googleapis.com/token';
  ACCESS_TYPE: string = 'offline';
  oAuth2Client: any;
  gmailClient: gmail_v1.Gmail;

  constructor(private readonly configService: ConfigService) {
    this.oAuth2Client = new google.auth.OAuth2(
      this.configService.get(env.mailer.clientId),
      this.configService.get(env.mailer.clientSecret),
      this.configService.get(env.mailer.redirectUrl),
    );
    this.gmailClient = gmail({ version: 'v1', auth: this.oAuth2Client });
    this.authorize().catch((err: Error) => {
      throw err;
    });
  }

  private static encodeMessage(msg: Buffer): string {
    return Buffer.from(msg)
      .toString('base64')
      .replace(/\+/g, '-')
      .replace(/\//g, '_')
      .replace(/=+&/g, '');
  }

  async sendMail(mail: Mail.Options): Promise<void> {
    if (this.configService.get(env.environment) === 'test') return;
    if (this.configService.get(env.environment) !== 'production') {
      mail.to = this.configService.get(env.mailer.testTarget);
    }
    await this.authorize();
    await this.send(mail);
  }

  async authorize(): Promise<void> {
    // check token file exists
    await fs.readFile(this.TOKEN_PATH, async (err: Error, tokenFile: any) => {
      let token: Token;
      if (err) {
        token = await this.getNewToken(); // token file not exist
      } else {
        token = JSON.parse(tokenFile);
        if (token.expiry_date - new Date().getTime() < 30000) {
          token = await this.getNewToken(); // token was expired
        }
      }
      this.oAuth2Client.setCredentials(token);
    });
  }

  // refresh token
  async getNewToken(): Promise<Token> {
    const response: AxiosResponse = await axios.post(this.refreshTokenUrl, {
      client_id: this.configService.get(env.mailer.clientId),
      client_secret: this.configService.get(env.mailer.clientSecret),
      grant_type: 'refresh_token',
      refresh_token: this.configService.get(env.mailer.refreshToken),
    });
    const token: Token = response.data;
    if (token.expires_in && !token.expiry_date) {
      token.expiry_date = new Date().getTime() + token.expires_in * 1000;
    }
    await fs.writeFile(this.TOKEN_PATH, JSON.stringify(token), (err: Error) => {
      if (err) throw err;
    });
    return token;
  }

  private async send(mail: Mail.Options): Promise<void> {
    const mailComposer: MailComposer = new MailComposer(mail); // build mail with nodemailer
    mailComposer.compile().build((err: Error, msg: Buffer) => {
      if (err) throw err;
      this.gmailClient.users.messages.send(
        {
          userId: 'me',
          requestBody: {
            raw: GmailService.encodeMessage(msg),
          },
        },
        (err: Error, result: any) => {
          if (err) throw err;
          console.log('NODEMAILER reply from server', result.data);
        },
      );
    });
  }
}

Upvotes: 0

Linda Lawton - DaImTo
Linda Lawton - DaImTo

Reputation: 117136

In order to use Google APis you must first create a project on Google developer console. Once your project is created you will be able to enable which api you are looking at using in your project.

In order to access any data you will need to create credentials. These credentials identify your project to google and are used by your application to authorize and authenticate a user vai Oauth2.

No there is no way to use any google api accessing private user data without having a credeitnals.json file in your project.

Upvotes: 1

Related Questions