Skip to content

Commit

Permalink
chore: add cypress tests
Browse files Browse the repository at this point in the history
  • Loading branch information
nsklikas committed Feb 13, 2025
1 parent ebfad8a commit cd8162e
Show file tree
Hide file tree
Showing 4 changed files with 271 additions and 0 deletions.
124 changes: 124 additions & 0 deletions cypress/integration/oauth2/device_auth.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
// Copyright © 2022 Ory Corp
// SPDX-License-Identifier: Apache-2.0

import { prng } from "../../helpers"

const accessTokenStrategies = ["opaque", "jwt"]

describe("The OAuth 2.0 Device Authorization Grant", function () {
accessTokenStrategies.forEach((accessTokenStrategy) => {
describe("access_token_strategy=" + accessTokenStrategy, function () {
const nc = (extradata) => ({
client_secret: prng(),
scope: "offline_access openid",
subject_type: "public",
token_endpoint_auth_method: "client_secret_basic",
grant_types: [
"urn:ietf:params:oauth:grant-type:device_code",
"refresh_token",
],
access_token_strategy: accessTokenStrategy,
...extradata,
})

it("should return an Access, Refresh, and ID Token when scope offline_access and openid are granted", function () {
const client = nc()
cy.deviceAuthFlow(client, {
consent: { scope: ["offline_access", "openid"] },
})

cy.get("body")
.invoke("text")
.then((content) => {
const {
result,
token: { access_token, id_token, refresh_token },
} = JSON.parse(content)

expect(result).to.equal("success")
expect(access_token).to.not.be.empty
expect(id_token).to.not.be.empty
expect(refresh_token).to.not.be.empty
})
})

it("should return an Access and Refresh Token when scope offline_access is granted", function () {
const client = nc()
cy.deviceAuthFlow(client, { consent: { scope: ["offline_access"] } })

cy.get("body")
.invoke("text")
.then((content) => {
const {
result,
token: { access_token, id_token, refresh_token },
} = JSON.parse(content)

expect(result).to.equal("success")
expect(access_token).to.not.be.empty
expect(id_token).to.be.undefined
expect(refresh_token).to.not.be.empty
})
})

it("should return an Access and ID Token when scope offline_access is granted", function () {
const client = nc()
cy.deviceAuthFlow(client, { consent: { scope: ["openid"] } })

cy.get("body")
.invoke("text")
.then((content) => {
const {
result,
token: { access_token, id_token, refresh_token },
} = JSON.parse(content)

expect(result).to.equal("success")
expect(access_token).to.not.be.empty
expect(id_token).to.not.be.empty
expect(refresh_token).to.be.undefined
})
})

it("should return an Access Token when no scope is granted", function () {
const client = nc()
cy.deviceAuthFlow(client, { consent: { scope: [] } })

cy.get("body")
.invoke("text")
.then((content) => {
const {
result,
token: { access_token, id_token, refresh_token },
} = JSON.parse(content)

expect(result).to.equal("success")
expect(access_token).to.not.be.empty
expect(id_token).to.be.undefined
expect(refresh_token).to.be.undefined
})
})

it("should skip consent if the client is confgured thus", function () {
const client = nc({ skip_consent: true })
cy.deviceAuthFlow(client, {
consent: { scope: ["offline_access", "openid"], skip: true },
})

cy.get("body")
.invoke("text")
.then((content) => {
const {
result,
token: { access_token, id_token, refresh_token },
} = JSON.parse(content)

expect(result).to.equal("success")
expect(access_token).to.not.be.empty
expect(id_token).to.not.be.empty
expect(refresh_token).to.not.be.empty
})
})
})
})
})
78 changes: 78 additions & 0 deletions cypress/support/commands.js
Original file line number Diff line number Diff line change
Expand Up @@ -216,3 +216,81 @@ Cypress.Commands.add("refreshTokenBrowser", (client, token) =>
failOnStatusCode: false,
}),
)

Cypress.Commands.add(
"deviceAuthFlow",
(
client,
{
override: { scope, client_id, client_secret } = {},
consent: {
accept: acceptConsent = true,
skip: skipConsent = false,
remember: rememberConsent = false,
scope: acceptScope = [],
} = {},
login: {
accept: acceptLogin = true,
skip: skipLogin = false,
remember: rememberLogin = false,
username = "foo@bar.com",
password = "foobar",
} = {},
prompt = "",
createClient: doCreateClient = true,
} = {},
path = "oauth2",
) => {
const run = (client) => {
cy.visit(
`${Cypress.env("client_url")}/${path}/device?client_id=${
client_id || client.client_id
}&client_secret=${client_secret || client.client_secret}&scope=${(
scope || client.scope
)}`,
{ failOnStatusCode: false },
)

cy.get("#verify").click()

if (!skipLogin) {
cy.get("#email").type(username, { delay: 1 })
cy.get("#password").type(password, { delay: 1 })

if (rememberLogin) {
cy.get("#remember").click()
}

if (acceptLogin) {
cy.get("#accept").click()
} else {
cy.get("#reject").click()
}
}

if (!skipConsent) {
acceptScope.forEach((s) => {
cy.get(`#${s}`).click()
})

if (rememberConsent) {
cy.get("#remember").click()
}

if (acceptConsent) {
cy.get("#accept").click()
} else {
cy.get("#reject").click()
}
}
}

if (doCreateClient) {
createClient(client).should((client) => {
run(client)
})
return
}
run(client)
},
)
2 changes: 2 additions & 0 deletions test/e2e/circle-ci.bash
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@ fi
(cd oauth2-client; PORT=5002 HYDRA_ADMIN_URL=http://127.0.0.1:5001 npm run consent > ../login-consent-logout.e2e.log 2>&1 &)

export URLS_SELF_ISSUER=http://127.0.0.1:5004/
export URLS_DEVICE_VERIFICATION=http://127.0.0.1:5002/device/code
export URLS_DEVICE_SUCCESS=http://127.0.0.1:5003/oauth2/device/success
export URLS_CONSENT=http://127.0.0.1:5002/consent
export URLS_LOGIN=http://127.0.0.1:5002/login
export URLS_LOGOUT=http://127.0.0.1:5002/logout
Expand Down
67 changes: 67 additions & 0 deletions test/e2e/oauth2-client/src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,73 @@ app.get("/oauth2/callback", async (req, res) => {
})
})

app.get("/oauth2/device", async (req, res) => {
const client = {
id: req.query.client_id,
secret: req.query.client_secret,

Check warning

Code scanning / CodeQL

Sensitive data read from GET request Medium test

Route handler
for GET requests uses query parameter as sensitive data.
}

const state = uuid.v4()
const scope = req.query.scope || ""

req.session.client = client
req.session.scope = scope.split(" ")

const params = new URLSearchParams()
params.append("client_id", req.query.client_id)
params.append("scope", scope)

let headers = new(Headers)
headers.set('Authorization', 'Basic ' + Buffer.from(req.query.client_id + ":" + req.query.client_secret).toString('base64'))

Check warning

Code scanning / CodeQL

Sensitive data read from GET request Medium test

Route handler
for GET requests uses query parameter as sensitive data.

fetch(new URL("/oauth2/device/auth", config.public).toString(), {
method: "POST",
body: params,
headers: headers,
})
.then(isStatusOk)
.then((res) => res.json())
.then((body) => {
// Store the device_code to use after authentication to get the tokens
req.session.device_code = body?.device_code
res.redirect(body?.verification_uri_complete)
})
.catch((err) => {
res.send(JSON.stringify({ error: err.toString() }))
})
})

app.get("/oauth2/device/success", async (req, res) => {
const clientId = req.session?.client?.id
const clientSecret = req.session?.client?.secret

if (clientId === undefined || clientSecret === undefined) {
res.send(JSON.stringify({ result: "error", error: "no client credentials in session" }))
return
}

const params = new URLSearchParams()
params.append("client_id", clientId)
params.append("device_code", req.session?.device_code)
params.append("grant_type", "urn:ietf:params:oauth:grant-type:device_code")
let headers = new(Headers)
headers.set('Authorization', 'Basic ' + Buffer.from(clientId + ":" + clientSecret).toString('base64'))

fetch(new URL("/oauth2/token", config.public).toString(), {
method: "POST",
body: params,
headers: headers,
})
.then(isStatusOk)
.then(resp => resp.json() )
.then(data => {
res.send({ result: "success", token: data })
})
.catch((err) => {
res.send(JSON.stringify({ error: err.toString() }))
})
})

app.get("/oauth2/refresh", function (req, res) {
oauth2
.create(req.session.credentials)
Expand Down

0 comments on commit cd8162e

Please sign in to comment.