Skip to content

Commit

Permalink
Implement API auth route handlers, tests, and configuration
Browse files Browse the repository at this point in the history
Co-Authored-By: Brendan Quinn <[email protected]>
  • Loading branch information
kdid and bmquinn committed Oct 10, 2022
1 parent 0f3a4e9 commit 7bf3734
Show file tree
Hide file tree
Showing 12 changed files with 379 additions and 13 deletions.
20 changes: 10 additions & 10 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,13 @@ jobs:
AWS_SECRET_ACCESS_KEY: ci
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: 16.x
cache: 'npm'
- run: npm ci
- name: Check code style
run: npm run prettier
- name: Run tests
run: npm run test:coverage
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: 16.x
cache: "npm"
- run: npm ci
- name: Check code style
run: npm run prettier
- name: Run tests
run: npm run test:coverage
15 changes: 15 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

12 changes: 11 additions & 1 deletion src/aws/environment.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@ function apiToken() {
return jwt.sign(token, process.env.API_TOKEN_SECRET);
}

function dcApiEndpoint() {
return process.env.DC_API_ENDPOINT;
}

function elasticsearchEndpoint() {
return process.env.ELASTICSEARCH_ENDPOINT;
}
Expand All @@ -23,4 +27,10 @@ function region() {
return process.env.AWS_REGION || "us-east-1";
}

module.exports = { apiToken, elasticsearchEndpoint, prefix, region };
module.exports = {
apiToken,
dcApiEndpoint,
elasticsearchEndpoint,
prefix,
region,
};
59 changes: 59 additions & 0 deletions src/handlers/get-auth-callback.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
const axios = require("axios").default;
const cookie = require("cookie");
const jwt = require("jsonwebtoken");

/**
* NUSSO auth callback
*/
exports.handler = async (event) => {
const returnPath = cookie.parse(event.headers.Cookie, {
decode: function (token) {
return Buffer.from(token, "base64").toString("utf8");
},
})?.redirectUrl;

const user = await redeemSsoToken(event);
let response;
if (user) {
const token = jwt.sign(user, process.env.API_TOKEN_SECRET);
response = {
statusCode: 302,
headers: {
location: returnPath,
"set-cookie": cookie.serialize("dcApiV2Token", token, {
domain: "library.northwestern.edu",
path: "/",
secure: true,
}),
},
};
}

return response;
};

async function redeemSsoToken(event) {
const cookies = cookie.parse(event.headers.Cookie);

if (cookies.nusso) {
try {
const response = await axios.get(
`${process.env.NUSSO_BASE_URL}validate-with-directory-search-response`,
{
headers: {
apikey: process.env.NUSSO_API_KEY,
webssotoken: cookies.nusso,
},
}
);
const user = response.data.results[0];
return user;
} catch (err) {
console.error(err);
return null;
}
} else {
console.warn("No NUSSO token found in request");
return null;
}
}
50 changes: 50 additions & 0 deletions src/handlers/get-auth-login.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
const { dcApiEndpoint } = require("../aws/environment");
const axios = require("axios").default;
const cookie = require("cookie");

/**
* Performs NUSSO login
*/
exports.handler = async (event) => {
const callbackUrl = `${dcApiEndpoint()}/auth/callback`;
const url = `${process.env.NUSSO_BASE_URL}get-ldap-redirect-url`;
const returnPath =
event.queryStringParameters?.goto || event.headers?.Referer;

if (!returnPath) {
return {
statusCode: 400,
};
}

let resp;

await axios
.get(url, {
headers: {
apikey: process.env.NUSSO_API_KEY,
goto: callbackUrl,
},
})
.then((response) => {
resp = {
statusCode: 302,
headers: {
location: response.data.redirecturl,
"set-cookie": cookie.serialize("redirectUrl", returnPath, {
encode: function (token) {
return Buffer.from(token, "utf8").toString("base64");
},
}),
},
};
})
.catch((error) => {
console.error("NUSSO request error", error);
resp = {
statusCode: 401,
};
});

return resp;
};
28 changes: 28 additions & 0 deletions src/handlers/get-auth-whoami.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
const cookie = require("cookie");
const jwt = require("jsonwebtoken");

/**
* NUSSO whoami - validates JWT and returns user info
*/
exports.handler = async (event) => {
try {
const token = cookie.parse(event.headers.Cookie)?.dcApiV2Token;
const user = jwt.verify(token, process.env.API_TOKEN_SECRET);

return {
statusCode: 200,
headers: {
"Access-Control-Allow-Origin": event.headers.Origin,
"Access-Control-Allow-Headers": "*",
"Access-Control-Allow-Methods": "POST, GET, OPTIONS",
"Access-Control-Allow-Credentials": "true",
},
body: JSON.stringify(user),
};
} catch (error) {
return {
statusCode: 401,
body: "Error verifying API token: " + error.message,
};
}
};
14 changes: 14 additions & 0 deletions src/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions src/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
"@aws-sdk/signature-v4": "^3.130.0",
"axios": ">=0.21.1",
"base64-js": "^1.5.1",
"cookie": "^0.5.0",
"jsonwebtoken": "^8.5.1",
"lz-string": "^1.4.4"
}
Expand Down
87 changes: 85 additions & 2 deletions template.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,9 @@ Parameters:
CustomDomainHost:
Type: String
Description: Hostname within ApiDomainName for Custom Domain
DcApiEndpoint:
Type: String
Description: URL for DC API
DcUrl:
Type: String
Description: URL of Digital Collections website
Expand All @@ -33,6 +36,12 @@ Parameters:
Type: String
Description: Index Prefix
Default: ""
NussoApiKey:
Type: String
Description: API key for auth server
NussoBaseUrl:
Type: String
Description: Auth server URL
V1ApiId:
Type: String
Description: ID of the v1 API to mount on /api/v1
Expand All @@ -42,6 +51,80 @@ Parameters:
Default: latest
Resources:
# V2 API
getAuthCallbackFunction:
Type: AWS::Serverless::Function
Properties:
CodeUri: ./src
Handler: handlers/get-auth-callback.handler
Runtime: nodejs16.x
Architectures:
- x86_64
MemorySize: 128
Timeout: 100
Description: NUSSO callback function.
Environment:
Variables:
API_TOKEN_SECRET: !Ref ApiSecret
DC_API_ENDPOINT: !Ref DcApiEndpoint
DC_URL: !Ref DcUrl
NUSSO_API_KEY: !Ref NussoApiKey
NUSSO_BASE_URL: !Ref NussoBaseUrl
Events:
Api:
Type: HttpApi
Properties:
ApiId: !Ref dcApi
Path: /auth/callback
Method: GET
getAuthLoginFunction:
Type: AWS::Serverless::Function
Properties:
CodeUri: ./src
Handler: handlers/get-auth-login.handler
Runtime: nodejs16.x
Architectures:
- x86_64
MemorySize: 128
Timeout: 100
Description: Performs NUSSO login.
Environment:
Variables:
DC_API_ENDPOINT: !Ref DcApiEndpoint
DC_URL: !Ref DcUrl
NUSSO_API_KEY: !Ref NussoApiKey
NUSSO_BASE_URL: !Ref NussoBaseUrl
Events:
Api:
Type: HttpApi
Properties:
ApiId: !Ref dcApi
Path: /auth/login
Method: GET
getAuthWhoAmIFunction:
Type: AWS::Serverless::Function
Properties:
CodeUri: ./src
Handler: handlers/get-auth-whoami.handler
Runtime: nodejs16.x
Architectures:
- x86_64
MemorySize: 128
Timeout: 100
Description: Exchanges valid JWT token for user information.
Environment:
Variables:
API_TOKEN_SECRET: !Ref ApiTokenSecret
DC_API_ENDPOINT: !Ref DcApiEndpoint
DC_URL: !Ref DcUrl
NUSSO_API_KEY: !Ref NussoApiKey
NUSSO_BASE_URL: !Ref NussoBaseUrl
Events:
Api:
Type: HttpApi
Properties:
ApiId: !Ref dcApi
Path: /auth/whoami
Method: GET
getCollectionsFunction:
Type: AWS::Serverless::Function
Properties:
Expand Down Expand Up @@ -354,8 +437,8 @@ Resources:
- Sid: PublicReadForGetBucketObjects
Effect: Allow
Principal: "*"
Action: 's3:GetObject'
Resource: !Sub "arn:aws:s3:::${docsBucket}/*"
Action: "s3:GetObject"
Resource: !Sub "arn:aws:s3:::${docsBucket}/*"
Bucket: !Ref docsBucket
docsIntegration:
Type: AWS::ApiGatewayV2::Integration
Expand Down
Loading

0 comments on commit 7bf3734

Please sign in to comment.