Skip to content

Instantly share code, notes, and snippets.

@nathanvogel
Last active December 24, 2018 10:35
Show Gist options
  • Save nathanvogel/15ed311258b91d7ec3d25f44047780e2 to your computer and use it in GitHub Desktop.
Save nathanvogel/15ed311258b91d7ec3d25f44047780e2 to your computer and use it in GitHub Desktop.
IFTTT OAuth token fake example with Firebase Cloud Functions
// A React-Redux component to confirm which user is authorizing IFTTT
// It is react-routed at http://localhost:3000/authorize_ifttt
import React, { Component } from "react";
import { Redirect } from "react-router-dom";
// ...
class AuthorizeIFTTT extends Component {
signOut = _event => {
this.props.signOut();
};
authorize = _event => {
// TODO: Save to RTDB that access is okay or something like that.
// OR : get a token and authentify the redirect with this token and check
// the token server side.
// Redirect the user
// Sample current URL:
// http://localhost:3000/authorize_ifttt?state=[REDACTED]&redirect_uri=https%3A%2F%2Fifttt.com%2Fchannels%2Fmltest%2Fauthorize
const params = new URLSearchParams(this.props.location.search);
const iftttState = params.get("state");
const redirect_uri = params.get("redirect_uri");
const get_code_uri = FIREBASE_FUNCTION_URL + "/oauth/authorize_user";
window.location =
get_code_uri +
"?uid=" +
this.props.auth.user.uid +
"&state=" +
iftttState +
"&redirect_uri=" +
redirect_uri;
};
render() {
return (
<div className="AuthorizeIFTTT">
<div>
Signed in as {this.props.auth.user.email},{" "}
{this.props.auth.user.displayName}
</div>
<button onClick={this.authorize}>Authorize IFTTT</button>
<button onClick={this.signOut}>Change user</button>
</div>
);
}
}
export default connect(
mapStateToProps,
mapDispatchToProps
)(AuthorizeIFTTT);
// This gist is based on thie example from IFTTT
// https://github.com/IFTTT/connect_with_ifttt_auth_sample
// which was originally made for the mobile SDKs, but I adapted
// it to run it in a Firebase Cloud Function using UIDs.
const functions = require("firebase-functions");
var express = require("express");
var int_encoder = require("int-encoder");
var crypto = require("crypto");
var bodyParser = require("body-parser");
// The Firebase Admin SDK to access the Firebase Realtime Database.
const admin = require("firebase-admin");
admin.initializeApp();
const APP_URL = "http://localhost:3000";
const ENCRYPTION_KEY = "xyz123";
int_encoder.alphabet();
function encrypt_string(string) {
var cipher = crypto.createCipher("aes-256-cbc", ENCRYPTION_KEY);
var crypted = cipher.update(string, "utf8", "hex");
crypted += cipher.final("hex");
return int_encoder.encode(crypted, 16);
}
function decrypt_string(string) {
key = int_encoder.decode(string, 16);
var decipher = crypto.createDecipher("aes-256-cbc", ENCRYPTION_KEY);
var dec = decipher.update(key, "hex", "utf8");
dec += decipher.final("utf8");
return dec;
}
function random(max) {
return Math.floor(Math.random() * max + 1);
}
function getTokenWithUid(uid) {
const token = uid + ":" + random(100);
// console.log("Generated token:" + token);
const encrypted_token = encrypt_string(token);
// console.log("uid:" + uid + " -> Token:" + encrypted_token);
return encrypted_token;
}
function getUidFromToken(token) {
const user_random = decrypt_string(token);
// console.log("user_random_t: " + user_random);
const uid = user_random.split(":")[0];
// console.log("Token:" + token + " -> uid:" + uid);
return uid;
}
function getAuthCodeWithUid(uid) {
const authcode = uid + "." + random(100);
// console.log("Generated authocode:" + authcode);
const encrypted_authcode = encrypt_string(authcode);
// console.log("uid:" + uid + " -> Code:" + encrypted_authcode);
return encrypted_authcode;
}
function getUidFromAuthCode(authcode) {
const user_random = decrypt_string(authcode);
// console.log("user_random_c: " + user_random);
const uid = user_random.split(".")[0];
// console.log("Code:" + authcode + " -> uid:" + uid);
return uid;
}
var app = express();
// parse application/x-www-form-urlencoded
app.use(bodyParser.urlencoded({ extended: false }));
// parse application/json
app.use(bodyParser.json());
// ****************** START OAUTH SERVER ******************
// Since I'm not using the Connect with IFTTT SDK, I'm not calling
// /generate_oauth_code from the client, but rather redirecting the user
// to the app, which will then redirect to /oauth/authorize_user with the UID.
// PS: I'm not sure if it's a good clean idea.
app.get("/oauth/authorize", (req, res) => {
console.log(req.url);
// Redirect my user to my app (in the auth popup opened by IFTTT)
// and indicate to my app where to redirect the user if the user authorizes
// IFTTT to access his account.
// The actual code generation is done in /oauth/authorize_user, to which my
// app redirects my user once they've authorized IFTTT access.
// redirect_uri and state are carried over from
res.redirect(
APP_URL +
"/authorize_ifttt?state=" +
req.query.state +
"&redirect_uri=" +
encodeURIComponent(req.query.redirect_uri)
);
});
// After having authorized IFTTT access, the client makes this request.
// Now that we have the UID, we can generate an auth code for IFTTT and
// redirect with the state that IFTTT gave us at the beginning.
// We should probably check that he did set the permissions,
// in the RTDB for example.
app.get("/oauth/authorize_user", (req, res) => {
console.log(req.url);
var code = getAuthCodeWithUid(req.query.uid);
var redirect_uri = "https://ifttt.com/channels/mltest/authorize";
res.redirect(
redirect_uri +
"?code=" +
encodeURIComponent(code) +
"&state=" +
req.query.state
);
});
// Our standard OAuth token exchange endpoint.
// We'll take a code that was generated previously in the
// /generate_oauth_code or /oauth/authorize_user endpoint
app.post("/oauth/token", (req, res) => {
console.log(req.url);
console.log("Body code: " + req.body.code);
try {
// Decrypt the uid from the code
var uid = getUidFromAuthCode(req.body.code);
} catch (e) {
res.json(401, { error: "Invalid auth code" });
return;
}
// Re-encrypt our uid as the app OAuth access token
res.json({
token_type: "bearer",
access_token: getTokenWithUid(uid)
});
});
// ****************** END OAUTH SERVER ******************
// ****************** START IFTTT API ******************
app.get("/ifttt/v1/user/info", (req, res) => {
var bearer_token = req.header("Authorization").split(" ")[1];
try {
// Decrypt the uid from the code
var decrypted = getUidFromToken(bearer_token);
} catch (e) {
res.json(401, { error: "Invalid access token" });
}
var uid = decrypted;
console.log("Asking user info for " + uid);
admin
.auth()
.getUser(uid)
.then(userRecord => {
return res.json({
data: {
name: userRecord.displayName,
url: APP_URL,
id: userRecord.uid
}
});
})
.catch(e => {
return res.status(400).json({
errors: [
{
message: "Error: couldn't retrieve user " + uid + ". " + e.message
}
]
});
});
});
// ****************** END IFTTT API ******************
exports.iftttapi = functions.https.onRequest(app);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment