简体   繁体   English

创建一个基本方法来保护 Firebase 中的 API?

[英]Create a basic method to secure your APIs in Firebase?

In most of the occasions we require that the access to our API's is restricted to certain users, for which we take into account some values like the method by which our API was called by the user, other we obtain them from the TOKEN that we receive from the user like the IAT (The time of emission of the identification token, in seconds since the Unix era. That is, the time in which this identification token was issued and must begin to be considered valid), custom claims, also if the user has parameters necessary to execute an action or if in the body of the user's call we have the necessary values.在大多数情况下,我们要求对我们的 API 的访问仅限于某些用户,为此我们考虑了一些值,例如用户调用 API 的方法,其他我们从我们收到的 TOKEN 中获取它们来自用户喜欢 IAT(识别令牌的发射时间,以 Unix 时代以来的秒数为单位。也就是说,这个识别令牌被发布并且必须开始被认为是有效的时间),自定义声明,如果用户具有执行操作所需的参数,或者如果在用户调用的主体中我们具有必要的值。

The creation of this code was to save me lines of code in each API that I created but without neglecting part of the security.创建此代码是为了节省我创建的每个 API 中的代码行,但不会忽略部分安全性。

For the moment in the CUSTOM CLAIM section I will use the lvl values which is a number if they have another claim we should make some small changes.目前在 CUSTOM CLAIM 部分,我将使用 lvl 值,这是一个数字,如果他们有另一个声明,我们应该进行一些小的更改。 However, next week I hope to update this code to be able to add the CUSTOM CLAIMS that we need to verify and if they have the level accepted with the option that they can decide if the level should be greater than, greater than or equal to, less than, less than or equal to, or just equal to, and I will also see if it is possible to allow custom claims with boolean values.但是,下周我希望更新此代码,以便能够添加我们需要验证的 CUSTOM CLAIMS,以及他们是否具有接受的级别以及他们可以决定级别是否应该大于、大于或等于的选项, 小于, 小于或等于, 或刚好等于, 我还将看看是否可以允许使用 boolean 值的自定义声明。

PS: If you have some kind of custom claim check that you want me to add, I will be working on it to make this basic code as complete as possible. PS:如果您希望我添加某种自定义声明检查,我将努力使此基本代码尽可能完整。 If you have doubts about how some parts work, ask me without problems.如果您对某些部件的工作方式有疑问,请毫无问题地问我。

-----UPDATED------ - - -更新 - - -

I will be uploading the updates to my github repository .我会将更新上传到我的github 存储库 Examples of how the function calls should be made will obviously change as needed, and I will be uploading them to my repository. function 调用的示例显然会根据需要进行更改,我会将它们上传到我的存储库。 In this stackoverflow post I will upload the notes of the changes made.在这篇 stackoverflow 帖子中,我将上传所做更改的注释。

FIX: Added the option to check parameters and body to a public api.修复:向公共 api 添加了检查参数和正文的选项。

CURRENT EXAMPLES DELETE/GET REQUESTS当前示例删除/获取请求

// DELETE-GET = DG
export const dgPUBLIC = functions.https.onRequest((request, response) =>         
{
    corsHandler(request, response, async () => {
        return await security.securityLayer(
            { _definedMethod: "GET", userValue: request.method },
            { _definedType: false },
            { required: false }, { required: false })
            .then((answer) => response.status(answer.status).send(answer));
    });
});

// DELETE-GET = DG
export const dgPRIVATE = functions.https.onRequest((request, response) => {
    corsHandler(request, response, async () => {
        return await security.securityLayer(
            { _definedMethod: "GET", userValue: request.method },
            {
                _definedType: true,
                _definedLevel: [4],
                _definedSeconds: 12,
                userToken: request.header("_token"),
            },
            { required: false }, { required: false })
            .then((answer) => response.status(answer.status).send(answer));
    });
});

// DELETE-GET = DG
export const dgPRIVATEwithPARAMS = functions.https.onRequest((request, response) => {
    corsHandler(request, response, async () => {
        // Params
        const _definedParams = ["item", "price"];
        const userParams = [];
        for (const iterator of _definedParams) {
            let value = null;
            if (request.body[iterator] !== undefined) {
                value = request.query[iterator];
            }
            userParams.push({ nameBody: iterator, valueBody: value });
        }
        return await security.securityLayer(
            { _definedMethod: "GET", userValue: request.method },
            {
                _definedType: true,
                _definedLevel: [4, 3],
                _definedSeconds: 12,
                userToken: request.header("_token"),
            },
            {
                required: true,
                _definedParams: _definedParams,
                userParams: userParams,
            },
            { required: false })
            .then((answer) => response.status(answer.status).send(answer));
    });
});

CURRENT EXAMPLES PATCH/POST/PUT REQUESTS当前示例 PATCH/POST/PUT 请求

// POST-PUT-PATCH === P3
export const p3PUBLICwithBODY = functions.https.onRequest((request, response) => {
    corsHandler(request, response, async () => {
        // Body
        const _definedBody = ["id", "name"];
        const userBody = [];
        for (const iterator of _definedBody) {
            let value = null;
            if (request.body[iterator] !== undefined) {
                value = request.body[iterator];
            }
            userBody.push({ nameBody: iterator, valueBody: value });
        }

        return await security.securityLayer(
            { _definedMethod: "POST", userValue: request.method },
            { _definedType: false },
            { required: false },
            {
                required: true,
                _definedBody: _definedBody,
                userBody: userBody,
            })
            .then((answer) => response.status(answer.status).send(answer));
    });
});

// POST-PUT-PATCH === P3
export const p3PRIVATEwithBODY = functions.https.onRequest((request, response) => {
    corsHandler(request, response, async () => {
        // Body
        const _definedBody = ["id", "name"];
        const userBody = [];
        for (const iterator of _definedBody) {
            let value = null;
            if (request.body[iterator] !== undefined) {
                value = request.body[iterator];
            }
            userBody.push({ nameBody: iterator, valueBody: value });
        }

        return await security.securityLayer(
            { _definedMethod: "POST", userValue: request.method },
            {
                _definedType: true,
                _definedLevel: [4],
                _definedSeconds: 12,
                userToken: request.header("_token"),
            },
            { required: false },
            {
                required: true,
                _definedBody: _definedBody,
                userBody: userBody,
            })
            .then((answer) => response.status(answer.status).send(answer));
    });
});

// POST-PUT-PATCH === P3
export const p3PUBLICwithPARAMSwithBODY =  functions.https.onRequest((request, response) => {
    corsHandler(request, response, async () => {
        // Params
        const _definedParams = ["item", "price"];
        const userParams = [];
        for (const iterator of _definedParams) {
            let value = null;
            if (request.body[iterator] !== undefined) {
                value = request.query[iterator];
            }
            userParams.push({ nameBody: iterator, valueBody: value });
        }

        // Body
        const _definedBody = ["id", "name"];
        const userBody = [];
        for (const iterator of _definedBody) {
            let value = null;
            if (request.body[iterator] !== undefined) {
                value = request.body[iterator];
            }
            userBody.push({ nameBody: iterator, valueBody: value });
        }

        return await security.securityLayer(
            { _definedMethod: "POST", userValue: request.method },
            { _definedType: false },
            {
                required: true,
                _definedParams: _definedParams,
                userParams: userParams,
            },
            {
                required: true,
                _definedBody: _definedBody,
                userBody: userBody,
            })
            .then((answer) => response.status(answer.status).send(answer));
    });
});

// POST-PUT-PATCH === P3
export const p3PRIVATEwithPARAMSwithBODY = functions.https.onRequest((request, response) => {
    corsHandler(request, response, async () => {
        // Params
        const _definedParams = ["item", "price"];
        const userParams = [];
        for (const iterator of _definedParams) {
            let value = null;
            if (request.body[iterator] !== undefined) {
                value = request.query[iterator];
            }
            userParams.push({ nameBody: iterator, valueBody: value });
        }

        // Body
        const _definedBody = ["id", "name"];
        const userBody = [];
        for (const iterator of _definedBody) {
            let value = null;
            if (request.body[iterator] !== undefined) {
                value = request.body[iterator];
            }
            userBody.push({ nameBody: iterator, valueBody: value });
        }

        return await security.securityLayer(
            { _definedMethod: "POST", userValue: request.method },
            {
                _definedType: true,
                _definedLevel: [4],
                _definedSeconds: 12,
                userToken: request.header("_token"),
            },
            {
                required: true,
                _definedParams: _definedParams,
                userParams: userParams,
            },
            {
                required: true,
                _definedBody: _definedBody,
                userBody: userBody,
            })
            .then((answer) => response.status(answer.status).send(answer));
    });
});

Don't use the code below, go to my repository and download it.不要使用下面的代码 go 到我的存储库并下载它。 The name of my file is security.ts我的文件名是 security.ts

***LEGACY CODE***


import initFx = require("../servidor/inicializar");
const admin = initFx.adminFirebase;
const firestore = initFx.firestoreOpts;

// Change if you need other numbers
type _definedLevel = (1 | 2 | 3 | 4 | 5);

/**
 * Basic function that allows me to protect my API (POST | PUT | PATCH).
 * Blocking unnecessary calls to my API that intend to make harmful or unauthorized changes to my resources.
 *
 * @param {object} apiMethod
 * The type of method by which my API will be called will be
 * defined and the method by which the user executes the call will be attached.
 * @example
 * "API POST call accepted"
 * { _definedMethod: "POST", userValue: "POST" } return "The method defined and the method performed by the user coincide."**
 *
 * "API PUT call rejected"
 * { _definedMethod: "PUT", userValue: "PATCH" } return "The method defined and the method performed by the user do not match."**
 *
 * **"The shown examples of returns do not follow the established definitions, for more information see the RETURNS section."
 *
 * @param {object} apiType
 * We define if the API is public or private, in which the public one does not need the TOKEN of the user and in the private one it is necessary to export the TOKEN of the user,
 * as well as we must also define the LEVEL of authorized access and the allowed seconds of expiration of the TOKEN based on the hour of the FIREBASE.
 * @example
 * "PUBLIC API"
 * { _definedType: false }
 *
 * "PRIVATE API: Only a level 4 user is allowed access to the API and their token must be no more than 10 seconds old."
 * { _definedType: true, _definedSeconds: 10, _definedLevel: [4], userToken: "EXAMPLE_TOKEN" }
 *
 * @param {object} apiParams We declare the part of the parameters that the API must contain, and export the parameters of the call by the user.
 * @example
 * "The parameters ARE necessary for the API"
 * {required: true, _definedParams: string[], userValue: {nameParam: string, valueParam: any[]}
 *
 * "The parameters ARE NOT required for the API"
 * {requires: false}
 *
 * // API call accepted
 * {required: true, _definedParams: ["uid", "center"], userValue: [{nameParam: "uid", valueParam: "a1b2c3"}, {nameParam: "center", valueParam: "center-001"}]}
 * return "Defined parameters match user parameters"**
 *
 * // API call rejected
 * {requires: true, _definedParams: ["uid", "center"], userValue: [{nameParams: "uid", valueParams: undefined}, {nameParams: "center", valueParams: "center-001"}]}
 * return "UID parameter does not match the parameter defined by the API"**
 *
 * **"The shown examples of returns do not follow the established definitions, for more information see the RETURNS section."
 *
 * @param {object} apiBody We declare the part of the body that should contain the API, and export the body of the call by the user.
 * @example
 * * "The body IS required for the API"
 * {required: true, _definedBody: string[], userValue: {nameBody: string, valueBody: any[]}
 *
 * "The body IS NOT required for API"
 * {requires: false}
 *
 * // API call accepted
 * {_definedBody: ["uid", "center"], userValue: [{nameBody: "uid", valueBody: "a1b2c3"}, {nameBody: "center", valueBody: "center-001"}]}
 * return "The defined body matches the user's body."**
 *
 * // API call rejected
 * {_definedBody: ["uid", "center"], userValue: [{nameBody: "uid", valueBody: undefined}, {nameBody: "center", valueBody: "center-001"}]}
 * return "The defined body does not match the user's body."
 *
 * **"The shown examples of returns do not follow the established definitions, for more information see the RETURNS section."
 *
 * @return {{status: number, code: string, subcode: number, devDescription: string}}
 * Returns whether or not it is possible to execute the function.
 *
 * @example
 * // API call accepted (201) - UNIQUE
 * return { status: 201, code: "x19f-S/S/securityLayer", subcode: 0, devDescription: "API has successfully passed all defined security guidelines." }
 *
 * // API call rejected (400) - 6 different responses
 * return { status: 400, code: "x1f-S/S/securityLayer", subcode: 1, devDescription: "API was not called by the previously defined method" };
 * return { status: 400, code: "x1f-S/S/securityLayer", subcode: 2, devDescription: "API does not contain TOKEN header" }
 * return { status: 400, code: "x1f-S/S/securityLayer", subcode: 3, devDescription: "User TOKEN is invalid.." };
 * return { status: 400, code: "x1f-S/S/securityLayer", subcode: 4, devDescription: "User's TOKEN expired " + timeRemaining + " seconds ago." };
 * return { status: 400, code: "x1f-S/S/securityLayer", subcode: 5, devDescription: "The PARAMETERS of the user call do not contain the PARAMETERS defined by the API." };
 * return { status: 400, code: "x1f-S/S/securityLayer", subcode: 6, devDescription: "The body of the user call does not contain the body defined by the API." };
 *
 * // API call rejected (401) - UNIQUE
 * return { status: 401, code: "x1f-S/S/securityLayer", subcode: 0, devDescription: "The user LEVEL is different from the LEVEL defined by the API. User LEVEL: " + levelUser }
 *
 */
export async function securityLayer(
    apiMethod: {
        _definedMethod: "GET" | "POST" | "PUT" | "PATCH" | "DELETE",
        userValue: string
    },
    apiType: { _definedType: false } |
    {
        _definedType: true,
        _definedSeconds: number,
        _definedLevel: _definedLevel[],
        userToken: string | undefined
    },
    apiParams: { required: false } |
    {
        required: true,
        _definedParams: string[],
        userParams: { nameBody: string, valueBody: any }[]
    },
    apiBody: { required: false } |
    {
        required: true,
        _definedBody: string[],
        userBody: { nameBody: string, valueBody: any }[]
    }
): Promise<{ status: number, code: string, subcode: number, devDescription: string }> {
        if (apiMethod.userValue !== apiMethod._definedMethod) {
    return {
        status: 400,
        code: "x1f-S/S/securityLayer",
        subcode: 1,
        devDescription:
            "API was not called by the previously defined method",
    };
} else {
    if (!apiType._definedType) {
        // Public API
        return {
            status: 201,
            code: "x19f-S/S/securityLayer",
            subcode: 0,
            devDescription:
                "API has successfully passed all defined security guidelines.",
        };
    } else {
        // Private API
        if (apiType.userToken === undefined) {
            return {
                status: 400,
                code: "x1f-S/S/securityLayer",
                subcode: 2,
                devDescription:
                    "API does not contain TOKEN header",
            };
        } else {
            return await admin.auth().verifyIdToken(apiType.userToken).then((decodedToken) => {
                const _definedTime = apiType._definedSeconds;
                const timeServer = firestore.Timestamp.now();
                const timeUser = decodedToken.iat;
                const _definedLevel = apiType._definedLevel;
                const levelUser = decodedToken.lvl;

                if (timeServer.seconds - timeUser > _definedTime) {
                    const timeRemaining = timeServer.seconds - timeUser - _definedTime;
                    return {
                        status: 400,
                        code: "x1f-S/S/securityLayer",
                        subcode: 4,
                        devDescription:
                            "User's TOKEN expired " + timeRemaining + " seconds ago.",
                    };
                }

                if (_definedLevel.indexOf(levelUser) === -1) {
                    return {
                        status: 401,
                        code: "x1f-S/S/securityLayer",
                        subcode: 0,
                        devDescription:
                            "The user's LEVEL does not appear in the LEVELS allowed by the API. User LEVEL: " + levelUser + ". User required: " + _definedLevel,
                    };
                }

                if (apiParams.required) {
                    for (const iterator of apiParams._definedParams) {
                        const item = apiParams.userParams.find((item) => item.nameBody === iterator);
                        if (item?.valueBody === null) {
                            return {
                                status: 400,
                                code: "x1f-S/S/securityLayer",
                                subcode: 5,
                                devDescription:
                                    "The PARAMETERS of the user call do not contain the PARAMETERS defined by the API.",
                            };
                        }
                    }
                }

                if (apiBody.required) {
                    for (const iterator of apiBody._definedBody) {
                        const item = apiBody.userBody.find((item) => item.nameBody === iterator);
                        if (item?.valueBody === null) {
                            return {
                                status: 400,
                                code: "x1f-S/S/securityLayer",
                                subcode: 6,
                                devDescription:
                                    "The body of the user call does not contain the body defined by the API.",
                            };
                        }
                    }
                }

                return {
                    status: 201,
                    code: "x19f-S/S/securityLayer",
                    subcode: 0,
                    devDescription:
                        "API has successfully passed all defined security guidelines.",
                };
            }).catch(() => {
                return {
                    status: 400,
                    code: "x1f-S/S/securityLayer",
                    subcode: 3,
                    devDescription:
                        "User TOKEN is invalid.",
                };
            });
        }
    }
}
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM