sf-oauth
Version:
Utility to generate SuccessFactors OAuth SAML assertions with the option to integrate with Postman
192 lines (180 loc) • 7.71 kB
JavaScript
const express = require('express')
const { generate } = require('./gen')
var bodyParser = require('body-parser')
const log = require('./logger')
const serve = (port = 3000) => {
const app = express()
app.use(bodyParser.json())
app.use(bodyParser.urlencoded({ extended: true }))
app.post('/authorize', async (req, res) => {
const userId = req.body?.user_id
const clientId = req.body?.client_id
const hostname = req.body?.scope
const companyId = req.body?.state
const redirectUrl = decodeURI(req.body?.redirect_uri)
generateToken(req, res, clientId, userId, hostname, companyId, redirectUrl)
})
app.get('/authorize', async (req, res) => {
const userId = req.query?.user_id
if (!userId) {
let sHtml = `
<html>
<head>
<style>
body {
background: #f2f2f2;
font-family: 'Open Sans',sans-serif;
font-size: 14px;
}
.pm-main-app-login-success {
color: #505050;
line-height: 24px;
text-align: center;
margin-top: 100px;
}
.pm-main-app-login-success > h1 {
font-size: 16px;
}
.pm-main-app-login-success > p {
font-size: 14px;
margin: 0;
}
</style>
</head>
<body>
<div class="pm-main-app-login-success ">
<svg height="100" width="100" version="1.1" viewBox="0 0 512 512" fill="none" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"
xml:space="preserve">
<g>
<g>
<path style="fill:#FF4F19;" d="M400.764,402.012l-18.14-108.591h-0.053c-2.519-14.684-12.614-29.89-29.801-34.702
c-0.212-0.06-0.424-0.106-0.642-0.137l-42.323-6.351c-0.259-0.039-0.812-0.06-1.077-0.06c-2.677,0-4.935,1.996-5.261,4.655
c-2.253,18.298-17.382,32.665-36.85,36.595h-21.241c-19.47-3.93-34.599-18.295-36.847-36.595
c-0.332-2.736-2.796-4.794-5.396-4.652c-0.311-0.018-0.616,0.008-0.942,0.057l-42.323,6.351c-0.217,0.031-0.43,0.078-0.642,0.137
c-17.188,4.812-27.283,20.019-29.801,34.702h-0.053l-18.14,108.591C107.479,421.731,122.597,440,142.67,440H144h224h1.33
C389.403,440,404.52,421.731,400.764,402.012z"/>
</g>
<g>
<path style="fill:#FDC88E;" d="M256,246.87c-18.52,0-35.322-7.947-47.719-20.791c-0.009,7.698-0.289,18.808-2.33,26.915
c1.339,0.818,2.378,2.162,2.58,3.833c2.646,21.529,23.051,37.761,47.469,37.761c24.413,0,44.818-16.235,47.469-37.761
c0.206-1.674,1.222-3.015,2.581-3.831c-2.041-8.107-2.322-19.219-2.331-26.917C291.322,238.923,274.519,246.87,256,246.87z"/>
</g>
<g>
<path style="fill:#FFB980;" d="M302.684,227.056C290.39,239.301,274.025,246.87,256,246.87c-18.52,0-35.322-7.947-47.719-20.791
c-0.009,7.698-0.289,18.808-2.33,26.915c1.339,0.818,2.378,2.162,2.58,3.833c1.17,9.516,5.817,17.989,12.74,24.491
C253.194,276.822,289.426,245.3,302.684,227.056z"/>
</g>
<g>
<path style="fill:#FFE1B2;" d="M303.717,114.321h-90.133c-14.617,0-26.51,11.893-26.51,26.51v31.812
c0,40.929,30.921,74.227,68.925,74.227s68.925-33.298,68.925-74.227v-37.114C324.925,123.835,315.414,114.321,303.717,114.321z"/>
</g>
<g>
<path style="fill:#7E5449;" d="M293.113,56h-68.925c-9.972,0-18.355,6.917-20.612,16.204
c-18.251,2.32-32.407,17.946-32.407,36.816v26.51c0,13.664,5.801,26.411,15.906,35.47v-30.168c0-14.617,11.893-26.51,26.51-26.51
h90.133c11.696,0,21.208,9.514,21.208,21.208v35.464c10.107-9.057,15.906-21.804,15.906-35.464v-31.812
C340.831,77.407,319.426,56,293.113,56z"/>
</g>
<g>
<path style="fill:#D8DCE1;" d="M352,440H160c-4.418,0-8-3.582-8-8V328c0-4.418,3.582-8,8-8h192c4.418,0,8,3.582,8,8v104
C360,436.418,356.418,440,352,440z"/>
</g>
<g>
<g>
<path style="fill:#9F6459;" d="M504,456H8c-4.422,0-8-3.582-8-8s3.578-8,8-8h496c4.422,0,8,3.582,8,8S508.422,456,504,456z"/>
</g>
</g>
<g>
<circle style="fill:#FFFFFF;" cx="256" cy="384" r="16"/>
</g>
</g>
</svg>
<h1>SuccessFactors userId:</h1>
<form method="post" action="/authorize" autocomplete="on">
<input type="text" name="user_id" required>
<input id="client_id" type="hidden" name="client_id">
<input id="state" type="hidden" name="state">
<input id="scope" type="hidden" name="scope">
<input id="redirect_uri" type="hidden" name="redirect_uri">
<input type="submit" value="Continue">
</form>
</div>
<script>
const queryString = window.location.search
const urlParams = new URLSearchParams(queryString)
document.getElementById('client_id').value = urlParams.get('client_id')
document.getElementById('state').value = urlParams.get('state')
document.getElementById('scope').value = urlParams.get('scope')
document.getElementById('redirect_uri').value = urlParams.get('redirect_uri')
</script>
</body>
</html>`
res.set('Content-Type', 'text/html')
res.end(sHtml)
} else {
const clientId = req.query?.client_id
const hostname = req.query?.scope
const companyId = req.query?.state
const redirectUrl = decodeURI(req.query?.redirect_uri)
generateToken(
req,
res,
clientId,
userId,
hostname,
companyId,
redirectUrl
)
}
})
app.get('/', async (req, res) => {
res.status(400).send('Time to take a look at README.md 😉')
})
app.listen(port, () => {
log.info(
`🚀 SAML Assertion OAuth access token generator listening on port ${port}`
)
})
}
async function generateToken(
req,
res,
clientId,
userId,
hostname,
companyId,
redirectUrl
) {
let signedAssertionB64 = generate(clientId, userId, hostname, companyId)
let params = new URLSearchParams()
params.append('client_id', clientId)
params.append('grant_type', 'urn:ietf:params:oauth:grant-type:saml2-bearer')
params.append('company_id', companyId)
params.append('assertion', signedAssertionB64)
try {
const response = await fetch(`https://${hostname}/oauth/token`, {
method: 'POST',
body: params,
})
if (response.status != 200) {
const oErrorResponse = await response.json()
const sStatusMessage = oErrorResponse?.errorMessage
? oErrorResponse.errorMessage
: response.statusText
res.status(response.status).send(sStatusMessage)
} else {
const oToken = await response.json()
if (redirectUrl) {
res.redirect(
`${redirectUrl}?access_token=${oToken.access_token}&token_type=${oToken.token_type}&expires_in=${oToken.expires_in}`
)
} else {
res.send(oToken)
}
}
} catch (error) {
log.error(error)
res.status(500).send(error.message)
}
}
module.exports = serve