Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Restreamer v2 support to secured streams with JWT #357

Closed
utilsites opened this issue Jun 6, 2022 · 3 comments
Closed

Restreamer v2 support to secured streams with JWT #357

utilsites opened this issue Jun 6, 2022 · 3 comments
Assignees
Labels

Comments

@utilsites
Copy link

Does Restreamer v2 has option to create private/secured streams, using some token in the url?
We implemented in v0.x a JWT token that's has some attributes like time allowed to play, ads on or off, etc.
It would be good that Restreamer v2 would have this kind of functionality, otherwise we have to clone v2 and do the same changes to support this (hope that the code is similar :)
If you are interested in this we can show how we did it and share the code.
Best regards

@ioppermann
Copy link
Member

This is currently not implemented, but it's on our roadmap. v2 has a completely different codebase from v0.x.

It would be a great help and might accelerate the development on our side if you could share the code how you solved this.

@svenerbeck svenerbeck assigned svenerbeck and ioppermann and unassigned svenerbeck Jun 7, 2022
@utilsites
Copy link
Author

Code with JWT support for limiting stream time play and show or not ads, for Restreamer 0.x, need to be adapted for v2, it would be very good if you reviewed this code and implemented/adapted this solution on/to v2. This solution works very well now, its stable.

The main website passes a jwt token in the query string for restreamer player.html page, that is validated (has time limit to be valid) and if it allows ads or not (in a inside attribute). The player.html page passes the jwt token to the other urls used to obtain the stream video file and be validated again.

/* FILE: webserver/app.js  (part) */

    initProd() {
        const self = this;
        logger.debug("Init webserver with PROD environment");
        this.initAlways();

        this.app.get("/", async (req, res) => {
            res.sendFile(path.join(global.__public, "index.prod.html"));
        });

        this.app.get("/player.html", async (req, res) => {
            const jwt = req.query.jwt;
            if (jwt) {
                try {
                    let decodedJWT = await self.authLib.verifyToken(jwt);
                    if (decodedJWT.show_video_ads == "1") {
                        let templateFilePath = path.join(global.__public, "playerAdTemplate.html");
                        fs.readFile(templateFilePath, "utf8", function (err, data) {
                            if (err) {
                                return console.log(err);
                            }
                            var result = data.replace("tag: ''",  "tag: '" + process.env.RS_AD_TAG + "'");
                            result = result.replace("hls/live.stream.m3u8", "hls/live.stream.m3u8?jwt=" + jwt);

                            res.send(result);
                            res.end();
                        });
                    } else {
                        let defaultFilePath = path.join(global.__public, "player.html")
                        fs.readFile(defaultFilePath, "utf8", function (err, data) {
                            if (err) {
                                return console.log(err);
                            }
                            let result = data.replace("hls/live.stream.m3u8", "hls/live.stream.m3u8?jwt=" + jwt);

                            res.send(result);
                            res.end();
                        });
                    }
                } catch (err) {
                    logger.info("Error app.js: " + err, false);
                }
            } else {
                res.sendFile(path.join(global.__public, "player.html"));
            }
        });
        this.add404ErrorHandling();
        this.add500ErrorHandling();
    }


 /* FILE: webserver/controllers/index.js (part)
   * Handle HLS serving files */
   
    restreamer.app.get("/hls/:file", async (req, res) => {
        const file = req.params.file;
        var ext = file.substr(file.lastIndexOf(".") + 1);

        // check file extension
        if (ext != "ts" && ext != "m3u8") {
            res.writeHead(401, {
                "Content-Type": "text/plain",
            });
            res.end("Unauthorized");
            return;
        }

        // check jwt
        const token = req.query.jwt || "";
        if (ext == "m3u8") {
            if (token) {
                try {
                    await restreamer.authLib.verifyToken(token);
                } catch (err) {
                    res.writeHead(401, {
                        "Content-Type": "text/plain",
                    });
                    res.end("Unauthorized");
                    return;
                }
            } else {
                res.writeHead(401, {
                    "Content-Type": "text/plain",
                });
                res.end("Unauthorized");
                return;
            }
        }

        res.sendFile(`/tmp/hls/${req.params.file}`);
    });


 /*** FILE: classes/Auth.js (part) ****
   *** JWT util lib ***/
   
const jwt = require("jsonwebtoken");
const logger = require.main.require("./classes/Logger")("Webserver");

class Auth {
    constructor(jwt_secret, jwt_expiration_seconds) {
        this.jwt_secret = jwt_secret;
        this.jwt_expiration_seconds = jwt_expiration_seconds;
    }

    signToken(id) {
        const self = this;
        return new Promise((resolve, reject) => {
            jwt.sign(
                { id: id },
                self.jwt_secret,
                {
                    expiresIn: self.jwt_expiration_seconds,
                },
                (err, token) => {
                    if (err) {
                        logger.error('Error sign Auth.js: ' + err, false);
                        reject(err);
                        return;
                    }
                    resolve(token);
                }
            );
        });
    }

    verifyToken(token) {
        const self = this;
        return new Promise((resolve, reject) => {
            jwt.verify(token, self.jwt_secret, (err, decoded) => {
                if (err) {
                    logger.error('Error verify Auth.js: ' + err, false);
                    reject(err);
                    return;
                }
                resolve(decoded);
            });
        });
    }
}

module.exports = Auth;

 /*** FILE: package.json (part) ****/
"dependencies": {
        "jsonwebtoken": "^8.5.1",
	...
}

/*** FILE conf/live.json (part) ****/
added new variable: RS_JWT_SECRET_KEY


@utilsites utilsites changed the title Restreamer v2 support to secured streams Restreamer v2 support to secured streams with JWT Jul 19, 2022
@svenerbeck
Copy link
Member

Hello

We are closing your ticket #357.

This may be due to the following reasons:

  • Problem/inquiry has been solved
  • Ticket remained unanswered by you for a more extended period
  • Problem was explained and handled in another ticket

You can reopen this ticket at any time!

Please only open related tickets once! Always answer/ask in the original ticket with the same issue!

With kind regards,
Your datarhei team

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

3 participants