function requestJWTs()

in Modules/token-path.js [186:426]


function requestJWTs(event, callback) {
    //Retrieving the Authorization Request based on the device code provided by the client application
    var DynamoDBParams = {
        Key: {
            "Device_code": {
                S: event.queryStringParameters.device_code
            }
        },
        TableName: process.env.DYNAMODB_TABLE
    };
    common.dynamodb.getItem(DynamoDBParams, function(err, data) {
        if (err) { 
            //There was an error
            console.log("Error occured while retrieving device code");
            console.log(err, err.stack);
            common.returnJSONError(500, callback);
        } else {
            //Sucessful
            console.log("Sucessful request");
            //If Result Set has no value
            if (data.Item.length == 0) {
                console.log("No item matching device code exists");
                common.returnExpiredDeviceCodeError(callback);
            //If Result Set has more than one value
            } else if (data.Item.length > 1) {
                console.log("More than one device code has been returned");
                common.returnExpiredDeviceCodeError(callback);
            //If Result Set has only one value but it is with an "Expired" status
            } else if (data.Item.Status.S == "expired") {
                console.log("The Device code has already expired");
                common.returnExpiredDeviceCodeError(callback);
            //If Result Set has only one value, is not explicitely expired, but has not been requested initally by the same client application 
            } else if (data.Item.Client_id.S != event.queryStringParameters.client_id) {
                console.log("The Client id does not match the initial requestor client id");
                common.returnExpiredDeviceCodeError(callback);
            //If Result Set has only one value, is not explicitely expired, has been requested initally by the same client application, but has a lifetime older than the maximum lifetime
            } else if (Date.now() > parseInt(data.Item.Max_expiry.S)) {
                //Update status of the Authorization request to "Expired"
                console.log("The Device code has expired");
                DynamoDBParams = {
                    ExpressionAttributeNames: {
                        "#Status": "Status"
                    },
                    ExpressionAttributeValues: {
                        ":status": {
                            S: "expired"
                        }
                    }, 
                    Key: {
                        "Device_code": {
                            S: event.queryStringParameters.device_code
                        }
                    },
                    ReturnValues: "ALL_NEW", 
                    TableName: process.env.DYNAMODB_TABLE,
                    UpdateExpression: "SET #Status = :status"
                };
                common.dynamodb.updateItem(DynamoDBParams, function(err, data) {
                    if (err) {
                        //There was an error, so return an JSON error message the Code has expired
                        console.log("The Device code has expired, but an error occured when updating the DB");
                        console.log(err, err.stack);
                        common.returnExpiredDeviceCodeError(callback);
                    } else {
                        //Return an JSON error message the Code has expired
                        common.returnExpiredDeviceCodeError(callback);
                    }
                });
            //If Result Set has only one value, is not expired, has been requested initally by the same client application, but application client request a status too quickly
            } else if (Date.now() <= (parseInt(data.Item.Last_checked.S) + parseInt(process.env.POLLING_INTERVAL) * 1000) ) {
                //Update last checked timestamp of the Authorization request to Now
                DynamoDBParams = {
                    ExpressionAttributeNames: {
                        "#LC": "Last_checked"
                    },
                    ExpressionAttributeValues: {
                        ":lc": {
                            S: (Date.now()).toString()
                        }
                    }, 
                    Key: {
                        "Device_code": {
                            S: event.queryStringParameters.device_code
                        }
                    },
                    ReturnValues: "ALL_NEW", 
                    TableName: process.env.DYNAMODB_TABLE,
                    UpdateExpression: "SET #LC = :lc"
                };
                common.dynamodb.updateItem(DynamoDBParams, function(err, data) {
                    if (err) {
                        //There was an error, so return an JSON error message the client application has to slow down
                        console.log("Client makes too much API calls, but an error occured while updated the last check timestamp in the DB");
                        console.log(err, err.stack);
                        common.returnSlowDownError(callback);
                    } else {
                        //Return an JSON error message the client application has to slow down
                        console.log("Client makes too much API calls");
                        common.returnSlowDownError(callback);
                    }
                });
            //If all is good
            } else {
                //Must check the status
                //But first update last checked timestamp of the Authorization request to Now
                DynamoDBParams = {
                    ExpressionAttributeNames: {
                        "#LC": "Last_checked"
                    },
                    ExpressionAttributeValues: {
                        ":lc": {
                            S: (Date.now()).toString()
                        }
                    }, 
                    Key: {
                        "Device_code": {
                            S: event.queryStringParameters.device_code
                        }
                    },
                    ReturnValues: "ALL_NEW", 
                    TableName: process.env.DYNAMODB_TABLE,
                    UpdateExpression: "SET #LC = :lc"
                };
                common.dynamodb.updateItem(DynamoDBParams, function(err, data) {
                    if (err) {
                        //There was an error, so return an JSON error message the client application has to slow down
                        console.log("Client is on time for checking, but an error occured while updated the last check timestamp in the DB");
                        console.log(err, err.stack);
                        common.returnSlowDownError(callback);
                    }
                    else {
                        //Sucessfull
                        console.log("Client is on time for checking, we got a status");
                        //If the Status is authorization_pending or denied, return the status to the Client application
                        if (data.Attributes.Status.S == 'authorization_pending' || data.Attributes.Status.S == 'denied') {
                            console.log("Client is on time for checking, we got a status: " + data.Attributes.Status.S);
                            common.returnJSONErrorWithMsg(400, data.Attributes.Status.S, callback);
                        //If the Status is authorized
                        } else if (data.Attributes.Status.S == 'authorized') {
                            console.log("Client is on time for checking, we got a status: " + data.Attributes.Status.S);
                            console.log("Token Set is empty");
                            //Prepare the retrieving of JWT tokens from Cognito using the Athorization Code grant flow with PKCE
                            var options = {
                                hostname: process.env.CUP_DOMAIN + ".auth." + process.env.CUP_REGION + ".amazoncognito.com",
                                port: 443,
                                path: '/oauth2/token',
                                method: 'POST',
                                headers: {
                                    'Content-Type':  'application/x-www-form-urlencoded',
                                }
                            };
                            //If client application is private, has a Client Secret, and had provided it in the initial request, add it as an Authorization header to this request
                            if (event.headers.authorization != undefined) {
                                console.log("Setting Authorization header");
                                // Client knows authentication is required for Private Client, has been issued a Client secret, and therefore present an authentication header
                                    // Otherwise Client knows authentication is not necessary for Public Client or has made an error
                                options.headers.authorization = event.headers.authorization;
                            }
                            
                            console.log("Launching Request for Tokens");
                            //Request JWT tokens
                            const req = https.request(options, res => {
                                console.log('statusCode:', res.statusCode);
                                
                                //Reading request's response data
                                res.on('data', (d) => {
                                    //Prepare JWT Tokens blob
                                    if (d.error) {
                                        console.log("Cognito User Pool returned an error");
                                        common.returnExpiredDeviceCodeError(callback);
                                    } else {
                                        var result = JSON.parse(d.toString());
                                        var response = {}

                                        var rts = process.env.RESULT_TOKEN_SET.split('+');
                                        for (token_type in rts) {
                                            if (rts[token_type] == 'ID') response.id_token = result.id_token;
                                            if (rts[token_type] == 'ACCESS') response.access_token = result.access_token;
                                            if (rts[token_type] == 'REFRESH') response.refresh_token = result.refresh_token;
                                        }

                                        response.expires_in = result.expires_in;

                                        //Update the status of the Authorization request to "Denied" to prevent replay
                                        DynamoDBParams = {
                                            ExpressionAttributeNames: {
                                                "#Status": "Status"
                                            },
                                            ExpressionAttributeValues: {
                                                ":status": {
                                                    S: "expired"
                                                }
                                            }, 
                                            Key: {
                                                "Device_code": {
                                                    S: event.queryStringParameters.device_code
                                                }
                                            },
                                            ReturnValues: "ALL_NEW", 
                                            TableName: process.env.DYNAMODB_TABLE,
                                            UpdateExpression: "SET #Status = :status"
                                        };
                                        common.dynamodb.updateItem(DynamoDBParams, function(err, data) {
                                            if (err) {
                                                //There was an error, return expired message as Authroization code has been used
                                                console.log("We got the tokens but we got an error updating the DB");
                                                console.log(err, err.stack);
                                                common.returnExpiredDeviceCodeError(callback);
                                            } else {
                                                //Return the JWT tokens
                                                common.returnJSONSuccess(response, callback);
                                            }
                                        });
                                    }
                                });  
                            });
                            
                            //There was an error retrieving JWT Tokens
                            req.on('error', (e) => {
                                console.log("Got an error");
                                console.log(e);
                                common.returnExpiredDeviceCodeError(callback);
                            });
                            
                            //Writing Body of the request
                            req.write('grant_type=authorization_code&client_id=' + data.Attributes.Client_id.S + '&scope=' + data.Attributes.Scope.S  + '&redirect_uri=' + encodeURIComponent("https://" + process.env.CODE_VERIFICATION_URI + '/callback') + '&code=' + data.Attributes.AuthZ_code.S + '&code_verifier=' + data.Attributes.AuthZ_Verifier_code.S);
                            
                            //When request is finalized
                            req.end((e) => {
                                console.log("Finished");
                            });
                        //If Status is not suppoted
                        } else {
                            common.returnExpiredDeviceCodeError(callback);
                        }
                    }
                });
            }
        }
    });
}