This commit is contained in:
parent
77fedd2815
commit
51a8609880
6
package-lock.json
generated
6
package-lock.json
generated
@ -2361,6 +2361,12 @@
|
|||||||
"integrity": "sha1-1PRWKwzjaW5BrFLQ4ALlemNdxtw=",
|
"integrity": "sha1-1PRWKwzjaW5BrFLQ4ALlemNdxtw=",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"prettier": {
|
||||||
|
"version": "2.0.5",
|
||||||
|
"resolved": "https://npm.hibas123.de/prettier/-/prettier-2.0.5.tgz",
|
||||||
|
"integrity": "sha512-7PtVymN48hGcO4fGjybyBSIWDsLU4H4XlvOHfq91pz9kkGlonzwTfYkaIEwiRg/dAJF9YlbsduBAgtYLi+8cFg==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
"process-nextick-args": {
|
"process-nextick-args": {
|
||||||
"version": "2.0.1",
|
"version": "2.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz",
|
||||||
|
@ -19,7 +19,8 @@
|
|||||||
"watch-views": "cd views && npm run watch",
|
"watch-views": "cd views && npm run watch",
|
||||||
"install-views_repo": "git submodule init && git submodule update && cd views_repo && npm install ",
|
"install-views_repo": "git submodule init && git submodule update && cd views_repo && npm install ",
|
||||||
"build-views_repo": "cd views_repo && npm run build",
|
"build-views_repo": "cd views_repo && npm run build",
|
||||||
"watch-views_repo": "cd views_repo && npm run dev"
|
"watch-views_repo": "cd views_repo && npm run dev",
|
||||||
|
"format": "prettier --write ."
|
||||||
},
|
},
|
||||||
"pipelines": {
|
"pipelines": {
|
||||||
"install": [
|
"install": [
|
||||||
@ -47,6 +48,7 @@
|
|||||||
"apidoc": "^0.20.0",
|
"apidoc": "^0.20.0",
|
||||||
"concurrently": "^5.1.0",
|
"concurrently": "^5.1.0",
|
||||||
"nodemon": "^2.0.2",
|
"nodemon": "^2.0.2",
|
||||||
|
"prettier": "^2.0.5",
|
||||||
"typescript": "^3.8.3"
|
"typescript": "^3.8.3"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
@ -5,7 +5,6 @@ import Client from "../../models/client";
|
|||||||
import verify, { Types } from "../middlewares/verify";
|
import verify, { Types } from "../middlewares/verify";
|
||||||
import { randomBytes } from "crypto";
|
import { randomBytes } from "crypto";
|
||||||
|
|
||||||
|
|
||||||
const ClientRouter: Router = Router();
|
const ClientRouter: Router = Router();
|
||||||
ClientRouter.route("/")
|
ClientRouter.route("/")
|
||||||
/**
|
/**
|
||||||
@ -26,11 +25,13 @@ ClientRouter.route("/")
|
|||||||
* @apiSuccess {String} clients.client_id Client ID used outside of DB
|
* @apiSuccess {String} clients.client_id Client ID used outside of DB
|
||||||
* @apiSuccess {String} clients.client_secret
|
* @apiSuccess {String} clients.client_secret
|
||||||
*/
|
*/
|
||||||
.get(promiseMiddleware(async (req, res) => {
|
.get(
|
||||||
|
promiseMiddleware(async (req, res) => {
|
||||||
let clients = await Client.find({});
|
let clients = await Client.find({});
|
||||||
//ToDo check if user is required!
|
//ToDo check if user is required!
|
||||||
res.json(clients);
|
res.json(clients);
|
||||||
}))
|
})
|
||||||
|
)
|
||||||
/**
|
/**
|
||||||
* @api {get} /admin/client
|
* @api {get} /admin/client
|
||||||
* @apiName AdminAddClients
|
* @apiName AdminAddClients
|
||||||
@ -55,31 +56,37 @@ ClientRouter.route("/")
|
|||||||
* @apiSuccess {String} clients.client_id Client ID used outside of DB
|
* @apiSuccess {String} clients.client_id Client ID used outside of DB
|
||||||
* @apiSuccess {String} clients.client_secret
|
* @apiSuccess {String} clients.client_secret
|
||||||
*/
|
*/
|
||||||
.post(verify({
|
.post(
|
||||||
|
verify(
|
||||||
|
{
|
||||||
internal: {
|
internal: {
|
||||||
type: Types.BOOLEAN,
|
type: Types.BOOLEAN,
|
||||||
optional: true
|
optional: true,
|
||||||
},
|
},
|
||||||
name: {
|
name: {
|
||||||
type: Types.STRING
|
type: Types.STRING,
|
||||||
},
|
},
|
||||||
redirect_url: {
|
redirect_url: {
|
||||||
type: Types.STRING
|
type: Types.STRING,
|
||||||
},
|
},
|
||||||
website: {
|
website: {
|
||||||
type: Types.STRING
|
type: Types.STRING,
|
||||||
},
|
},
|
||||||
logo: {
|
logo: {
|
||||||
type: Types.STRING,
|
type: Types.STRING,
|
||||||
optional: true
|
optional: true,
|
||||||
}
|
},
|
||||||
}, true), promiseMiddleware(async (req, res) => {
|
},
|
||||||
|
true
|
||||||
|
),
|
||||||
|
promiseMiddleware(async (req, res) => {
|
||||||
req.body.client_secret = randomBytes(32).toString("hex");
|
req.body.client_secret = randomBytes(32).toString("hex");
|
||||||
let client = Client.new(req.body);
|
let client = Client.new(req.body);
|
||||||
client.maintainer = req.user._id;
|
client.maintainer = req.user._id;
|
||||||
await Client.save(client)
|
await Client.save(client);
|
||||||
res.json(client);
|
res.json(client);
|
||||||
}))
|
})
|
||||||
|
);
|
||||||
|
|
||||||
ClientRouter.route("/:id")
|
ClientRouter.route("/:id")
|
||||||
/**
|
/**
|
||||||
@ -92,11 +99,13 @@ ClientRouter.route("/:id")
|
|||||||
*
|
*
|
||||||
* @apiSuccess {Boolean} success
|
* @apiSuccess {Boolean} success
|
||||||
*/
|
*/
|
||||||
.delete(promiseMiddleware(async (req, res) => {
|
.delete(
|
||||||
|
promiseMiddleware(async (req, res) => {
|
||||||
let { id } = req.params;
|
let { id } = req.params;
|
||||||
await Client.delete(id);
|
await Client.delete(id);
|
||||||
res.json({ success: true });
|
res.json({ success: true });
|
||||||
}))
|
})
|
||||||
|
)
|
||||||
/**
|
/**
|
||||||
* @api {put} /admin/client/:id
|
* @api {put} /admin/client/:id
|
||||||
* @apiParam {String} id Client _id
|
* @apiParam {String} id Client _id
|
||||||
@ -121,37 +130,46 @@ ClientRouter.route("/:id")
|
|||||||
* @apiSuccess {String} client_id Client ID used outside of DB
|
* @apiSuccess {String} client_id Client ID used outside of DB
|
||||||
* @apiSuccess {String} client_secret The client secret, that can be used to obtain token
|
* @apiSuccess {String} client_secret The client secret, that can be used to obtain token
|
||||||
*/
|
*/
|
||||||
.put(verify({
|
.put(
|
||||||
|
verify(
|
||||||
|
{
|
||||||
internal: {
|
internal: {
|
||||||
type: Types.BOOLEAN,
|
type: Types.BOOLEAN,
|
||||||
optional: true
|
optional: true,
|
||||||
},
|
},
|
||||||
name: {
|
name: {
|
||||||
type: Types.STRING,
|
type: Types.STRING,
|
||||||
optional: true
|
optional: true,
|
||||||
},
|
},
|
||||||
redirect_url: {
|
redirect_url: {
|
||||||
type: Types.STRING,
|
type: Types.STRING,
|
||||||
optional: true
|
optional: true,
|
||||||
},
|
},
|
||||||
website: {
|
website: {
|
||||||
type: Types.STRING,
|
type: Types.STRING,
|
||||||
optional: true
|
optional: true,
|
||||||
},
|
},
|
||||||
logo: {
|
logo: {
|
||||||
type: Types.STRING,
|
type: Types.STRING,
|
||||||
optional: true
|
optional: true,
|
||||||
}
|
},
|
||||||
}, true), promiseMiddleware(async (req, res) => {
|
},
|
||||||
|
true
|
||||||
|
),
|
||||||
|
promiseMiddleware(async (req, res) => {
|
||||||
let { id } = req.query;
|
let { id } = req.query;
|
||||||
let client = await Client.findById(id);
|
let client = await Client.findById(id);
|
||||||
if (!client) throw new RequestError(req.__("Client not found"), HttpStatusCode.BAD_REQUEST);
|
if (!client)
|
||||||
|
throw new RequestError(
|
||||||
|
req.__("Client not found"),
|
||||||
|
HttpStatusCode.BAD_REQUEST
|
||||||
|
);
|
||||||
for (let key in req.body) {
|
for (let key in req.body) {
|
||||||
client[key] = req.body[key];
|
client[key] = req.body[key];
|
||||||
}
|
}
|
||||||
await Client.save(client);
|
await Client.save(client);
|
||||||
res.json(client);
|
res.json(client);
|
||||||
}))
|
})
|
||||||
|
);
|
||||||
|
|
||||||
export default ClientRouter;
|
export default ClientRouter;
|
@ -9,12 +9,16 @@ import RequestError, { HttpStatusCode } from "../../helper/request_error";
|
|||||||
const AdminRoute: Router = Router();
|
const AdminRoute: Router = Router();
|
||||||
|
|
||||||
AdminRoute.use(GetUserMiddleware(true, true), (req: Request, res, next) => {
|
AdminRoute.use(GetUserMiddleware(true, true), (req: Request, res, next) => {
|
||||||
if (!req.isAdmin) throw new RequestError("You have no permission to access this API", HttpStatusCode.FORBIDDEN);
|
if (!req.isAdmin)
|
||||||
else next()
|
throw new RequestError(
|
||||||
|
"You have no permission to access this API",
|
||||||
|
HttpStatusCode.FORBIDDEN
|
||||||
|
);
|
||||||
|
else next();
|
||||||
});
|
});
|
||||||
|
|
||||||
AdminRoute.use("/client", ClientRoute);
|
AdminRoute.use("/client", ClientRoute);
|
||||||
AdminRoute.use("/regcode", RegCodeRoute)
|
AdminRoute.use("/regcode", RegCodeRoute);
|
||||||
AdminRoute.use("/user", UserRoute)
|
AdminRoute.use("/user", UserRoute);
|
||||||
AdminRoute.use("/permission", PermissionRoute);
|
AdminRoute.use("/permission", PermissionRoute);
|
||||||
export default AdminRoute;
|
export default AdminRoute;
|
@ -56,18 +56,18 @@ PermissionRoute.route("/")
|
|||||||
verify(
|
verify(
|
||||||
{
|
{
|
||||||
client: {
|
client: {
|
||||||
type: Types.STRING
|
type: Types.STRING,
|
||||||
},
|
},
|
||||||
name: {
|
name: {
|
||||||
type: Types.STRING
|
type: Types.STRING,
|
||||||
},
|
},
|
||||||
description: {
|
description: {
|
||||||
type: Types.STRING
|
type: Types.STRING,
|
||||||
},
|
},
|
||||||
type: {
|
type: {
|
||||||
type: Types.ENUM,
|
type: Types.ENUM,
|
||||||
values: ["user", "client"]
|
values: ["user", "client"],
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
true
|
true
|
||||||
),
|
),
|
||||||
@ -83,7 +83,7 @@ PermissionRoute.route("/")
|
|||||||
description: req.body.description,
|
description: req.body.description,
|
||||||
name: req.body.name,
|
name: req.body.name,
|
||||||
client: client._id,
|
client: client._id,
|
||||||
grant_type: req.body.type
|
grant_type: req.body.type,
|
||||||
});
|
});
|
||||||
await Permission.save(permission);
|
await Permission.save(permission);
|
||||||
res.json(permission);
|
res.json(permission);
|
||||||
|
@ -21,10 +21,12 @@ RegCodeRoute.route("/")
|
|||||||
* @apiSuccess {String} permissions.valid Defines if the Regcode is valid
|
* @apiSuccess {String} permissions.valid Defines if the Regcode is valid
|
||||||
* @apiSuccess {String} permissions.validTill Expiration date of RegCode
|
* @apiSuccess {String} permissions.validTill Expiration date of RegCode
|
||||||
*/
|
*/
|
||||||
.get(promiseMiddleware(async (req, res) => {
|
.get(
|
||||||
|
promiseMiddleware(async (req, res) => {
|
||||||
let regcodes = await RegCode.find({});
|
let regcodes = await RegCode.find({});
|
||||||
res.json(regcodes);
|
res.json(regcodes);
|
||||||
}))
|
})
|
||||||
|
)
|
||||||
/**
|
/**
|
||||||
* @api {delete} /admin/regcode
|
* @api {delete} /admin/regcode
|
||||||
* @apiName AdminDeleteRegcode
|
* @apiName AdminDeleteRegcode
|
||||||
@ -36,11 +38,13 @@ RegCodeRoute.route("/")
|
|||||||
*
|
*
|
||||||
* @apiSuccess {Boolean} success
|
* @apiSuccess {Boolean} success
|
||||||
*/
|
*/
|
||||||
.delete(promiseMiddleware(async (req, res) => {
|
.delete(
|
||||||
|
promiseMiddleware(async (req, res) => {
|
||||||
let { id } = req.query;
|
let { id } = req.query;
|
||||||
await RegCode.delete(id);
|
await RegCode.delete(id);
|
||||||
res.json({ success: true });
|
res.json({ success: true });
|
||||||
}))
|
})
|
||||||
|
)
|
||||||
/**
|
/**
|
||||||
* @api {post} /admin/regcode
|
* @api {post} /admin/regcode
|
||||||
* @apiName AdminAddRegcode
|
* @apiName AdminAddRegcode
|
||||||
@ -50,14 +54,16 @@ RegCodeRoute.route("/")
|
|||||||
*
|
*
|
||||||
* @apiSuccess {String} code The newly created code
|
* @apiSuccess {String} code The newly created code
|
||||||
*/
|
*/
|
||||||
.post(promiseMiddleware(async (req, res) => {
|
.post(
|
||||||
|
promiseMiddleware(async (req, res) => {
|
||||||
let regcode = RegCode.new({
|
let regcode = RegCode.new({
|
||||||
token: randomBytes(10).toString("hex"),
|
token: randomBytes(10).toString("hex"),
|
||||||
valid: true,
|
valid: true,
|
||||||
validTill: moment().add("1", "month").toDate()
|
validTill: moment().add("1", "month").toDate(),
|
||||||
})
|
});
|
||||||
await RegCode.save(regcode);
|
await RegCode.save(regcode);
|
||||||
res.json({ code: regcode.token });
|
res.json({ code: regcode.token });
|
||||||
}))
|
})
|
||||||
|
);
|
||||||
|
|
||||||
export default RegCodeRoute;
|
export default RegCodeRoute;
|
@ -9,9 +9,9 @@ import LoginToken from "../../models/login_token";
|
|||||||
|
|
||||||
const UserRoute: Router = Router();
|
const UserRoute: Router = Router();
|
||||||
UserRoute.use(GetUserMiddleware(true, true), (req: Request, res, next) => {
|
UserRoute.use(GetUserMiddleware(true, true), (req: Request, res, next) => {
|
||||||
if (!req.isAdmin) res.sendStatus(HttpStatusCode.FORBIDDEN)
|
if (!req.isAdmin) res.sendStatus(HttpStatusCode.FORBIDDEN);
|
||||||
else next()
|
else next();
|
||||||
})
|
});
|
||||||
|
|
||||||
UserRoute.route("/")
|
UserRoute.route("/")
|
||||||
/**
|
/**
|
||||||
@ -29,11 +29,15 @@ UserRoute.route("/")
|
|||||||
* @apiSuccess {Number} user.gender 0 = none, 1 = male, 2 = female, 3 = other
|
* @apiSuccess {Number} user.gender 0 = none, 1 = male, 2 = female, 3 = other
|
||||||
* @apiSuccess {Boolean} user.admin Is admin or not
|
* @apiSuccess {Boolean} user.admin Is admin or not
|
||||||
*/
|
*/
|
||||||
.get(promiseMiddleware(async (req, res) => {
|
.get(
|
||||||
|
promiseMiddleware(async (req, res) => {
|
||||||
let users = await User.find({});
|
let users = await User.find({});
|
||||||
users.forEach(e => delete e.password && delete e.salt && delete e.encryption_key);
|
users.forEach(
|
||||||
|
(e) => delete e.password && delete e.salt && delete e.encryption_key
|
||||||
|
);
|
||||||
res.json(users);
|
res.json(users);
|
||||||
}))
|
})
|
||||||
|
)
|
||||||
/**
|
/**
|
||||||
* @api {delete} /admin/user
|
* @api {delete} /admin/user
|
||||||
* @apiName AdminDeleteUser
|
* @apiName AdminDeleteUser
|
||||||
@ -45,21 +49,23 @@ UserRoute.route("/")
|
|||||||
*
|
*
|
||||||
* @apiSuccess {Boolean} success
|
* @apiSuccess {Boolean} success
|
||||||
*/
|
*/
|
||||||
.delete(promiseMiddleware(async (req, res) => {
|
.delete(
|
||||||
|
promiseMiddleware(async (req, res) => {
|
||||||
let { id } = req.query;
|
let { id } = req.query;
|
||||||
let user = await User.findById(id);
|
let user = await User.findById(id);
|
||||||
|
|
||||||
await Promise.all([
|
await Promise.all([
|
||||||
user.mails.map(mail => Mail.delete(mail)),
|
user.mails.map((mail) => Mail.delete(mail)),
|
||||||
[
|
[
|
||||||
RefreshToken.deleteFilter({ user: user._id }),
|
RefreshToken.deleteFilter({ user: user._id }),
|
||||||
LoginToken.deleteFilter({ user: user._id })
|
LoginToken.deleteFilter({ user: user._id }),
|
||||||
]
|
],
|
||||||
])
|
]);
|
||||||
|
|
||||||
await User.delete(user);
|
await User.delete(user);
|
||||||
res.json({ success: true });
|
res.json({ success: true });
|
||||||
}))
|
})
|
||||||
|
)
|
||||||
/**
|
/**
|
||||||
* @api {put} /admin/user
|
* @api {put} /admin/user
|
||||||
* @apiName AdminChangeUser
|
* @apiName AdminChangeUser
|
||||||
@ -75,11 +81,13 @@ UserRoute.route("/")
|
|||||||
* admin -> user
|
* admin -> user
|
||||||
* user -> admin
|
* user -> admin
|
||||||
*/
|
*/
|
||||||
.put(promiseMiddleware(async (req, res) => {
|
.put(
|
||||||
|
promiseMiddleware(async (req, res) => {
|
||||||
let { id } = req.query;
|
let { id } = req.query;
|
||||||
let user = await User.findById(id);
|
let user = await User.findById(id);
|
||||||
user.admin = !user.admin;
|
user.admin = !user.admin;
|
||||||
await User.save(user);
|
await User.save(user);
|
||||||
res.json({ success: true })
|
res.json({ success: true });
|
||||||
}))
|
})
|
||||||
|
);
|
||||||
export default UserRoute;
|
export default UserRoute;
|
@ -1,6 +1,9 @@
|
|||||||
import { Request, Response, Router } from "express"
|
import { Request, Response, Router } from "express";
|
||||||
import Stacker from "../middlewares/stacker";
|
import Stacker from "../middlewares/stacker";
|
||||||
import { GetClientAuthMiddleware, GetClientApiAuthMiddleware } from "../middlewares/client";
|
import {
|
||||||
|
GetClientAuthMiddleware,
|
||||||
|
GetClientApiAuthMiddleware,
|
||||||
|
} from "../middlewares/client";
|
||||||
import { GetUserMiddleware } from "../middlewares/user";
|
import { GetUserMiddleware } from "../middlewares/user";
|
||||||
import { createJWT } from "../../keys";
|
import { createJWT } from "../../keys";
|
||||||
import Client from "../../models/client";
|
import Client from "../../models/client";
|
||||||
@ -8,7 +11,6 @@ import RequestError, { HttpStatusCode } from "../../helper/request_error";
|
|||||||
import config from "../../config";
|
import config from "../../config";
|
||||||
import Mail from "../../models/mail";
|
import Mail from "../../models/mail";
|
||||||
|
|
||||||
|
|
||||||
const ClientRouter = Router();
|
const ClientRouter = Router();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -24,39 +26,59 @@ const ClientRouter = Router();
|
|||||||
*
|
*
|
||||||
* @apiPermission user_client Requires ClientID and Authenticated User
|
* @apiPermission user_client Requires ClientID and Authenticated User
|
||||||
*/
|
*/
|
||||||
ClientRouter.get("/user", Stacker(GetClientAuthMiddleware(false), GetUserMiddleware(false, false), async (req: Request, res: Response) => {
|
ClientRouter.get(
|
||||||
|
"/user",
|
||||||
|
Stacker(
|
||||||
|
GetClientAuthMiddleware(false),
|
||||||
|
GetUserMiddleware(false, false),
|
||||||
|
async (req: Request, res: Response) => {
|
||||||
let { redirect_uri, state } = req.query;
|
let { redirect_uri, state } = req.query;
|
||||||
|
|
||||||
if (redirect_uri !== req.client.redirect_url)
|
if (redirect_uri !== req.client.redirect_url)
|
||||||
throw new RequestError("Invalid redirect URI", HttpStatusCode.BAD_REQUEST);
|
throw new RequestError(
|
||||||
|
"Invalid redirect URI",
|
||||||
|
HttpStatusCode.BAD_REQUEST
|
||||||
|
);
|
||||||
|
|
||||||
let jwt = await createJWT({
|
let jwt = await createJWT(
|
||||||
|
{
|
||||||
client: req.client.client_id,
|
client: req.client.client_id,
|
||||||
uid: req.user.uid,
|
uid: req.user.uid,
|
||||||
username: req.user.username,
|
username: req.user.username,
|
||||||
state: state
|
state: state,
|
||||||
}, {
|
},
|
||||||
|
{
|
||||||
expiresIn: 30,
|
expiresIn: 30,
|
||||||
issuer: config.core.url,
|
issuer: config.core.url,
|
||||||
algorithm: "RS256",
|
algorithm: "RS256",
|
||||||
subject: req.user.uid,
|
subject: req.user.uid,
|
||||||
audience: req.client.client_id
|
audience: req.client.client_id,
|
||||||
}); //after 30 seconds this token is invalid
|
}
|
||||||
res.redirect(redirect_uri + "?jwt=" + jwt + (state ? `&state=${state}` : ""));
|
); //after 30 seconds this token is invalid
|
||||||
}));
|
res.redirect(
|
||||||
|
redirect_uri + "?jwt=" + jwt + (state ? `&state=${state}` : "")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
ClientRouter.get("/account", Stacker(GetClientApiAuthMiddleware(), async (req: Request, res) => {
|
ClientRouter.get(
|
||||||
let mails = await Promise.all(req.user.mails.map(id => Mail.findById(id)));
|
"/account",
|
||||||
|
Stacker(GetClientApiAuthMiddleware(), async (req: Request, res) => {
|
||||||
|
let mails = await Promise.all(
|
||||||
|
req.user.mails.map((id) => Mail.findById(id))
|
||||||
|
);
|
||||||
|
|
||||||
let mail = mails.find(e => e.primary) || mails[0];
|
let mail = mails.find((e) => e.primary) || mails[0];
|
||||||
|
|
||||||
res.json({
|
res.json({
|
||||||
user: {
|
user: {
|
||||||
username: req.user.username,
|
username: req.user.username,
|
||||||
name: req.user.name,
|
name: req.user.name,
|
||||||
email: mail
|
email: mail,
|
||||||
}
|
},
|
||||||
|
});
|
||||||
})
|
})
|
||||||
}));
|
);
|
||||||
|
|
||||||
export default ClientRouter;
|
export default ClientRouter;
|
||||||
|
@ -2,7 +2,7 @@ import { Request, Response } from "express";
|
|||||||
import Stacker from "../middlewares/stacker";
|
import Stacker from "../middlewares/stacker";
|
||||||
import {
|
import {
|
||||||
ClientAuthMiddleware,
|
ClientAuthMiddleware,
|
||||||
GetClientAuthMiddleware
|
GetClientAuthMiddleware,
|
||||||
} from "../middlewares/client";
|
} from "../middlewares/client";
|
||||||
import Permission from "../../models/permissions";
|
import Permission from "../../models/permissions";
|
||||||
import User from "../../models/user";
|
import User from "../../models/user";
|
||||||
@ -22,19 +22,19 @@ export const GetPermissions = Stacker(
|
|||||||
if (user) {
|
if (user) {
|
||||||
const grant = await Grant.findOne({
|
const grant = await Grant.findOne({
|
||||||
client: req.client._id,
|
client: req.client._id,
|
||||||
user: user
|
user: user,
|
||||||
});
|
});
|
||||||
|
|
||||||
permissions = await Promise.all(
|
permissions = await Promise.all(
|
||||||
grant.permissions.map(perm => Permission.findById(perm))
|
grant.permissions.map((perm) => Permission.findById(perm))
|
||||||
).then(res =>
|
).then((res) =>
|
||||||
res
|
res
|
||||||
.filter(e => e.grant_type === "client")
|
.filter((e) => e.grant_type === "client")
|
||||||
.map(e => {
|
.map((e) => {
|
||||||
return {
|
return {
|
||||||
id: e._id.toHexString(),
|
id: e._id.toHexString(),
|
||||||
name: e.name,
|
name: e.name,
|
||||||
description: e.description
|
description: e.description,
|
||||||
};
|
};
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
@ -43,10 +43,10 @@ export const GetPermissions = Stacker(
|
|||||||
if (permission) {
|
if (permission) {
|
||||||
const grants = await Grant.find({
|
const grants = await Grant.find({
|
||||||
client: req.client._id,
|
client: req.client._id,
|
||||||
permissions: new ObjectID(permission)
|
permissions: new ObjectID(permission),
|
||||||
});
|
});
|
||||||
|
|
||||||
users = grants.map(grant => grant.user.toHexString());
|
users = grants.map((grant) => grant.user.toHexString());
|
||||||
}
|
}
|
||||||
|
|
||||||
res.json({ permissions, users });
|
res.json({ permissions, users });
|
||||||
@ -73,14 +73,14 @@ export const PostPermissions = Stacker(
|
|||||||
|
|
||||||
let grant = await Grant.findOne({
|
let grant = await Grant.findOne({
|
||||||
client: req.client._id,
|
client: req.client._id,
|
||||||
user: req.user._id
|
user: req.user._id,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!grant) {
|
if (!grant) {
|
||||||
grant = Grant.new({
|
grant = Grant.new({
|
||||||
client: req.client._id,
|
client: req.client._id,
|
||||||
user: req.user._id,
|
user: req.user._id,
|
||||||
permissions: []
|
permissions: [],
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -92,7 +92,7 @@ export const PostPermissions = Stacker(
|
|||||||
await Grant.save(grant);
|
await Grant.save(grant);
|
||||||
|
|
||||||
res.json({
|
res.json({
|
||||||
success: true
|
success: true,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import * as express from "express"
|
import * as express from "express";
|
||||||
import AdminRoute from "./admin";
|
import AdminRoute from "./admin";
|
||||||
import UserRoute from "./user";
|
import UserRoute from "./user";
|
||||||
import InternalRoute from "./internal";
|
import InternalRoute from "./internal";
|
||||||
@ -9,7 +9,7 @@ import OAuthRoute from "./oauth";
|
|||||||
|
|
||||||
const ApiRouter: express.IRouter = express.Router();
|
const ApiRouter: express.IRouter = express.Router();
|
||||||
ApiRouter.use("/admin", AdminRoute);
|
ApiRouter.use("/admin", AdminRoute);
|
||||||
ApiRouter.use(cors())
|
ApiRouter.use(cors());
|
||||||
ApiRouter.use("/user", UserRoute);
|
ApiRouter.use("/user", UserRoute);
|
||||||
ApiRouter.use("/internal", InternalRoute);
|
ApiRouter.use("/internal", InternalRoute);
|
||||||
ApiRouter.use("/oauth", OAuthRoute);
|
ApiRouter.use("/oauth", OAuthRoute);
|
||||||
|
@ -26,5 +26,5 @@ InternalRoute.get("/oauth", OAuthInternalApp);
|
|||||||
* @apiParam {String} uid User ID (either username or UID)
|
* @apiParam {String} uid User ID (either username or UID)
|
||||||
* @apiParam {String} password Hashed and Salted according to specification
|
* @apiParam {String} password Hashed and Salted according to specification
|
||||||
*/
|
*/
|
||||||
InternalRoute.post("/password", PasswordAuth)
|
InternalRoute.post("/password", PasswordAuth);
|
||||||
export default InternalRoute;
|
export default InternalRoute;
|
@ -6,11 +6,16 @@ import RequestError, { HttpStatusCode } from "../../helper/request_error";
|
|||||||
import ClientCode from "../../models/client_code";
|
import ClientCode from "../../models/client_code";
|
||||||
import moment = require("moment");
|
import moment = require("moment");
|
||||||
import { randomBytes } from "crypto";
|
import { randomBytes } from "crypto";
|
||||||
export const OAuthInternalApp = Stacker(GetClientAuthMiddleware(false, true), UserMiddleware,
|
export const OAuthInternalApp = Stacker(
|
||||||
|
GetClientAuthMiddleware(false, true),
|
||||||
|
UserMiddleware,
|
||||||
async (req: Request, res: Response) => {
|
async (req: Request, res: Response) => {
|
||||||
let { redirect_uri, state } = req.query
|
let { redirect_uri, state } = req.query;
|
||||||
if (!redirect_uri) {
|
if (!redirect_uri) {
|
||||||
throw new RequestError("No redirect url set!", HttpStatusCode.BAD_REQUEST);
|
throw new RequestError(
|
||||||
|
"No redirect url set!",
|
||||||
|
HttpStatusCode.BAD_REQUEST
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
let sep = redirect_uri.indexOf("?") < 0 ? "?" : "&";
|
let sep = redirect_uri.indexOf("?") < 0 ? "?" : "&";
|
||||||
@ -20,10 +25,17 @@ export const OAuthInternalApp = Stacker(GetClientAuthMiddleware(false, true), Us
|
|||||||
client: req.client._id,
|
client: req.client._id,
|
||||||
validTill: moment().add(30, "minutes").toDate(),
|
validTill: moment().add(30, "minutes").toDate(),
|
||||||
code: randomBytes(16).toString("hex"),
|
code: randomBytes(16).toString("hex"),
|
||||||
permissions: []
|
permissions: [],
|
||||||
});
|
});
|
||||||
await ClientCode.save(code);
|
await ClientCode.save(code);
|
||||||
|
|
||||||
res.redirect(redirect_uri + sep + "code=" + code.code + (state ? "&state=" + state : ""));
|
res.redirect(
|
||||||
|
redirect_uri +
|
||||||
|
sep +
|
||||||
|
"code=" +
|
||||||
|
code.code +
|
||||||
|
(state ? "&state=" + state : "")
|
||||||
|
);
|
||||||
res.end();
|
res.end();
|
||||||
});
|
}
|
||||||
|
);
|
||||||
|
@ -4,22 +4,32 @@ import Stacker from "../middlewares/stacker";
|
|||||||
import RequestError, { HttpStatusCode } from "../../helper/request_error";
|
import RequestError, { HttpStatusCode } from "../../helper/request_error";
|
||||||
import User from "../../models/user";
|
import User from "../../models/user";
|
||||||
|
|
||||||
const PasswordAuth = Stacker(GetClientAuthMiddleware(true, true), async (req: Request, res: Response) => {
|
const PasswordAuth = Stacker(
|
||||||
let { username, password, uid }: { username: string, password: string, uid: string } = req.body;
|
GetClientAuthMiddleware(true, true),
|
||||||
|
async (req: Request, res: Response) => {
|
||||||
|
let {
|
||||||
|
username,
|
||||||
|
password,
|
||||||
|
uid,
|
||||||
|
}: { username: string; password: string; uid: string } = req.body;
|
||||||
let query: any = { password: password };
|
let query: any = { password: password };
|
||||||
if (username) {
|
if (username) {
|
||||||
query.username = username.toLowerCase()
|
query.username = username.toLowerCase();
|
||||||
} else if (uid) {
|
} else if (uid) {
|
||||||
query.uid = uid;
|
query.uid = uid;
|
||||||
} else {
|
} else {
|
||||||
throw new RequestError(req.__("No username or uid set"), HttpStatusCode.BAD_REQUEST);
|
throw new RequestError(
|
||||||
|
req.__("No username or uid set"),
|
||||||
|
HttpStatusCode.BAD_REQUEST
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
let user = await User.findOne(query);
|
let user = await User.findOne(query);
|
||||||
if (!user) {
|
if (!user) {
|
||||||
res.json({ error: req.__("Password or username wrong") })
|
res.json({ error: req.__("Password or username wrong") });
|
||||||
} else {
|
} else {
|
||||||
res.json({ success: true, uid: user.uid });
|
res.json({ success: true, uid: user.uid });
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
|
);
|
||||||
export default PasswordAuth;
|
export default PasswordAuth;
|
@ -6,7 +6,11 @@ import User from "../../models/user";
|
|||||||
import Mail from "../../models/mail";
|
import Mail from "../../models/mail";
|
||||||
import { OAuthJWT } from "../../helper/jwt";
|
import { OAuthJWT } from "../../helper/jwt";
|
||||||
|
|
||||||
export function GetClientAuthMiddleware(checksecret = true, internal = false, checksecret_if_available = false) {
|
export function GetClientAuthMiddleware(
|
||||||
|
checksecret = true,
|
||||||
|
internal = false,
|
||||||
|
checksecret_if_available = false
|
||||||
|
) {
|
||||||
return async (req: Request, res: Response, next: NextFunction) => {
|
return async (req: Request, res: Response, next: NextFunction) => {
|
||||||
try {
|
try {
|
||||||
let client_id = req.query.client_id || req.body.client_id;
|
let client_id = req.query.client_id || req.body.client_id;
|
||||||
@ -24,19 +28,29 @@ export function GetClientAuthMiddleware(checksecret = true, internal = false, ch
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!client_id || (!client_secret && checksecret)) {
|
if (!client_id || (!client_secret && checksecret)) {
|
||||||
throw new RequestError("No client credentials", HttpStatusCode.BAD_REQUEST);
|
throw new RequestError(
|
||||||
|
"No client credentials",
|
||||||
|
HttpStatusCode.BAD_REQUEST
|
||||||
|
);
|
||||||
}
|
}
|
||||||
let w = { client_id: client_id, client_secret: client_secret };
|
let w = { client_id: client_id, client_secret: client_secret };
|
||||||
if (!checksecret && !(checksecret_if_available && client_secret)) delete w.client_secret;
|
if (!checksecret && !(checksecret_if_available && client_secret))
|
||||||
|
delete w.client_secret;
|
||||||
|
|
||||||
let client = await Client.findOne(w)
|
let client = await Client.findOne(w);
|
||||||
|
|
||||||
if (!client) {
|
if (!client) {
|
||||||
throw new RequestError("Invalid client_id" + (checksecret ? "or client_secret" : ""), HttpStatusCode.BAD_REQUEST);
|
throw new RequestError(
|
||||||
|
"Invalid client_id" + (checksecret ? "or client_secret" : ""),
|
||||||
|
HttpStatusCode.BAD_REQUEST
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (internal && !client.internal) {
|
if (internal && !client.internal) {
|
||||||
throw new RequestError(req.__("Client has no permission for access"), HttpStatusCode.FORBIDDEN)
|
throw new RequestError(
|
||||||
|
req.__("Client has no permission for access"),
|
||||||
|
HttpStatusCode.FORBIDDEN
|
||||||
|
);
|
||||||
}
|
}
|
||||||
req.client = client;
|
req.client = client;
|
||||||
next();
|
next();
|
||||||
@ -44,7 +58,7 @@ export function GetClientAuthMiddleware(checksecret = true, internal = false, ch
|
|||||||
if (next) next(e);
|
if (next) next(e);
|
||||||
else throw e;
|
else throw e;
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export const ClientAuthMiddleware = GetClientAuthMiddleware();
|
export const ClientAuthMiddleware = GetClientAuthMiddleware();
|
||||||
@ -52,10 +66,13 @@ export const ClientAuthMiddleware = GetClientAuthMiddleware();
|
|||||||
export function GetClientApiAuthMiddleware(permissions?: string[]) {
|
export function GetClientApiAuthMiddleware(permissions?: string[]) {
|
||||||
return async (req: Request, res: Response, next: NextFunction) => {
|
return async (req: Request, res: Response, next: NextFunction) => {
|
||||||
try {
|
try {
|
||||||
const invalid_err = new RequestError(req.__("You are not logged in or your login is expired"), HttpStatusCode.UNAUTHORIZED);
|
const invalid_err = new RequestError(
|
||||||
let token: string = req.query.access_token || req.headers.authorization;
|
req.__("You are not logged in or your login is expired"),
|
||||||
if (!token)
|
HttpStatusCode.UNAUTHORIZED
|
||||||
throw invalid_err;
|
);
|
||||||
|
let token: string =
|
||||||
|
req.query.access_token || req.headers.authorization;
|
||||||
|
if (!token) throw invalid_err;
|
||||||
|
|
||||||
if (token.toLowerCase().startsWith("bearer "))
|
if (token.toLowerCase().startsWith("bearer "))
|
||||||
token = token.substring(7);
|
token = token.substring(7);
|
||||||
@ -64,19 +81,21 @@ export function GetClientApiAuthMiddleware(permissions?: string[]) {
|
|||||||
try {
|
try {
|
||||||
data = await validateJWT(token);
|
data = await validateJWT(token);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
throw invalid_err
|
throw invalid_err;
|
||||||
}
|
}
|
||||||
|
|
||||||
let user = await User.findOne({ uid: data.user });
|
let user = await User.findOne({ uid: data.user });
|
||||||
|
|
||||||
if (!user)
|
if (!user) throw invalid_err;
|
||||||
throw invalid_err;
|
|
||||||
|
|
||||||
let client = await Client.findOne({ client_id: data.application })
|
let client = await Client.findOne({ client_id: data.application });
|
||||||
if (!client)
|
if (!client) throw invalid_err;
|
||||||
throw invalid_err;
|
|
||||||
|
|
||||||
if (permissions && (!data.permissions || !permissions.every(e => data.permissions.indexOf(e) >= 0)))
|
if (
|
||||||
|
permissions &&
|
||||||
|
(!data.permissions ||
|
||||||
|
!permissions.every((e) => data.permissions.indexOf(e) >= 0))
|
||||||
|
)
|
||||||
throw invalid_err;
|
throw invalid_err;
|
||||||
|
|
||||||
req.user = user;
|
req.user = user;
|
||||||
@ -86,5 +105,5 @@ export function GetClientApiAuthMiddleware(permissions?: string[]) {
|
|||||||
if (next) next(e);
|
if (next) next(e);
|
||||||
else throw e;
|
else throw e;
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
@ -8,19 +8,21 @@ function call(handler: RH, req: Request, res: Response) {
|
|||||||
let p = handler(req, res, (err) => {
|
let p = handler(req, res, (err) => {
|
||||||
if (err) no(err);
|
if (err) no(err);
|
||||||
else yes();
|
else yes();
|
||||||
})
|
});
|
||||||
if (p && p.catch) p.catch(err => no(err));
|
if (p && p.catch) p.catch((err) => no(err));
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const Stacker = (...handler: RH[]) => {
|
const Stacker = (...handler: RH[]) => {
|
||||||
return promiseMiddleware(async (req: Request, res: Response, next: NextFunction) => {
|
return promiseMiddleware(
|
||||||
|
async (req: Request, res: Response, next: NextFunction) => {
|
||||||
let hc = handler.concat();
|
let hc = handler.concat();
|
||||||
while (hc.length > 0) {
|
while (hc.length > 0) {
|
||||||
let h = hc.shift();
|
let h = hc.shift();
|
||||||
await call(h, req, res);
|
await call(h, req, res);
|
||||||
}
|
}
|
||||||
next();
|
next();
|
||||||
});
|
}
|
||||||
}
|
);
|
||||||
|
};
|
||||||
export default Stacker;
|
export default Stacker;
|
@ -22,7 +22,7 @@ export function GetUserMiddleware(
|
|||||||
redirect_uri?: string,
|
redirect_uri?: string,
|
||||||
validated = true
|
validated = true
|
||||||
) {
|
) {
|
||||||
return promiseMiddleware(async function(
|
return promiseMiddleware(async function (
|
||||||
req: Request,
|
req: Request,
|
||||||
res: Response,
|
res: Response,
|
||||||
next?: NextFunction
|
next?: NextFunction
|
||||||
@ -57,7 +57,7 @@ export function GetUserMiddleware(
|
|||||||
token: special,
|
token: special,
|
||||||
special: true,
|
special: true,
|
||||||
valid: true,
|
valid: true,
|
||||||
user: token.user
|
user: token.user,
|
||||||
});
|
});
|
||||||
if (!(await CheckToken(special_token, validated)))
|
if (!(await CheckToken(special_token, validated)))
|
||||||
invalid("Special token invalid");
|
invalid("Special token invalid");
|
||||||
@ -68,7 +68,7 @@ export function GetUserMiddleware(
|
|||||||
req.isAdmin = user.admin;
|
req.isAdmin = user.admin;
|
||||||
req.token = {
|
req.token = {
|
||||||
login: token,
|
login: token,
|
||||||
special: special_token
|
special: special_token,
|
||||||
};
|
};
|
||||||
|
|
||||||
if (next) next();
|
if (next) next();
|
||||||
|
@ -1,6 +1,14 @@
|
|||||||
import { Request, Response, NextFunction } from "express"
|
import { Request, Response, NextFunction } from "express";
|
||||||
import { Logging } from "@hibas123/nodelogging";
|
import { Logging } from "@hibas123/nodelogging";
|
||||||
import { isBoolean, isString, isNumber, isObject, isDate, isArray, isSymbol } from "util";
|
import {
|
||||||
|
isBoolean,
|
||||||
|
isString,
|
||||||
|
isNumber,
|
||||||
|
isObject,
|
||||||
|
isDate,
|
||||||
|
isArray,
|
||||||
|
isSymbol,
|
||||||
|
} from "util";
|
||||||
import RequestError, { HttpStatusCode } from "../../helper/request_error";
|
import RequestError, { HttpStatusCode } from "../../helper/request_error";
|
||||||
|
|
||||||
export enum Types {
|
export enum Types {
|
||||||
@ -11,39 +19,41 @@ export enum Types {
|
|||||||
OBJECT,
|
OBJECT,
|
||||||
DATE,
|
DATE,
|
||||||
ARRAY,
|
ARRAY,
|
||||||
ENUM
|
ENUM,
|
||||||
}
|
}
|
||||||
|
|
||||||
function isEmail(value: any): boolean {
|
function isEmail(value: any): boolean {
|
||||||
return /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/.test(value)
|
return /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/.test(
|
||||||
|
value
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface CheckObject {
|
export interface CheckObject {
|
||||||
type: Types
|
type: Types;
|
||||||
query?: boolean
|
query?: boolean;
|
||||||
optional?: boolean
|
optional?: boolean;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Only when Type.ENUM
|
* Only when Type.ENUM
|
||||||
*
|
*
|
||||||
* values to check before
|
* values to check before
|
||||||
*/
|
*/
|
||||||
values?: string[]
|
values?: string[];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Only when Type.STRING
|
* Only when Type.STRING
|
||||||
*/
|
*/
|
||||||
notempty?: boolean // Only STRING
|
notempty?: boolean; // Only STRING
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Checks {
|
export interface Checks {
|
||||||
[index: string]: CheckObject// | Types
|
[index: string]: CheckObject; // | Types
|
||||||
}
|
}
|
||||||
|
|
||||||
// req: Request, res: Response, next: NextFunction
|
// req: Request, res: Response, next: NextFunction
|
||||||
export default function (fields: Checks, noadditional = false) {
|
export default function (fields: Checks, noadditional = false) {
|
||||||
return (req: Request, res: Response, next: NextFunction) => {
|
return (req: Request, res: Response, next: NextFunction) => {
|
||||||
let errors: { message: string, field: string }[] = []
|
let errors: { message: string; field: string }[] = [];
|
||||||
|
|
||||||
function check(data: any, field_name: string, field: CheckObject) {
|
function check(data: any, field_name: string, field: CheckObject) {
|
||||||
if (data !== undefined && data !== null) {
|
if (data !== undefined && data !== null) {
|
||||||
@ -78,48 +88,60 @@ export default function (fields: Checks, noadditional = false) {
|
|||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
Logging.error(`Invalid type to check: ${field.type} ${Types[field.type]}`)
|
Logging.error(
|
||||||
|
`Invalid type to check: ${field.type} ${Types[field.type]}`
|
||||||
|
);
|
||||||
}
|
}
|
||||||
errors.push({
|
errors.push({
|
||||||
message: res.__("Field {{field}} has wrong type. It should be from type {{type}}", { field: field_name, type: Types[field.type].toLowerCase() }),
|
message: res.__(
|
||||||
field: field_name
|
"Field {{field}} has wrong type. It should be from type {{type}}",
|
||||||
})
|
{ field: field_name, type: Types[field.type].toLowerCase() }
|
||||||
|
),
|
||||||
|
field: field_name,
|
||||||
|
});
|
||||||
} else {
|
} else {
|
||||||
if (!field.optional) errors.push({
|
if (!field.optional)
|
||||||
message: res.__("Field {{field}} is not defined", { field: field_name }),
|
errors.push({
|
||||||
field: field_name
|
message: res.__("Field {{field}} is not defined", {
|
||||||
})
|
field: field_name,
|
||||||
|
}),
|
||||||
|
field: field_name,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for (let field_name in fields) {
|
for (let field_name in fields) {
|
||||||
let field = fields[field_name]
|
let field = fields[field_name];
|
||||||
let data = fields[field_name].query ? req.query[field_name] : req.body[field_name]
|
let data = fields[field_name].query
|
||||||
check(data, field_name, field)
|
? req.query[field_name]
|
||||||
|
: req.body[field_name];
|
||||||
|
check(data, field_name, field);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (noadditional) { //Checks if the data given has additional parameters
|
if (noadditional) {
|
||||||
|
//Checks if the data given has additional parameters
|
||||||
let should = Object.keys(fields);
|
let should = Object.keys(fields);
|
||||||
should = should.filter(e => !fields[e].query); //Query parameters should not exist on body
|
should = should.filter((e) => !fields[e].query); //Query parameters should not exist on body
|
||||||
let has = Object.keys(req.body);
|
let has = Object.keys(req.body);
|
||||||
|
|
||||||
has.every(e => {
|
has.every((e) => {
|
||||||
if (should.indexOf(e) >= 0) {
|
if (should.indexOf(e) >= 0) {
|
||||||
return true;
|
return true;
|
||||||
} else {
|
} else {
|
||||||
errors.push({
|
errors.push({
|
||||||
message: res.__("Field {{field}} should not be there", { field: e }),
|
message: res.__("Field {{field}} should not be there", {
|
||||||
field: e
|
field: e,
|
||||||
})
|
}),
|
||||||
|
field: e,
|
||||||
|
});
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (errors.length > 0) {
|
if (errors.length > 0) {
|
||||||
let err = new RequestError(errors, HttpStatusCode.BAD_REQUEST, true);
|
let err = new RequestError(errors, HttpStatusCode.BAD_REQUEST, true);
|
||||||
next(err);
|
next(err);
|
||||||
} else
|
} else next();
|
||||||
next()
|
};
|
||||||
}
|
|
||||||
}
|
}
|
@ -93,11 +93,13 @@ const GetAuthRoute = (view = false) =>
|
|||||||
redirect_uri,
|
redirect_uri,
|
||||||
scope,
|
scope,
|
||||||
state,
|
state,
|
||||||
nored
|
nored,
|
||||||
} = req.query;
|
} = req.query;
|
||||||
const sendError = type => {
|
const sendError = (type) => {
|
||||||
if (redirect_uri === "$local") redirect_uri = "/code";
|
if (redirect_uri === "$local") redirect_uri = "/code";
|
||||||
res.redirect((redirect_uri += `?error=${type}${state ? "&state=" + state : ""}`));
|
res.redirect(
|
||||||
|
(redirect_uri += `?error=${type}${state ? "&state=" + state : ""}`)
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const scopes = scope.split(";");
|
const scopes = scope.split(";");
|
||||||
@ -123,7 +125,7 @@ const GetAuthRoute = (view = false) =>
|
|||||||
let permissions: IPermission[] = [];
|
let permissions: IPermission[] = [];
|
||||||
let proms: PromiseLike<void>[] = [];
|
let proms: PromiseLike<void>[] = [];
|
||||||
if (scopes) {
|
if (scopes) {
|
||||||
for (let perm of scopes.filter(e => e !== "read_user")) {
|
for (let perm of scopes.filter((e) => e !== "read_user")) {
|
||||||
let oid = undefined;
|
let oid = undefined;
|
||||||
try {
|
try {
|
||||||
oid = new ObjectID(perm);
|
oid = new ObjectID(perm);
|
||||||
@ -132,7 +134,7 @@ const GetAuthRoute = (view = false) =>
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
proms.push(
|
proms.push(
|
||||||
Permission.findById(oid).then(p => {
|
Permission.findById(oid).then((p) => {
|
||||||
if (!p) return Promise.reject(new Error());
|
if (!p) return Promise.reject(new Error());
|
||||||
permissions.push(p);
|
permissions.push(p);
|
||||||
})
|
})
|
||||||
@ -141,7 +143,7 @@ const GetAuthRoute = (view = false) =>
|
|||||||
}
|
}
|
||||||
|
|
||||||
let err = undefined;
|
let err = undefined;
|
||||||
await Promise.all(proms).catch(e => {
|
await Promise.all(proms).catch((e) => {
|
||||||
err = e;
|
err = e;
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -152,7 +154,7 @@ const GetAuthRoute = (view = false) =>
|
|||||||
|
|
||||||
let grant: IGrant | undefined = await Grant.findOne({
|
let grant: IGrant | undefined = await Grant.findOne({
|
||||||
client: client._id,
|
client: client._id,
|
||||||
user: req.user._id
|
user: req.user._id,
|
||||||
});
|
});
|
||||||
|
|
||||||
Logging.debug("Grant", grant, permissions);
|
Logging.debug("Grant", grant, permissions);
|
||||||
@ -161,14 +163,14 @@ const GetAuthRoute = (view = false) =>
|
|||||||
|
|
||||||
if (grant) {
|
if (grant) {
|
||||||
missing_permissions = grant.permissions
|
missing_permissions = grant.permissions
|
||||||
.map(perm => permissions.find(p => p._id.equals(perm)))
|
.map((perm) => permissions.find((p) => p._id.equals(perm)))
|
||||||
.filter(e => !!e);
|
.filter((e) => !!e);
|
||||||
} else {
|
} else {
|
||||||
missing_permissions = permissions;
|
missing_permissions = permissions;
|
||||||
}
|
}
|
||||||
|
|
||||||
let client_granted_perm = missing_permissions.filter(
|
let client_granted_perm = missing_permissions.filter(
|
||||||
e => e.grant_type == "client"
|
(e) => e.grant_type == "client"
|
||||||
);
|
);
|
||||||
if (client_granted_perm.length > 0) {
|
if (client_granted_perm.length > 0) {
|
||||||
return sendError("no_permission");
|
return sendError("no_permission");
|
||||||
@ -186,11 +188,11 @@ const GetAuthRoute = (view = false) =>
|
|||||||
GetAuthPage(
|
GetAuthPage(
|
||||||
req.__,
|
req.__,
|
||||||
client.name,
|
client.name,
|
||||||
permissions.map(perm => {
|
permissions.map((perm) => {
|
||||||
return {
|
return {
|
||||||
name: perm.name,
|
name: perm.name,
|
||||||
description: perm.description,
|
description: perm.description,
|
||||||
logo: client.logo
|
logo: client.logo,
|
||||||
};
|
};
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
@ -202,11 +204,11 @@ const GetAuthRoute = (view = false) =>
|
|||||||
grant = Grant.new({
|
grant = Grant.new({
|
||||||
client: client._id,
|
client: client._id,
|
||||||
user: req.user._id,
|
user: req.user._id,
|
||||||
permissions: []
|
permissions: [],
|
||||||
});
|
});
|
||||||
|
|
||||||
grant.permissions.push(
|
grant.permissions.push(
|
||||||
...missing_permissions.map(e => e._id)
|
...missing_permissions.map((e) => e._id)
|
||||||
);
|
);
|
||||||
await Grant.save(grant);
|
await Grant.save(grant);
|
||||||
} else {
|
} else {
|
||||||
@ -218,20 +220,19 @@ const GetAuthRoute = (view = false) =>
|
|||||||
let code = ClientCode.new({
|
let code = ClientCode.new({
|
||||||
user: req.user._id,
|
user: req.user._id,
|
||||||
client: client._id,
|
client: client._id,
|
||||||
permissions: permissions.map(p => p._id),
|
permissions: permissions.map((p) => p._id),
|
||||||
validTill: moment()
|
validTill: moment().add(30, "minutes").toDate(),
|
||||||
.add(30, "minutes")
|
code: randomBytes(16).toString("hex"),
|
||||||
.toDate(),
|
|
||||||
code: randomBytes(16).toString("hex")
|
|
||||||
});
|
});
|
||||||
await ClientCode.save(code);
|
await ClientCode.save(code);
|
||||||
|
|
||||||
let redir =
|
let redir =
|
||||||
client.redirect_url === "$local" ? "/code" : client.redirect_url;
|
client.redirect_url === "$local" ? "/code" : client.redirect_url;
|
||||||
let ruri = redir + `?code=${code.code}${state ? "&state=" + state : ""}`;
|
let ruri =
|
||||||
|
redir + `?code=${code.code}${state ? "&state=" + state : ""}`;
|
||||||
if (nored === "true") {
|
if (nored === "true") {
|
||||||
res.json({
|
res.json({
|
||||||
redirect_uri: ruri
|
redirect_uri: ruri,
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
res.redirect(ruri);
|
res.redirect(ruri);
|
||||||
|
@ -32,7 +32,7 @@ OAuthRoue.post("/auth", GetAuthRoute(false));
|
|||||||
*
|
*
|
||||||
* @apiSuccess {String} token The JWT that allowes the application to access the recources granted for refresh token
|
* @apiSuccess {String} token The JWT that allowes the application to access the recources granted for refresh token
|
||||||
*/
|
*/
|
||||||
OAuthRoue.get("/jwt", JWTRoute)
|
OAuthRoue.get("/jwt", JWTRoute);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @api {get} /oauth/public
|
* @api {get} /oauth/public
|
||||||
@ -43,7 +43,7 @@ OAuthRoue.get("/jwt", JWTRoute)
|
|||||||
*
|
*
|
||||||
* @apiSuccess {String} public_key The applications public_key. Used to verify JWT.
|
* @apiSuccess {String} public_key The applications public_key. Used to verify JWT.
|
||||||
*/
|
*/
|
||||||
OAuthRoue.get("/public", Public)
|
OAuthRoue.get("/public", Public);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @api {get} /oauth/refresh
|
* @api {get} /oauth/refresh
|
||||||
|
@ -8,21 +8,36 @@ import { getAccessTokenJWT } from "../../helper/jwt";
|
|||||||
|
|
||||||
const JWTRoute = promiseMiddleware(async (req: Request, res: Response) => {
|
const JWTRoute = promiseMiddleware(async (req: Request, res: Response) => {
|
||||||
let { refreshtoken } = req.query;
|
let { refreshtoken } = req.query;
|
||||||
if (!refreshtoken) throw new RequestError(req.__("Refresh token not set"), HttpStatusCode.BAD_REQUEST);
|
if (!refreshtoken)
|
||||||
|
throw new RequestError(
|
||||||
|
req.__("Refresh token not set"),
|
||||||
|
HttpStatusCode.BAD_REQUEST
|
||||||
|
);
|
||||||
|
|
||||||
let token = await RefreshToken.findOne({ token: refreshtoken });
|
let token = await RefreshToken.findOne({ token: refreshtoken });
|
||||||
if (!token) throw new RequestError(req.__("Invalid token"), HttpStatusCode.BAD_REQUEST);
|
if (!token)
|
||||||
|
throw new RequestError(
|
||||||
|
req.__("Invalid token"),
|
||||||
|
HttpStatusCode.BAD_REQUEST
|
||||||
|
);
|
||||||
|
|
||||||
let user = await User.findById(token.user);
|
let user = await User.findById(token.user);
|
||||||
if (!user) {
|
if (!user) {
|
||||||
token.valid = false;
|
token.valid = false;
|
||||||
await RefreshToken.save(token);
|
await RefreshToken.save(token);
|
||||||
throw new RequestError(req.__("Invalid token"), HttpStatusCode.BAD_REQUEST);
|
throw new RequestError(
|
||||||
|
req.__("Invalid token"),
|
||||||
|
HttpStatusCode.BAD_REQUEST
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
let client = await Client.findById(token.client);
|
let client = await Client.findById(token.client);
|
||||||
|
|
||||||
let jwt = await getAccessTokenJWT({ user, permissions: token.permissions, client });
|
let jwt = await getAccessTokenJWT({
|
||||||
|
user,
|
||||||
|
permissions: token.permissions,
|
||||||
|
client,
|
||||||
|
});
|
||||||
res.json({ token: jwt });
|
res.json({ token: jwt });
|
||||||
})
|
});
|
||||||
export default JWTRoute;
|
export default JWTRoute;
|
@ -2,5 +2,5 @@ import { Request, Response } from "express";
|
|||||||
import { public_key } from "../../keys";
|
import { public_key } from "../../keys";
|
||||||
|
|
||||||
export default function Public(req: Request, res: Response) {
|
export default function Public(req: Request, res: Response) {
|
||||||
res.json({ public_key: public_key })
|
res.json({ public_key: public_key });
|
||||||
}
|
}
|
@ -2,9 +2,13 @@ import { Request, Response } from "express";
|
|||||||
import RequestError, { HttpStatusCode } from "../../helper/request_error";
|
import RequestError, { HttpStatusCode } from "../../helper/request_error";
|
||||||
import User from "../../models/user";
|
import User from "../../models/user";
|
||||||
import Client from "../../models/client";
|
import Client from "../../models/client";
|
||||||
import { getAccessTokenJWT, getIDToken, AccessTokenJWTExp } from "../../helper/jwt";
|
import {
|
||||||
|
getAccessTokenJWT,
|
||||||
|
getIDToken,
|
||||||
|
AccessTokenJWTExp,
|
||||||
|
} from "../../helper/jwt";
|
||||||
import Stacker from "../middlewares/stacker";
|
import Stacker from "../middlewares/stacker";
|
||||||
import { GetClientAuthMiddleware } from "../middlewares/client"
|
import { GetClientAuthMiddleware } from "../middlewares/client";
|
||||||
import ClientCode from "../../models/client_code";
|
import ClientCode from "../../models/client_code";
|
||||||
import Mail from "../../models/mail";
|
import Mail from "../../models/mail";
|
||||||
import { randomBytes } from "crypto";
|
import { randomBytes } from "crypto";
|
||||||
@ -25,21 +29,26 @@ token, which will inform the authorization server of the breach.
|
|||||||
|
|
||||||
const refreshTokenValidTime = moment.duration(6, "month");
|
const refreshTokenValidTime = moment.duration(6, "month");
|
||||||
|
|
||||||
const RefreshTokenRoute = Stacker(GetClientAuthMiddleware(false, false, true), async (req: Request, res: Response) => {
|
const RefreshTokenRoute = Stacker(
|
||||||
|
GetClientAuthMiddleware(false, false, true),
|
||||||
|
async (req: Request, res: Response) => {
|
||||||
let grant_type = req.query.grant_type || req.body.grant_type;
|
let grant_type = req.query.grant_type || req.body.grant_type;
|
||||||
if (!grant_type || grant_type === "authorization_code") {
|
if (!grant_type || grant_type === "authorization_code") {
|
||||||
let code = req.query.code || req.body.code;
|
let code = req.query.code || req.body.code;
|
||||||
let nonce = req.query.nonce || req.body.nonce;
|
let nonce = req.query.nonce || req.body.nonce;
|
||||||
|
|
||||||
let c = await ClientCode.findOne({ code: code })
|
let c = await ClientCode.findOne({ code: code });
|
||||||
if (!c || moment(c.validTill).isBefore()) {
|
if (!c || moment(c.validTill).isBefore()) {
|
||||||
throw new RequestError(req.__("Invalid code"), HttpStatusCode.BAD_REQUEST);
|
throw new RequestError(
|
||||||
|
req.__("Invalid code"),
|
||||||
|
HttpStatusCode.BAD_REQUEST
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
let client = await Client.findById(c.client);
|
let client = await Client.findById(c.client);
|
||||||
|
|
||||||
let user = await User.findById(c.user);
|
let user = await User.findById(c.user);
|
||||||
let mails = await Promise.all(user.mails.map(m => Mail.findOne(m)));
|
let mails = await Promise.all(user.mails.map((m) => Mail.findOne(m)));
|
||||||
|
|
||||||
let token = RefreshToken.new({
|
let token = RefreshToken.new({
|
||||||
user: c.user,
|
user: c.user,
|
||||||
@ -47,12 +56,12 @@ const RefreshTokenRoute = Stacker(GetClientAuthMiddleware(false, false, true), a
|
|||||||
permissions: c.permissions,
|
permissions: c.permissions,
|
||||||
token: randomBytes(16).toString("hex"),
|
token: randomBytes(16).toString("hex"),
|
||||||
valid: true,
|
valid: true,
|
||||||
validTill: moment().add(refreshTokenValidTime).toDate()
|
validTill: moment().add(refreshTokenValidTime).toDate(),
|
||||||
});
|
});
|
||||||
await RefreshToken.save(token);
|
await RefreshToken.save(token);
|
||||||
await ClientCode.delete(c);
|
await ClientCode.delete(c);
|
||||||
|
|
||||||
let mail = mails.find(e => e.primary);
|
let mail = mails.find((e) => e.primary);
|
||||||
if (!mail) mail = mails[0];
|
if (!mail) mail = mails[0];
|
||||||
|
|
||||||
res.json({
|
res.json({
|
||||||
@ -61,7 +70,7 @@ const RefreshTokenRoute = Stacker(GetClientAuthMiddleware(false, false, true), a
|
|||||||
access_token: await getAccessTokenJWT({
|
access_token: await getAccessTokenJWT({
|
||||||
client: client,
|
client: client,
|
||||||
user: user,
|
user: user,
|
||||||
permissions: c.permissions
|
permissions: c.permissions,
|
||||||
}),
|
}),
|
||||||
token_type: "bearer",
|
token_type: "bearer",
|
||||||
expires_in: AccessTokenJWTExp.asSeconds(),
|
expires_in: AccessTokenJWTExp.asSeconds(),
|
||||||
@ -69,28 +78,46 @@ const RefreshTokenRoute = Stacker(GetClientAuthMiddleware(false, false, true), a
|
|||||||
uid: user.uid,
|
uid: user.uid,
|
||||||
email: mail ? mail.mail : "",
|
email: mail ? mail.mail : "",
|
||||||
name: user.name,
|
name: user.name,
|
||||||
enc_key: getEncryptionKey(user, client)
|
enc_key: getEncryptionKey(user, client),
|
||||||
},
|
},
|
||||||
id_token: getIDToken(user, client.client_id, nonce)
|
id_token: getIDToken(user, client.client_id, nonce),
|
||||||
});
|
});
|
||||||
} else if (grant_type === "refresh_token") {
|
} else if (grant_type === "refresh_token") {
|
||||||
let refresh_token = req.query.refresh_token || req.body.refresh_token;
|
let refresh_token = req.query.refresh_token || req.body.refresh_token;
|
||||||
if (!refresh_token) throw new RequestError(req.__("refresh_token not set"), HttpStatusCode.BAD_REQUEST);
|
if (!refresh_token)
|
||||||
|
throw new RequestError(
|
||||||
|
req.__("refresh_token not set"),
|
||||||
|
HttpStatusCode.BAD_REQUEST
|
||||||
|
);
|
||||||
|
|
||||||
let token = await RefreshToken.findOne({ token: refresh_token });
|
let token = await RefreshToken.findOne({ token: refresh_token });
|
||||||
if (!token || !token.valid || moment(token.validTill).isBefore())
|
if (!token || !token.valid || moment(token.validTill).isBefore())
|
||||||
throw new RequestError(req.__("Invalid token"), HttpStatusCode.BAD_REQUEST);
|
throw new RequestError(
|
||||||
|
req.__("Invalid token"),
|
||||||
|
HttpStatusCode.BAD_REQUEST
|
||||||
|
);
|
||||||
|
|
||||||
token.validTill = moment().add(refreshTokenValidTime).toDate();
|
token.validTill = moment().add(refreshTokenValidTime).toDate();
|
||||||
await RefreshToken.save(token);
|
await RefreshToken.save(token);
|
||||||
|
|
||||||
let user = await User.findById(token.user);
|
let user = await User.findById(token.user);
|
||||||
let client = await Client.findById(token.client)
|
let client = await Client.findById(token.client);
|
||||||
let jwt = await getAccessTokenJWT({ user, client, permissions: token.permissions });
|
let jwt = await getAccessTokenJWT({
|
||||||
res.json({ access_token: jwt, expires_in: AccessTokenJWTExp.asSeconds() });
|
user,
|
||||||
|
client,
|
||||||
|
permissions: token.permissions,
|
||||||
|
});
|
||||||
|
res.json({
|
||||||
|
access_token: jwt,
|
||||||
|
expires_in: AccessTokenJWTExp.asSeconds(),
|
||||||
|
});
|
||||||
} else {
|
} else {
|
||||||
throw new RequestError("invalid grant_type", HttpStatusCode.BAD_REQUEST);
|
throw new RequestError(
|
||||||
|
"invalid grant_type",
|
||||||
|
HttpStatusCode.BAD_REQUEST
|
||||||
|
);
|
||||||
}
|
}
|
||||||
})
|
}
|
||||||
|
);
|
||||||
|
|
||||||
export default RefreshTokenRoute;
|
export default RefreshTokenRoute;
|
@ -4,7 +4,9 @@ import { GetUserMiddleware } from "../middlewares/user";
|
|||||||
import LoginToken, { CheckToken } from "../../models/login_token";
|
import LoginToken, { CheckToken } from "../../models/login_token";
|
||||||
import RequestError, { HttpStatusCode } from "../../helper/request_error";
|
import RequestError, { HttpStatusCode } from "../../helper/request_error";
|
||||||
|
|
||||||
export const GetAccount = Stacker(GetUserMiddleware(true, true), async (req: Request, res: Response) => {
|
export const GetAccount = Stacker(
|
||||||
|
GetUserMiddleware(true, true),
|
||||||
|
async (req: Request, res: Response) => {
|
||||||
let user = {
|
let user = {
|
||||||
id: req.user.uid,
|
id: req.user.uid,
|
||||||
name: req.user.name,
|
name: req.user.name,
|
||||||
@ -13,4 +15,5 @@ export const GetAccount = Stacker(GetUserMiddleware(true, true), async (req: Req
|
|||||||
gender: req.user.gender,
|
gender: req.user.gender,
|
||||||
};
|
};
|
||||||
res.json({ user });
|
res.json({ user });
|
||||||
});
|
}
|
||||||
|
);
|
||||||
|
@ -3,14 +3,17 @@ import Stacker from "../middlewares/stacker";
|
|||||||
import { GetUserMiddleware } from "../middlewares/user";
|
import { GetUserMiddleware } from "../middlewares/user";
|
||||||
import Mail from "../../models/mail";
|
import Mail from "../../models/mail";
|
||||||
|
|
||||||
export const GetContactInfos = Stacker(GetUserMiddleware(true, true), async (req: Request, res: Response) => {
|
export const GetContactInfos = Stacker(
|
||||||
|
GetUserMiddleware(true, true),
|
||||||
|
async (req: Request, res: Response) => {
|
||||||
let mails = await Promise.all(
|
let mails = await Promise.all(
|
||||||
req.user.mails.map(mail => Mail.findById(mail))
|
req.user.mails.map((mail) => Mail.findById(mail))
|
||||||
);
|
);
|
||||||
|
|
||||||
let contact = {
|
let contact = {
|
||||||
mails: mails.filter(e => !!e),
|
mails: mails.filter((e) => !!e),
|
||||||
phones: req.user.phones
|
phones: req.user.phones,
|
||||||
};
|
};
|
||||||
res.json({ contact });
|
res.json({ contact });
|
||||||
});
|
}
|
||||||
|
);
|
||||||
|
@ -62,7 +62,7 @@ UserRoute.post("/register", Register);
|
|||||||
* @apiSuccess {String} tfa.name The name of the TFA Method
|
* @apiSuccess {String} tfa.name The name of the TFA Method
|
||||||
* @apiSuccess {String} tfa.type The type of the TFA Method
|
* @apiSuccess {String} tfa.type The type of the TFA Method
|
||||||
*/
|
*/
|
||||||
UserRoute.post("/login", Login)
|
UserRoute.post("/login", Login);
|
||||||
UserRoute.use("/twofactor", TwoFactorRoute);
|
UserRoute.use("/twofactor", TwoFactorRoute);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -95,7 +95,6 @@ UserRoute.get("/token", GetToken);
|
|||||||
*/
|
*/
|
||||||
UserRoute.delete("/token/:id", DeleteToken);
|
UserRoute.delete("/token/:id", DeleteToken);
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @api {delete} /user/account
|
* @api {delete} /user/account
|
||||||
* @apiName UserGetAccount
|
* @apiName UserGetAccount
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { Request, Response } from "express"
|
import { Request, Response } from "express";
|
||||||
import User, { IUser } from "../../models/user";
|
import User, { IUser } from "../../models/user";
|
||||||
import { randomBytes } from "crypto";
|
import { randomBytes } from "crypto";
|
||||||
import moment = require("moment");
|
import moment = require("moment");
|
||||||
@ -12,36 +12,39 @@ const Login = promiseMiddleware(async (req: Request, res: Response) => {
|
|||||||
let type = req.query.type;
|
let type = req.query.type;
|
||||||
if (type === "username") {
|
if (type === "username") {
|
||||||
let { username, uid } = req.query;
|
let { username, uid } = req.query;
|
||||||
let user = await User.findOne(username ? { username: username.toLowerCase() } : { uid: uid });
|
let user = await User.findOne(
|
||||||
|
username ? { username: username.toLowerCase() } : { uid: uid }
|
||||||
|
);
|
||||||
if (!user) {
|
if (!user) {
|
||||||
res.json({ error: req.__("User not found") })
|
res.json({ error: req.__("User not found") });
|
||||||
} else {
|
} else {
|
||||||
res.json({ salt: user.salt, uid: user.uid });
|
res.json({ salt: user.salt, uid: user.uid });
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
} else if (type === "password") {
|
} else if (type === "password") {
|
||||||
const sendToken = async (user: IUser, tfa?: any[]) => {
|
const sendToken = async (user: IUser, tfa?: any[]) => {
|
||||||
let ip = req.headers['x-forwarded-for'] || req.connection.remoteAddress
|
let ip =
|
||||||
|
req.headers["x-forwarded-for"] || req.connection.remoteAddress;
|
||||||
let client = {
|
let client = {
|
||||||
ip: Array.isArray(ip) ? ip[0] : ip,
|
ip: Array.isArray(ip) ? ip[0] : ip,
|
||||||
browser: req.headers["user-agent"]
|
browser: req.headers["user-agent"],
|
||||||
}
|
};
|
||||||
|
|
||||||
let token_str = randomBytes(16).toString("hex");
|
let token_str = randomBytes(16).toString("hex");
|
||||||
let tfa_exp = moment().add(5, "minutes").toDate()
|
let tfa_exp = moment().add(5, "minutes").toDate();
|
||||||
let token_exp = moment().add(6, "months").toDate()
|
let token_exp = moment().add(6, "months").toDate();
|
||||||
let token = LoginToken.new({
|
let token = LoginToken.new({
|
||||||
token: token_str,
|
token: token_str,
|
||||||
valid: true,
|
valid: true,
|
||||||
validTill: tfa ? tfa_exp : token_exp,
|
validTill: tfa ? tfa_exp : token_exp,
|
||||||
user: user._id,
|
user: user._id,
|
||||||
validated: tfa ? false : true,
|
validated: tfa ? false : true,
|
||||||
...client
|
...client,
|
||||||
});
|
});
|
||||||
await LoginToken.save(token);
|
await LoginToken.save(token);
|
||||||
|
|
||||||
let special_str = randomBytes(24).toString("hex");
|
let special_str = randomBytes(24).toString("hex");
|
||||||
let special_exp = moment().add(30, "minutes").toDate()
|
let special_exp = moment().add(30, "minutes").toDate();
|
||||||
let special = LoginToken.new({
|
let special = LoginToken.new({
|
||||||
token: special_str,
|
token: special_str,
|
||||||
valid: true,
|
valid: true,
|
||||||
@ -49,50 +52,74 @@ const Login = promiseMiddleware(async (req: Request, res: Response) => {
|
|||||||
special: true,
|
special: true,
|
||||||
user: user._id,
|
user: user._id,
|
||||||
validated: tfa ? false : true,
|
validated: tfa ? false : true,
|
||||||
...client
|
...client,
|
||||||
});
|
});
|
||||||
await LoginToken.save(special);
|
await LoginToken.save(special);
|
||||||
|
|
||||||
res.json({
|
res.json({
|
||||||
login: { token: token_str, expires: token.validTill.toUTCString() },
|
login: { token: token_str, expires: token.validTill.toUTCString() },
|
||||||
special: { token: special_str, expires: special.validTill.toUTCString() },
|
special: {
|
||||||
tfa
|
token: special_str,
|
||||||
|
expires: special.validTill.toUTCString(),
|
||||||
|
},
|
||||||
|
tfa,
|
||||||
});
|
});
|
||||||
}
|
};
|
||||||
|
|
||||||
let { username, password, uid, date } = req.body;
|
let { username, password, uid, date } = req.body;
|
||||||
|
|
||||||
let user = await User.findOne(username ? { username: username.toLowerCase() } : { uid: uid })
|
let user = await User.findOne(
|
||||||
|
username ? { username: username.toLowerCase() } : { uid: uid }
|
||||||
|
);
|
||||||
if (!user) {
|
if (!user) {
|
||||||
res.json({ error: req.__("User not found") })
|
res.json({ error: req.__("User not found") });
|
||||||
} else {
|
} else {
|
||||||
let upw = user.password;
|
let upw = user.password;
|
||||||
if (date) {
|
if (date) {
|
||||||
if (!moment(date).isBetween(moment().subtract(1, "minute"), moment().add(1, "minute"))) {
|
if (
|
||||||
res.json({ error: req.__("Invalid timestamp. Please check your devices time!") });
|
!moment(date).isBetween(
|
||||||
|
moment().subtract(1, "minute"),
|
||||||
|
moment().add(1, "minute")
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
res.json({
|
||||||
|
error: req.__(
|
||||||
|
"Invalid timestamp. Please check your devices time!"
|
||||||
|
),
|
||||||
|
});
|
||||||
return;
|
return;
|
||||||
} else {
|
} else {
|
||||||
upw = crypto.createHash("sha512").update(upw + date.toString()).digest("hex");
|
upw = crypto
|
||||||
|
.createHash("sha512")
|
||||||
|
.update(upw + date.toString())
|
||||||
|
.digest("hex");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (upw !== password) {
|
if (upw !== password) {
|
||||||
res.json({ error: req.__("Password or username wrong") })
|
res.json({ error: req.__("Password or username wrong") });
|
||||||
} else {
|
} else {
|
||||||
let twofactor = await TwoFactor.find({ user: user._id, valid: true })
|
let twofactor = await TwoFactor.find({
|
||||||
let expired = twofactor.filter(e => e.expires ? moment().isAfter(moment(e.expires)) : false)
|
user: user._id,
|
||||||
await Promise.all(expired.map(e => {
|
valid: true,
|
||||||
|
});
|
||||||
|
let expired = twofactor.filter((e) =>
|
||||||
|
e.expires ? moment().isAfter(moment(e.expires)) : false
|
||||||
|
);
|
||||||
|
await Promise.all(
|
||||||
|
expired.map((e) => {
|
||||||
e.valid = false;
|
e.valid = false;
|
||||||
return TwoFactor.save(e);
|
return TwoFactor.save(e);
|
||||||
}));
|
})
|
||||||
twofactor = twofactor.filter(e => e.valid);
|
);
|
||||||
|
twofactor = twofactor.filter((e) => e.valid);
|
||||||
if (twofactor && twofactor.length > 0) {
|
if (twofactor && twofactor.length > 0) {
|
||||||
let tfa = twofactor.map(e => {
|
let tfa = twofactor.map((e) => {
|
||||||
return {
|
return {
|
||||||
id: e._id,
|
id: e._id,
|
||||||
name: e.name || TFANames.get(e.type),
|
name: e.name || TFANames.get(e.type),
|
||||||
type: e.type
|
type: e.type,
|
||||||
}
|
};
|
||||||
})
|
});
|
||||||
await sendToken(user, tfa);
|
await sendToken(user, tfa);
|
||||||
} else {
|
} else {
|
||||||
await sendToken(user);
|
await sendToken(user);
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { Request, Response, Router } from "express"
|
import { Request, Response, Router } from "express";
|
||||||
import Stacker from "../middlewares/stacker";
|
import Stacker from "../middlewares/stacker";
|
||||||
import verify, { Types } from "../middlewares/verify";
|
import verify, { Types } from "../middlewares/verify";
|
||||||
import promiseMiddleware from "../../helper/promiseMiddleware";
|
import promiseMiddleware from "../../helper/promiseMiddleware";
|
||||||
@ -7,83 +7,93 @@ import { HttpStatusCode } from "../../helper/request_error";
|
|||||||
import Mail from "../../models/mail";
|
import Mail from "../../models/mail";
|
||||||
import RegCode from "../../models/regcodes";
|
import RegCode from "../../models/regcodes";
|
||||||
|
|
||||||
const Register = Stacker(verify({
|
const Register = Stacker(
|
||||||
|
verify({
|
||||||
mail: {
|
mail: {
|
||||||
type: Types.EMAIL,
|
type: Types.EMAIL,
|
||||||
notempty: true
|
notempty: true,
|
||||||
},
|
},
|
||||||
username: {
|
username: {
|
||||||
type: Types.STRING,
|
type: Types.STRING,
|
||||||
notempty: true
|
notempty: true,
|
||||||
},
|
},
|
||||||
password: {
|
password: {
|
||||||
type: Types.STRING,
|
type: Types.STRING,
|
||||||
notempty: true
|
notempty: true,
|
||||||
},
|
},
|
||||||
salt: {
|
salt: {
|
||||||
type: Types.STRING,
|
type: Types.STRING,
|
||||||
notempty: true
|
notempty: true,
|
||||||
},
|
},
|
||||||
regcode: {
|
regcode: {
|
||||||
type: Types.STRING,
|
type: Types.STRING,
|
||||||
notempty: true
|
notempty: true,
|
||||||
},
|
},
|
||||||
gender: {
|
gender: {
|
||||||
type: Types.STRING,
|
type: Types.STRING,
|
||||||
notempty: true
|
notempty: true,
|
||||||
},
|
},
|
||||||
name: {
|
name: {
|
||||||
type: Types.STRING,
|
type: Types.STRING,
|
||||||
notempty: true
|
notempty: true,
|
||||||
},
|
},
|
||||||
// birthday: {
|
// birthday: {
|
||||||
// type: Types.DATE
|
// type: Types.DATE
|
||||||
// }
|
// }
|
||||||
}), promiseMiddleware(async (req: Request, res: Response) => {
|
}),
|
||||||
let { username, password, salt, mail, gender, name, birthday, regcode } = req.body;
|
promiseMiddleware(async (req: Request, res: Response) => {
|
||||||
let u = await User.findOne({ username: username.toLowerCase() })
|
let {
|
||||||
|
username,
|
||||||
|
password,
|
||||||
|
salt,
|
||||||
|
mail,
|
||||||
|
gender,
|
||||||
|
name,
|
||||||
|
birthday,
|
||||||
|
regcode,
|
||||||
|
} = req.body;
|
||||||
|
let u = await User.findOne({ username: username.toLowerCase() });
|
||||||
if (u) {
|
if (u) {
|
||||||
let err = {
|
let err = {
|
||||||
message: [
|
message: [
|
||||||
{
|
{
|
||||||
message: req.__("Username taken"),
|
message: req.__("Username taken"),
|
||||||
field: "username"
|
field: "username",
|
||||||
}
|
},
|
||||||
],
|
],
|
||||||
status: HttpStatusCode.BAD_REQUEST,
|
status: HttpStatusCode.BAD_REQUEST,
|
||||||
nolog: true
|
nolog: true,
|
||||||
}
|
};
|
||||||
throw err;
|
throw err;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let m = await Mail.findOne({ mail: mail });
|
||||||
let m = await Mail.findOne({ mail: mail })
|
|
||||||
if (m) {
|
if (m) {
|
||||||
let err = {
|
let err = {
|
||||||
message: [
|
message: [
|
||||||
{
|
{
|
||||||
message: req.__("Mail linked with other account"),
|
message: req.__("Mail linked with other account"),
|
||||||
field: "mail"
|
field: "mail",
|
||||||
}
|
},
|
||||||
],
|
],
|
||||||
status: HttpStatusCode.BAD_REQUEST,
|
status: HttpStatusCode.BAD_REQUEST,
|
||||||
nolog: true
|
nolog: true,
|
||||||
}
|
};
|
||||||
throw err;
|
throw err;
|
||||||
}
|
}
|
||||||
|
|
||||||
let regc = await RegCode.findOne({ token: regcode })
|
let regc = await RegCode.findOne({ token: regcode });
|
||||||
if (!regc) {
|
if (!regc) {
|
||||||
let err = {
|
let err = {
|
||||||
message: [
|
message: [
|
||||||
{
|
{
|
||||||
message: req.__("Invalid registration code"),
|
message: req.__("Invalid registration code"),
|
||||||
field: "regcode"
|
field: "regcode",
|
||||||
}
|
},
|
||||||
],
|
],
|
||||||
status: HttpStatusCode.BAD_REQUEST,
|
status: HttpStatusCode.BAD_REQUEST,
|
||||||
nolog: true
|
nolog: true,
|
||||||
}
|
};
|
||||||
throw err;
|
throw err;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -92,28 +102,28 @@ const Register = Stacker(verify({
|
|||||||
message: [
|
message: [
|
||||||
{
|
{
|
||||||
message: req.__("Registration code already used"),
|
message: req.__("Registration code already used"),
|
||||||
field: "regcode"
|
field: "regcode",
|
||||||
}
|
},
|
||||||
],
|
],
|
||||||
status: HttpStatusCode.BAD_REQUEST,
|
status: HttpStatusCode.BAD_REQUEST,
|
||||||
nolog: true
|
nolog: true,
|
||||||
}
|
};
|
||||||
throw err;
|
throw err;
|
||||||
}
|
}
|
||||||
|
|
||||||
let g = -1;
|
let g = -1;
|
||||||
switch (gender) {
|
switch (gender) {
|
||||||
case "male":
|
case "male":
|
||||||
g = Gender.male
|
g = Gender.male;
|
||||||
break;
|
break;
|
||||||
case "female":
|
case "female":
|
||||||
g = Gender.female
|
g = Gender.female;
|
||||||
break;
|
break;
|
||||||
case "other":
|
case "other":
|
||||||
g = Gender.other
|
g = Gender.other;
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
g = Gender.none
|
g = Gender.none;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -124,21 +134,22 @@ const Register = Stacker(verify({
|
|||||||
gender: g,
|
gender: g,
|
||||||
name: name,
|
name: name,
|
||||||
// birthday: birthday,
|
// birthday: birthday,
|
||||||
admin: false
|
admin: false,
|
||||||
})
|
});
|
||||||
|
|
||||||
regc.valid = false;
|
regc.valid = false;
|
||||||
await RegCode.save(regc);
|
await RegCode.save(regc);
|
||||||
|
|
||||||
let ml = Mail.new({
|
let ml = Mail.new({
|
||||||
mail: mail,
|
mail: mail,
|
||||||
primary: true
|
primary: true,
|
||||||
})
|
});
|
||||||
|
|
||||||
await Mail.save(ml);
|
await Mail.save(ml);
|
||||||
|
|
||||||
user.mails.push(ml._id);
|
user.mails.push(ml._id);
|
||||||
await User.save(user)
|
await User.save(user);
|
||||||
res.json({ success: true });
|
res.json({ success: true });
|
||||||
}))
|
})
|
||||||
|
);
|
||||||
export default Register;
|
export default Register;
|
@ -4,26 +4,42 @@ import { GetUserMiddleware } from "../middlewares/user";
|
|||||||
import LoginToken, { CheckToken } from "../../models/login_token";
|
import LoginToken, { CheckToken } from "../../models/login_token";
|
||||||
import RequestError, { HttpStatusCode } from "../../helper/request_error";
|
import RequestError, { HttpStatusCode } from "../../helper/request_error";
|
||||||
|
|
||||||
export const GetToken = Stacker(GetUserMiddleware(true, true), async (req: Request, res: Response) => {
|
export const GetToken = Stacker(
|
||||||
let raw_token = await LoginToken.find({ user: req.user._id, valid: true });
|
GetUserMiddleware(true, true),
|
||||||
let token = await Promise.all(raw_token.map(async token => {
|
async (req: Request, res: Response) => {
|
||||||
|
let raw_token = await LoginToken.find({
|
||||||
|
user: req.user._id,
|
||||||
|
valid: true,
|
||||||
|
});
|
||||||
|
let token = await Promise.all(
|
||||||
|
raw_token
|
||||||
|
.map(async (token) => {
|
||||||
await CheckToken(token);
|
await CheckToken(token);
|
||||||
return {
|
return {
|
||||||
id: token._id,
|
id: token._id,
|
||||||
special: token.special,
|
special: token.special,
|
||||||
ip: token.ip,
|
ip: token.ip,
|
||||||
browser: token.browser,
|
browser: token.browser,
|
||||||
isthis: token._id.equals(token.special ? req.token.special._id : req.token.login._id)
|
isthis: token._id.equals(
|
||||||
}
|
token.special ? req.token.special._id : req.token.login._id
|
||||||
}).filter(t => t !== undefined));
|
),
|
||||||
|
};
|
||||||
|
})
|
||||||
|
.filter((t) => t !== undefined)
|
||||||
|
);
|
||||||
res.json({ token });
|
res.json({ token });
|
||||||
});
|
}
|
||||||
|
);
|
||||||
|
|
||||||
export const DeleteToken = Stacker(GetUserMiddleware(true, true), async (req: Request, res: Response) => {
|
export const DeleteToken = Stacker(
|
||||||
|
GetUserMiddleware(true, true),
|
||||||
|
async (req: Request, res: Response) => {
|
||||||
let { id } = req.params;
|
let { id } = req.params;
|
||||||
let token = await LoginToken.findById(id);
|
let token = await LoginToken.findById(id);
|
||||||
if (!token || !token.user.equals(req.user._id)) throw new RequestError("Invalid ID", HttpStatusCode.BAD_REQUEST);
|
if (!token || !token.user.equals(req.user._id))
|
||||||
|
throw new RequestError("Invalid ID", HttpStatusCode.BAD_REQUEST);
|
||||||
token.valid = false;
|
token.valid = false;
|
||||||
await LoginToken.save(token);
|
await LoginToken.save(token);
|
||||||
res.json({ success: true });
|
res.json({ success: true });
|
||||||
});
|
}
|
||||||
|
);
|
||||||
|
@ -1,7 +1,10 @@
|
|||||||
import { Router } from "express"
|
import { Router } from "express";
|
||||||
import Stacker from "../../../middlewares/stacker";
|
import Stacker from "../../../middlewares/stacker";
|
||||||
import { GetUserMiddleware } from "../../../middlewares/user";
|
import { GetUserMiddleware } from "../../../middlewares/user";
|
||||||
import TwoFactor, { TFATypes as TwoFATypes, IBackupCode } from "../../../../models/twofactor";
|
import TwoFactor, {
|
||||||
|
TFATypes as TwoFATypes,
|
||||||
|
IBackupCode,
|
||||||
|
} from "../../../../models/twofactor";
|
||||||
import RequestError, { HttpStatusCode } from "../../../../helper/request_error";
|
import RequestError, { HttpStatusCode } from "../../../../helper/request_error";
|
||||||
import moment = require("moment");
|
import moment = require("moment");
|
||||||
import { upgradeToken } from "../helper";
|
import { upgradeToken } from "../helper";
|
||||||
@ -15,14 +18,16 @@ function generateCode(length: number) {
|
|||||||
let bytes = crypto.randomBytes(length);
|
let bytes = crypto.randomBytes(length);
|
||||||
let nrs = "";
|
let nrs = "";
|
||||||
bytes.forEach((b, idx) => {
|
bytes.forEach((b, idx) => {
|
||||||
let nr = Math.floor((b / 255) * 9.9999)
|
let nr = Math.floor((b / 255) * 9.9999);
|
||||||
if (nr > 9) nr = 9;
|
if (nr > 9) nr = 9;
|
||||||
nrs += String(nr);
|
nrs += String(nr);
|
||||||
})
|
});
|
||||||
return nrs;
|
return nrs;
|
||||||
}
|
}
|
||||||
|
|
||||||
BackupCodeRoute.post("/", Stacker(GetUserMiddleware(true, true), async (req, res) => {
|
BackupCodeRoute.post(
|
||||||
|
"/",
|
||||||
|
Stacker(GetUserMiddleware(true, true), async (req, res) => {
|
||||||
//Generating new
|
//Generating new
|
||||||
let codes = Array(10).map(() => generateCode(8));
|
let codes = Array(10).map(() => generateCode(8));
|
||||||
console.log(codes);
|
console.log(codes);
|
||||||
@ -31,44 +36,65 @@ BackupCodeRoute.post("/", Stacker(GetUserMiddleware(true, true), async (req, res
|
|||||||
type: TwoFATypes.OTC,
|
type: TwoFATypes.OTC,
|
||||||
valid: true,
|
valid: true,
|
||||||
data: codes,
|
data: codes,
|
||||||
name: ""
|
name: "",
|
||||||
})
|
});
|
||||||
await TwoFactor.save(twofactor);
|
await TwoFactor.save(twofactor);
|
||||||
res.json({
|
res.json({
|
||||||
codes,
|
codes,
|
||||||
id: twofactor._id
|
id: twofactor._id,
|
||||||
|
});
|
||||||
})
|
})
|
||||||
}));
|
);
|
||||||
|
|
||||||
BackupCodeRoute.put("/", Stacker(GetUserMiddleware(true, false, undefined, false), async (req, res) => {
|
BackupCodeRoute.put(
|
||||||
|
"/",
|
||||||
|
Stacker(
|
||||||
|
GetUserMiddleware(true, false, undefined, false),
|
||||||
|
async (req, res) => {
|
||||||
let { login, special } = req.token;
|
let { login, special } = req.token;
|
||||||
let { id, code }: { id: string, code: string } = req.body;
|
let { id, code }: { id: string; code: string } = req.body;
|
||||||
|
|
||||||
let twofactor: IBackupCode = await TwoFactor.findById(id);
|
let twofactor: IBackupCode = await TwoFactor.findById(id);
|
||||||
|
|
||||||
if (!twofactor || !twofactor.valid || !twofactor.user.equals(req.user._id) || twofactor.type !== TwoFATypes.OTC) {
|
if (
|
||||||
throw new RequestError("Invalid Method!", HttpStatusCode.BAD_REQUEST);
|
!twofactor ||
|
||||||
|
!twofactor.valid ||
|
||||||
|
!twofactor.user.equals(req.user._id) ||
|
||||||
|
twofactor.type !== TwoFATypes.OTC
|
||||||
|
) {
|
||||||
|
throw new RequestError(
|
||||||
|
"Invalid Method!",
|
||||||
|
HttpStatusCode.BAD_REQUEST
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (twofactor.expires && moment().isAfter(twofactor.expires)) {
|
if (twofactor.expires && moment().isAfter(twofactor.expires)) {
|
||||||
twofactor.valid = false;
|
twofactor.valid = false;
|
||||||
await TwoFactor.save(twofactor);
|
await TwoFactor.save(twofactor);
|
||||||
throw new RequestError("Invalid Method!", HttpStatusCode.BAD_REQUEST);
|
throw new RequestError(
|
||||||
|
"Invalid Method!",
|
||||||
|
HttpStatusCode.BAD_REQUEST
|
||||||
|
);
|
||||||
}
|
}
|
||||||
code = code.replace(/\s/g, "");
|
code = code.replace(/\s/g, "");
|
||||||
let valid = twofactor.data.find(c => c === code);
|
let valid = twofactor.data.find((c) => c === code);
|
||||||
|
|
||||||
if (valid) {
|
if (valid) {
|
||||||
twofactor.data = twofactor.data.filter(c => c !== code);
|
twofactor.data = twofactor.data.filter((c) => c !== code);
|
||||||
await TwoFactor.save(twofactor);
|
await TwoFactor.save(twofactor);
|
||||||
let [login_exp, special_exp] = await Promise.all([
|
let [login_exp, special_exp] = await Promise.all([
|
||||||
upgradeToken(login),
|
upgradeToken(login),
|
||||||
upgradeToken(special)
|
upgradeToken(special),
|
||||||
]);
|
]);
|
||||||
res.json({ success: true, login_exp, special_exp })
|
res.json({ success: true, login_exp, special_exp });
|
||||||
} else {
|
} else {
|
||||||
throw new RequestError("Invalid or already used code!", HttpStatusCode.BAD_REQUEST);
|
throw new RequestError(
|
||||||
|
"Invalid or already used code!",
|
||||||
|
HttpStatusCode.BAD_REQUEST
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}))
|
}
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
export default BackupCodeRoute;
|
export default BackupCodeRoute;
|
||||||
|
@ -6,7 +6,10 @@ export async function upgradeToken(token: ILoginToken) {
|
|||||||
token.valid = true;
|
token.valid = true;
|
||||||
token.validated = true;
|
token.validated = true;
|
||||||
//TODO durations from config
|
//TODO durations from config
|
||||||
let expires = (token.special ? moment().add(30, "minute") : moment().add(6, "months")).toDate();
|
let expires = (token.special
|
||||||
|
? moment().add(30, "minute")
|
||||||
|
: moment().add(6, "months")
|
||||||
|
).toDate();
|
||||||
token.validTill = expires;
|
token.validTill = expires;
|
||||||
await LoginToken.save(token);
|
await LoginToken.save(token);
|
||||||
return expires;
|
return expires;
|
||||||
|
@ -3,32 +3,41 @@ import YubiKeyRoute from "./yubikey";
|
|||||||
import { GetUserMiddleware } from "../../middlewares/user";
|
import { GetUserMiddleware } from "../../middlewares/user";
|
||||||
import Stacker from "../../middlewares/stacker";
|
import Stacker from "../../middlewares/stacker";
|
||||||
import TwoFactor from "../../../models/twofactor";
|
import TwoFactor from "../../../models/twofactor";
|
||||||
import * as moment from "moment"
|
import * as moment from "moment";
|
||||||
import RequestError, { HttpStatusCode } from "../../../helper/request_error";
|
import RequestError, { HttpStatusCode } from "../../../helper/request_error";
|
||||||
import OTCRoute from "./otc";
|
import OTCRoute from "./otc";
|
||||||
import BackupCodeRoute from "./backup";
|
import BackupCodeRoute from "./backup";
|
||||||
|
|
||||||
const TwoFactorRouter = Router();
|
const TwoFactorRouter = Router();
|
||||||
|
|
||||||
TwoFactorRouter.get("/", Stacker(GetUserMiddleware(true, true), async (req, res) => {
|
TwoFactorRouter.get(
|
||||||
let twofactor = await TwoFactor.find({ user: req.user._id, valid: true })
|
"/",
|
||||||
let expired = twofactor.filter(e => e.expires ? moment().isAfter(moment(e.expires)) : false)
|
Stacker(GetUserMiddleware(true, true), async (req, res) => {
|
||||||
await Promise.all(expired.map(e => {
|
let twofactor = await TwoFactor.find({ user: req.user._id, valid: true });
|
||||||
|
let expired = twofactor.filter((e) =>
|
||||||
|
e.expires ? moment().isAfter(moment(e.expires)) : false
|
||||||
|
);
|
||||||
|
await Promise.all(
|
||||||
|
expired.map((e) => {
|
||||||
e.valid = false;
|
e.valid = false;
|
||||||
return TwoFactor.save(e);
|
return TwoFactor.save(e);
|
||||||
}));
|
})
|
||||||
twofactor = twofactor.filter(e => e.valid);
|
);
|
||||||
let tfa = twofactor.map(e => {
|
twofactor = twofactor.filter((e) => e.valid);
|
||||||
|
let tfa = twofactor.map((e) => {
|
||||||
return {
|
return {
|
||||||
id: e._id,
|
id: e._id,
|
||||||
name: e.name,
|
name: e.name,
|
||||||
type: e.type
|
type: e.type,
|
||||||
}
|
};
|
||||||
})
|
});
|
||||||
res.json({ methods: tfa });
|
res.json({ methods: tfa });
|
||||||
}));
|
})
|
||||||
|
);
|
||||||
|
|
||||||
TwoFactorRouter.delete("/:id", Stacker(GetUserMiddleware(true, true), async (req, res) => {
|
TwoFactorRouter.delete(
|
||||||
|
"/:id",
|
||||||
|
Stacker(GetUserMiddleware(true, true), async (req, res) => {
|
||||||
let { id } = req.params;
|
let { id } = req.params;
|
||||||
let tfa = await TwoFactor.findById(id);
|
let tfa = await TwoFactor.findById(id);
|
||||||
if (!tfa || !tfa.user.equals(req.user._id)) {
|
if (!tfa || !tfa.user.equals(req.user._id)) {
|
||||||
@ -37,7 +46,8 @@ TwoFactorRouter.delete("/:id", Stacker(GetUserMiddleware(true, true), async (req
|
|||||||
tfa.valid = false;
|
tfa.valid = false;
|
||||||
await TwoFactor.save(tfa);
|
await TwoFactor.save(tfa);
|
||||||
res.json({ success: true });
|
res.json({ success: true });
|
||||||
}));
|
})
|
||||||
|
);
|
||||||
|
|
||||||
TwoFactorRouter.use("/yubikey", YubiKeyRoute);
|
TwoFactorRouter.use("/yubikey", YubiKeyRoute);
|
||||||
TwoFactorRouter.use("/otc", OTCRoute);
|
TwoFactorRouter.use("/otc", OTCRoute);
|
||||||
|
@ -1,7 +1,10 @@
|
|||||||
import { Router } from "express"
|
import { Router } from "express";
|
||||||
import Stacker from "../../../middlewares/stacker";
|
import Stacker from "../../../middlewares/stacker";
|
||||||
import { GetUserMiddleware } from "../../../middlewares/user";
|
import { GetUserMiddleware } from "../../../middlewares/user";
|
||||||
import TwoFactor, { TFATypes as TwoFATypes, IOTC } from "../../../../models/twofactor";
|
import TwoFactor, {
|
||||||
|
TFATypes as TwoFATypes,
|
||||||
|
IOTC,
|
||||||
|
} from "../../../../models/twofactor";
|
||||||
import RequestError, { HttpStatusCode } from "../../../../helper/request_error";
|
import RequestError, { HttpStatusCode } from "../../../../helper/request_error";
|
||||||
import moment = require("moment");
|
import moment = require("moment");
|
||||||
import { upgradeToken } from "../helper";
|
import { upgradeToken } from "../helper";
|
||||||
@ -13,33 +16,43 @@ import config from "../../../../config";
|
|||||||
|
|
||||||
const OTCRoute = Router();
|
const OTCRoute = Router();
|
||||||
|
|
||||||
OTCRoute.post("/", Stacker(GetUserMiddleware(true, true), async (req, res) => {
|
OTCRoute.post(
|
||||||
|
"/",
|
||||||
|
Stacker(GetUserMiddleware(true, true), async (req, res) => {
|
||||||
const { type } = req.query;
|
const { type } = req.query;
|
||||||
if (type === "create") {
|
if (type === "create") {
|
||||||
//Generating new
|
//Generating new
|
||||||
let secret = speakeasy.generateSecret({
|
let secret = speakeasy.generateSecret({
|
||||||
name: config.core.name,
|
name: config.core.name,
|
||||||
issuer: config.core.name
|
issuer: config.core.name,
|
||||||
});
|
});
|
||||||
let twofactor = TwoFactor.new(<IOTC>{
|
let twofactor = TwoFactor.new(<IOTC>{
|
||||||
user: req.user._id,
|
user: req.user._id,
|
||||||
type: TwoFATypes.OTC,
|
type: TwoFATypes.OTC,
|
||||||
valid: false,
|
valid: false,
|
||||||
data: secret.base32
|
data: secret.base32,
|
||||||
})
|
});
|
||||||
let dataurl = await qrcode.toDataURL(secret.otpauth_url);
|
let dataurl = await qrcode.toDataURL(secret.otpauth_url);
|
||||||
await TwoFactor.save(twofactor);
|
await TwoFactor.save(twofactor);
|
||||||
res.json({
|
res.json({
|
||||||
image: dataurl,
|
image: dataurl,
|
||||||
id: twofactor._id
|
id: twofactor._id,
|
||||||
})
|
});
|
||||||
} else if (type === "validate") {
|
} else if (type === "validate") {
|
||||||
// Checking code and marking as valid
|
// Checking code and marking as valid
|
||||||
const { code, id } = req.body;
|
const { code, id } = req.body;
|
||||||
Logging.debug(req.body, id);
|
Logging.debug(req.body, id);
|
||||||
let twofactor: IOTC = await TwoFactor.findById(id);
|
let twofactor: IOTC = await TwoFactor.findById(id);
|
||||||
const err = () => { throw new RequestError("Invalid ID!", HttpStatusCode.BAD_REQUEST) };
|
const err = () => {
|
||||||
if (!twofactor || !twofactor.user.equals(req.user._id) || twofactor.type !== TwoFATypes.OTC || !twofactor.data || twofactor.valid) {
|
throw new RequestError("Invalid ID!", HttpStatusCode.BAD_REQUEST);
|
||||||
|
};
|
||||||
|
if (
|
||||||
|
!twofactor ||
|
||||||
|
!twofactor.user.equals(req.user._id) ||
|
||||||
|
twofactor.type !== TwoFATypes.OTC ||
|
||||||
|
!twofactor.data ||
|
||||||
|
twofactor.valid
|
||||||
|
) {
|
||||||
Logging.debug("Not found or wrong user", twofactor);
|
Logging.debug("Not found or wrong user", twofactor);
|
||||||
err();
|
err();
|
||||||
}
|
}
|
||||||
@ -53,8 +66,8 @@ OTCRoute.post("/", Stacker(GetUserMiddleware(true, true), async (req, res) => {
|
|||||||
let valid = speakeasy.totp.verify({
|
let valid = speakeasy.totp.verify({
|
||||||
secret: twofactor.data,
|
secret: twofactor.data,
|
||||||
encoding: "base32",
|
encoding: "base32",
|
||||||
token: code
|
token: code,
|
||||||
})
|
});
|
||||||
|
|
||||||
if (valid) {
|
if (valid) {
|
||||||
twofactor.expires = undefined;
|
twofactor.expires = undefined;
|
||||||
@ -67,39 +80,56 @@ OTCRoute.post("/", Stacker(GetUserMiddleware(true, true), async (req, res) => {
|
|||||||
} else {
|
} else {
|
||||||
throw new RequestError("Invalid type", HttpStatusCode.BAD_REQUEST);
|
throw new RequestError("Invalid type", HttpStatusCode.BAD_REQUEST);
|
||||||
}
|
}
|
||||||
}));
|
})
|
||||||
|
);
|
||||||
|
|
||||||
OTCRoute.put("/", Stacker(GetUserMiddleware(true, false, undefined, false), async (req, res) => {
|
OTCRoute.put(
|
||||||
|
"/",
|
||||||
|
Stacker(
|
||||||
|
GetUserMiddleware(true, false, undefined, false),
|
||||||
|
async (req, res) => {
|
||||||
let { login, special } = req.token;
|
let { login, special } = req.token;
|
||||||
let { id, code } = req.body;
|
let { id, code } = req.body;
|
||||||
let twofactor: IOTC = await TwoFactor.findById(id);
|
let twofactor: IOTC = await TwoFactor.findById(id);
|
||||||
|
|
||||||
if (!twofactor || !twofactor.valid || !twofactor.user.equals(req.user._id) || twofactor.type !== TwoFATypes.OTC) {
|
if (
|
||||||
throw new RequestError("Invalid Method!", HttpStatusCode.BAD_REQUEST);
|
!twofactor ||
|
||||||
|
!twofactor.valid ||
|
||||||
|
!twofactor.user.equals(req.user._id) ||
|
||||||
|
twofactor.type !== TwoFATypes.OTC
|
||||||
|
) {
|
||||||
|
throw new RequestError(
|
||||||
|
"Invalid Method!",
|
||||||
|
HttpStatusCode.BAD_REQUEST
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (twofactor.expires && moment().isAfter(twofactor.expires)) {
|
if (twofactor.expires && moment().isAfter(twofactor.expires)) {
|
||||||
twofactor.valid = false;
|
twofactor.valid = false;
|
||||||
await TwoFactor.save(twofactor);
|
await TwoFactor.save(twofactor);
|
||||||
throw new RequestError("Invalid Method!", HttpStatusCode.BAD_REQUEST);
|
throw new RequestError(
|
||||||
|
"Invalid Method!",
|
||||||
|
HttpStatusCode.BAD_REQUEST
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
let valid = speakeasy.totp.verify({
|
let valid = speakeasy.totp.verify({
|
||||||
secret: twofactor.data,
|
secret: twofactor.data,
|
||||||
encoding: "base32",
|
encoding: "base32",
|
||||||
token: code
|
token: code,
|
||||||
})
|
});
|
||||||
|
|
||||||
if (valid) {
|
if (valid) {
|
||||||
let [login_exp, special_exp] = await Promise.all([
|
let [login_exp, special_exp] = await Promise.all([
|
||||||
upgradeToken(login),
|
upgradeToken(login),
|
||||||
upgradeToken(special)
|
upgradeToken(special),
|
||||||
]);
|
]);
|
||||||
res.json({ success: true, login_exp, special_exp })
|
res.json({ success: true, login_exp, special_exp });
|
||||||
} else {
|
} else {
|
||||||
throw new RequestError("Invalid Code", HttpStatusCode.BAD_REQUEST);
|
throw new RequestError("Invalid Code", HttpStatusCode.BAD_REQUEST);
|
||||||
}
|
}
|
||||||
}))
|
}
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
export default OTCRoute;
|
export default OTCRoute;
|
||||||
|
@ -1,9 +1,12 @@
|
|||||||
import { Router, Request } from "express"
|
import { Router, Request } from "express";
|
||||||
import Stacker from "../../../middlewares/stacker";
|
import Stacker from "../../../middlewares/stacker";
|
||||||
import { UserMiddleware, GetUserMiddleware } from "../../../middlewares/user";
|
import { UserMiddleware, GetUserMiddleware } from "../../../middlewares/user";
|
||||||
import * as u2f from "u2f";
|
import * as u2f from "u2f";
|
||||||
import config from "../../../../config";
|
import config from "../../../../config";
|
||||||
import TwoFactor, { TFATypes as TwoFATypes, IYubiKey } from "../../../../models/twofactor";
|
import TwoFactor, {
|
||||||
|
TFATypes as TwoFATypes,
|
||||||
|
IYubiKey,
|
||||||
|
} from "../../../../models/twofactor";
|
||||||
import RequestError, { HttpStatusCode } from "../../../../helper/request_error";
|
import RequestError, { HttpStatusCode } from "../../../../helper/request_error";
|
||||||
import moment = require("moment");
|
import moment = require("moment");
|
||||||
import LoginToken from "../../../../models/login_token";
|
import LoginToken from "../../../../models/login_token";
|
||||||
@ -15,7 +18,9 @@ const U2FRoute = Router();
|
|||||||
/**
|
/**
|
||||||
* Registerinf a new YubiKey
|
* Registerinf a new YubiKey
|
||||||
*/
|
*/
|
||||||
U2FRoute.post("/", Stacker(GetUserMiddleware(true, true), async (req, res) => {
|
U2FRoute.post(
|
||||||
|
"/",
|
||||||
|
Stacker(GetUserMiddleware(true, true), async (req, res) => {
|
||||||
const { type } = req.query;
|
const { type } = req.query;
|
||||||
if (type === "challenge") {
|
if (type === "challenge") {
|
||||||
const registrationRequest = u2f.request(config.core.url);
|
const registrationRequest = u2f.request(config.core.url);
|
||||||
@ -25,17 +30,29 @@ U2FRoute.post("/", Stacker(GetUserMiddleware(true, true), async (req, res) => {
|
|||||||
type: TwoFATypes.U2F,
|
type: TwoFATypes.U2F,
|
||||||
valid: false,
|
valid: false,
|
||||||
data: {
|
data: {
|
||||||
registration: registrationRequest
|
registration: registrationRequest,
|
||||||
}
|
},
|
||||||
})
|
});
|
||||||
await TwoFactor.save(twofactor);
|
await TwoFactor.save(twofactor);
|
||||||
res.json({ request: registrationRequest, id: twofactor._id, appid: config.core.url });
|
res.json({
|
||||||
|
request: registrationRequest,
|
||||||
|
id: twofactor._id,
|
||||||
|
appid: config.core.url,
|
||||||
|
});
|
||||||
} else {
|
} else {
|
||||||
const { response, id } = req.body;
|
const { response, id } = req.body;
|
||||||
Logging.debug(req.body, id);
|
Logging.debug(req.body, id);
|
||||||
let twofactor: IYubiKey = await TwoFactor.findById(id);
|
let twofactor: IYubiKey = await TwoFactor.findById(id);
|
||||||
const err = () => { throw new RequestError("Invalid ID!", HttpStatusCode.BAD_REQUEST) };
|
const err = () => {
|
||||||
if (!twofactor || !twofactor.user.equals(req.user._id) || twofactor.type !== TwoFATypes.U2F || !twofactor.data.registration || twofactor.valid) {
|
throw new RequestError("Invalid ID!", HttpStatusCode.BAD_REQUEST);
|
||||||
|
};
|
||||||
|
if (
|
||||||
|
!twofactor ||
|
||||||
|
!twofactor.user.equals(req.user._id) ||
|
||||||
|
twofactor.type !== TwoFATypes.U2F ||
|
||||||
|
!twofactor.data.registration ||
|
||||||
|
twofactor.valid
|
||||||
|
) {
|
||||||
Logging.debug("Not found or wrong user", twofactor);
|
Logging.debug("Not found or wrong user", twofactor);
|
||||||
err();
|
err();
|
||||||
}
|
}
|
||||||
@ -46,45 +63,66 @@ U2FRoute.post("/", Stacker(GetUserMiddleware(true, true), async (req, res) => {
|
|||||||
err();
|
err();
|
||||||
}
|
}
|
||||||
|
|
||||||
const result = u2f.checkRegistration(twofactor.data.registration, response);
|
const result = u2f.checkRegistration(
|
||||||
|
twofactor.data.registration,
|
||||||
|
response
|
||||||
|
);
|
||||||
|
|
||||||
if (result.successful) {
|
if (result.successful) {
|
||||||
twofactor.data = {
|
twofactor.data = {
|
||||||
keyHandle: result.keyHandle,
|
keyHandle: result.keyHandle,
|
||||||
publicKey: result.publicKey
|
publicKey: result.publicKey,
|
||||||
}
|
};
|
||||||
twofactor.expires = undefined;
|
twofactor.expires = undefined;
|
||||||
twofactor.valid = true;
|
twofactor.valid = true;
|
||||||
await TwoFactor.save(twofactor);
|
await TwoFactor.save(twofactor);
|
||||||
res.json({ success: true });
|
res.json({ success: true });
|
||||||
} else {
|
} else {
|
||||||
throw new RequestError(result.errorMessage, HttpStatusCode.BAD_REQUEST);
|
throw new RequestError(
|
||||||
|
result.errorMessage,
|
||||||
|
HttpStatusCode.BAD_REQUEST
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}));
|
})
|
||||||
|
);
|
||||||
|
|
||||||
U2FRoute.get("/", Stacker(GetUserMiddleware(true, false, undefined, false), async (req, res) => {
|
U2FRoute.get(
|
||||||
|
"/",
|
||||||
|
Stacker(
|
||||||
|
GetUserMiddleware(true, false, undefined, false),
|
||||||
|
async (req, res) => {
|
||||||
let { login, special } = req.token;
|
let { login, special } = req.token;
|
||||||
let twofactor: IYubiKey = await TwoFactor.findOne({ user: req.user._id, type: TwoFATypes.U2F, valid: true })
|
let twofactor: IYubiKey = await TwoFactor.findOne({
|
||||||
|
user: req.user._id,
|
||||||
|
type: TwoFATypes.U2F,
|
||||||
|
valid: true,
|
||||||
|
});
|
||||||
|
|
||||||
if (!twofactor) {
|
if (!twofactor) {
|
||||||
throw new RequestError("Invalid Method!", HttpStatusCode.BAD_REQUEST);
|
throw new RequestError(
|
||||||
|
"Invalid Method!",
|
||||||
|
HttpStatusCode.BAD_REQUEST
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (twofactor.expires) {
|
if (twofactor.expires) {
|
||||||
if (moment().isAfter(twofactor.expires)) {
|
if (moment().isAfter(twofactor.expires)) {
|
||||||
twofactor.valid = false;
|
twofactor.valid = false;
|
||||||
await TwoFactor.save(twofactor);
|
await TwoFactor.save(twofactor);
|
||||||
throw new RequestError("Invalid Method!", HttpStatusCode.BAD_REQUEST);
|
throw new RequestError(
|
||||||
|
"Invalid Method!",
|
||||||
|
HttpStatusCode.BAD_REQUEST
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let request = u2f.request(config.core.url, twofactor.data.keyHandle);
|
let request = u2f.request(config.core.url, twofactor.data.keyHandle);
|
||||||
login.data = {
|
login.data = {
|
||||||
type: "ykr",
|
type: "ykr",
|
||||||
request
|
request,
|
||||||
};
|
};
|
||||||
let r;;
|
let r;
|
||||||
if (special) {
|
if (special) {
|
||||||
special.data = login.data;
|
special.data = login.data;
|
||||||
r = LoginToken.save(special);
|
r = LoginToken.save(special);
|
||||||
@ -92,43 +130,77 @@ U2FRoute.get("/", Stacker(GetUserMiddleware(true, false, undefined, false), asyn
|
|||||||
|
|
||||||
await Promise.all([r, LoginToken.save(login)]);
|
await Promise.all([r, LoginToken.save(login)]);
|
||||||
res.json({ request });
|
res.json({ request });
|
||||||
}))
|
}
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
U2FRoute.put("/", Stacker(GetUserMiddleware(true, false, undefined, false), async (req, res) => {
|
U2FRoute.put(
|
||||||
|
"/",
|
||||||
|
Stacker(
|
||||||
|
GetUserMiddleware(true, false, undefined, false),
|
||||||
|
async (req, res) => {
|
||||||
let { login, special } = req.token;
|
let { login, special } = req.token;
|
||||||
let twofactor: IYubiKey = await TwoFactor.findOne({ user: req.user._id, type: TwoFATypes.U2F, valid: true })
|
let twofactor: IYubiKey = await TwoFactor.findOne({
|
||||||
|
user: req.user._id,
|
||||||
|
type: TwoFATypes.U2F,
|
||||||
|
valid: true,
|
||||||
|
});
|
||||||
|
|
||||||
let { response } = req.body;
|
let { response } = req.body;
|
||||||
if (!twofactor || !login.data || login.data.type !== "ykr" || special && (!special.data || special.data.type !== "ykr")) {
|
if (
|
||||||
throw new RequestError("Invalid Method!", HttpStatusCode.BAD_REQUEST);
|
!twofactor ||
|
||||||
|
!login.data ||
|
||||||
|
login.data.type !== "ykr" ||
|
||||||
|
(special && (!special.data || special.data.type !== "ykr"))
|
||||||
|
) {
|
||||||
|
throw new RequestError(
|
||||||
|
"Invalid Method!",
|
||||||
|
HttpStatusCode.BAD_REQUEST
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (twofactor.expires && moment().isAfter(twofactor.expires)) {
|
if (twofactor.expires && moment().isAfter(twofactor.expires)) {
|
||||||
twofactor.valid = false;
|
twofactor.valid = false;
|
||||||
await TwoFactor.save(twofactor);
|
await TwoFactor.save(twofactor);
|
||||||
throw new RequestError("Invalid Method!", HttpStatusCode.BAD_REQUEST);
|
throw new RequestError(
|
||||||
|
"Invalid Method!",
|
||||||
|
HttpStatusCode.BAD_REQUEST
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
let login_exp;
|
let login_exp;
|
||||||
let special_exp;
|
let special_exp;
|
||||||
let result = u2f.checkSignature(login.data.request, response, twofactor.data.publicKey);
|
let result = u2f.checkSignature(
|
||||||
|
login.data.request,
|
||||||
|
response,
|
||||||
|
twofactor.data.publicKey
|
||||||
|
);
|
||||||
if (result.successful) {
|
if (result.successful) {
|
||||||
if (special) {
|
if (special) {
|
||||||
let result = u2f.checkSignature(special.data.request, response, twofactor.data.publicKey);
|
let result = u2f.checkSignature(
|
||||||
|
special.data.request,
|
||||||
|
response,
|
||||||
|
twofactor.data.publicKey
|
||||||
|
);
|
||||||
if (result.successful) {
|
if (result.successful) {
|
||||||
special_exp = await upgradeToken(special);
|
special_exp = await upgradeToken(special);
|
||||||
}
|
} else {
|
||||||
else {
|
throw new RequestError(
|
||||||
throw new RequestError(result.errorMessage, HttpStatusCode.BAD_REQUEST);
|
result.errorMessage,
|
||||||
|
HttpStatusCode.BAD_REQUEST
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
login_exp = await upgradeToken(login);
|
login_exp = await upgradeToken(login);
|
||||||
|
} else {
|
||||||
|
throw new RequestError(
|
||||||
|
result.errorMessage,
|
||||||
|
HttpStatusCode.BAD_REQUEST
|
||||||
|
);
|
||||||
}
|
}
|
||||||
else {
|
res.json({ success: true, login_exp, special_exp });
|
||||||
throw new RequestError(result.errorMessage, HttpStatusCode.BAD_REQUEST);
|
|
||||||
}
|
}
|
||||||
res.json({ success: true, login_exp, special_exp })
|
)
|
||||||
}))
|
);
|
||||||
|
|
||||||
export default U2FRoute;
|
export default U2FRoute;
|
||||||
|
@ -7,64 +7,68 @@ import * as ini from "ini";
|
|||||||
dotenv.config();
|
dotenv.config();
|
||||||
|
|
||||||
export interface DatabaseConfig {
|
export interface DatabaseConfig {
|
||||||
host: string
|
host: string;
|
||||||
database: string
|
database: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface WebConfig {
|
export interface WebConfig {
|
||||||
port: string
|
port: string;
|
||||||
secure: "true" | "false" | undefined
|
secure: "true" | "false" | undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface CoreConfig {
|
export interface CoreConfig {
|
||||||
name: string
|
name: string;
|
||||||
url: string
|
url: string;
|
||||||
dev: boolean
|
dev: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Config {
|
export interface Config {
|
||||||
core: CoreConfig
|
core: CoreConfig;
|
||||||
database: DatabaseConfig
|
database: DatabaseConfig;
|
||||||
web: WebConfig
|
web: WebConfig;
|
||||||
}
|
}
|
||||||
|
|
||||||
const config = parse({
|
const config = (parse(
|
||||||
|
{
|
||||||
core: {
|
core: {
|
||||||
dev: {
|
dev: {
|
||||||
default: false,
|
default: false,
|
||||||
type: Boolean
|
type: Boolean,
|
||||||
},
|
},
|
||||||
name: {
|
name: {
|
||||||
type: String,
|
type: String,
|
||||||
default: "Open Auth"
|
default: "Open Auth",
|
||||||
},
|
},
|
||||||
url: String
|
url: String,
|
||||||
},
|
},
|
||||||
database: {
|
database: {
|
||||||
database: {
|
database: {
|
||||||
type: String,
|
type: String,
|
||||||
default: "openauth"
|
default: "openauth",
|
||||||
},
|
},
|
||||||
host: {
|
host: {
|
||||||
type: String,
|
type: String,
|
||||||
default: "localhost"
|
default: "localhost",
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
web: {
|
web: {
|
||||||
port: {
|
port: {
|
||||||
type: Number,
|
type: Number,
|
||||||
default: 3004
|
default: 3004,
|
||||||
},
|
},
|
||||||
secure: {
|
secure: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
default: false
|
default: false,
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
}, "config.ini") as any as Config;
|
},
|
||||||
|
"config.ini"
|
||||||
|
) as any) as Config;
|
||||||
|
|
||||||
if (process.env.DEV === "true")
|
if (process.env.DEV === "true") config.core.dev = true;
|
||||||
config.core.dev = true;
|
|
||||||
if (config.core.dev)
|
if (config.core.dev)
|
||||||
Logging.warning("DEV mode active. This can cause major performance issues, data loss and vulnerabilities! ")
|
Logging.warning(
|
||||||
|
"DEV mode active. This can cause major performance issues, data loss and vulnerabilities! "
|
||||||
|
);
|
||||||
|
|
||||||
export default config;
|
export default config;
|
@ -1,7 +1,7 @@
|
|||||||
import SafeMongo from "@hibas123/safe_mongo";
|
import SafeMongo from "@hibas123/safe_mongo";
|
||||||
import Config from "./config"
|
import Config from "./config";
|
||||||
let dbname = "openauth"
|
let dbname = "openauth";
|
||||||
let host = "localhost"
|
let host = "localhost";
|
||||||
if (Config.database) {
|
if (Config.database) {
|
||||||
if (Config.database.database) dbname = Config.database.database;
|
if (Config.database.database) dbname = Config.database.database;
|
||||||
if (Config.database.host) host = Config.database.host;
|
if (Config.database.host) host = Config.database.host;
|
||||||
|
2
src/express.d.ts
vendored
2
src/express.d.ts
vendored
@ -11,6 +11,6 @@ declare module "express" {
|
|||||||
token: {
|
token: {
|
||||||
login: ILoginToken;
|
login: ILoginToken;
|
||||||
special?: ILoginToken;
|
special?: ILoginToken;
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -9,42 +9,52 @@ export interface OAuthJWT {
|
|||||||
user: string;
|
user: string;
|
||||||
username: string;
|
username: string;
|
||||||
permissions: string[];
|
permissions: string[];
|
||||||
application: string
|
application: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const issuer = config.core.url;
|
const issuer = config.core.url;
|
||||||
|
|
||||||
export const IDTokenJWTExp = moment.duration(30, "m").asSeconds();
|
export const IDTokenJWTExp = moment.duration(30, "m").asSeconds();
|
||||||
export function getIDToken(user: IUser, client_id: string, nonce: string) {
|
export function getIDToken(user: IUser, client_id: string, nonce: string) {
|
||||||
return createJWT({
|
return createJWT(
|
||||||
|
{
|
||||||
user: user.uid,
|
user: user.uid,
|
||||||
name: user.name,
|
name: user.name,
|
||||||
nickname: user.username,
|
nickname: user.username,
|
||||||
username: user.username,
|
username: user.username,
|
||||||
preferred_username: user.username,
|
preferred_username: user.username,
|
||||||
gender: Gender[user.gender],
|
gender: Gender[user.gender],
|
||||||
nonce
|
nonce,
|
||||||
}, {
|
},
|
||||||
|
{
|
||||||
expiresIn: IDTokenJWTExp,
|
expiresIn: IDTokenJWTExp,
|
||||||
issuer,
|
issuer,
|
||||||
algorithm: "RS256",
|
algorithm: "RS256",
|
||||||
subject: user.uid,
|
subject: user.uid,
|
||||||
audience: client_id
|
audience: client_id,
|
||||||
})
|
}
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export const AccessTokenJWTExp = moment.duration(6, "h");
|
export const AccessTokenJWTExp = moment.duration(6, "h");
|
||||||
export function getAccessTokenJWT(token: { user: IUser, permissions: ObjectID[], client: IClient }) {
|
export function getAccessTokenJWT(token: {
|
||||||
return createJWT(<OAuthJWT>{
|
user: IUser;
|
||||||
|
permissions: ObjectID[];
|
||||||
|
client: IClient;
|
||||||
|
}) {
|
||||||
|
return createJWT(
|
||||||
|
<OAuthJWT>{
|
||||||
user: token.user.uid,
|
user: token.user.uid,
|
||||||
username: token.user.username,
|
username: token.user.username,
|
||||||
permissions: token.permissions.map(p => p.toHexString()),
|
permissions: token.permissions.map((p) => p.toHexString()),
|
||||||
application: token.client.client_id
|
application: token.client.client_id,
|
||||||
}, {
|
},
|
||||||
|
{
|
||||||
expiresIn: AccessTokenJWTExp.asSeconds(),
|
expiresIn: AccessTokenJWTExp.asSeconds(),
|
||||||
issuer,
|
issuer,
|
||||||
algorithm: "RS256",
|
algorithm: "RS256",
|
||||||
subject: token.user.uid,
|
subject: token.user.uid,
|
||||||
audience: token.client.client_id
|
audience: token.client.client_id,
|
||||||
})
|
}
|
||||||
|
);
|
||||||
}
|
}
|
@ -1,5 +1,7 @@
|
|||||||
import { Request, Response, NextFunction } from "express"
|
import { Request, Response, NextFunction } from "express";
|
||||||
|
|
||||||
export default (fn: (req: Request, res: Response, next: NextFunction) => Promise<any>) => (req: Request, res: Response, next: NextFunction) => {
|
export default (
|
||||||
Promise.resolve(fn(req, res, next)).catch(next)
|
fn: (req: Request, res: Response, next: NextFunction) => Promise<any>
|
||||||
}
|
) => (req: Request, res: Response, next: NextFunction) => {
|
||||||
|
Promise.resolve(fn(req, res, next)).catch(next);
|
||||||
|
};
|
||||||
|
@ -1,10 +1,8 @@
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Hypertext Transfer Protocol (HTTP) response status codes.
|
* Hypertext Transfer Protocol (HTTP) response status codes.
|
||||||
* @see {@link https://en.wikipedia.org/wiki/List_of_HTTP_status_codes}
|
* @see {@link https://en.wikipedia.org/wiki/List_of_HTTP_status_codes}
|
||||||
*/
|
*/
|
||||||
export enum HttpStatusCode {
|
export enum HttpStatusCode {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The server has received the request headers and the client should proceed to send the request body
|
* The server has received the request headers and the client should proceed to send the request body
|
||||||
* (in the case of a request for which a body needs to be sent; for example, a POST request).
|
* (in the case of a request for which a body needs to be sent; for example, a POST request).
|
||||||
@ -376,13 +374,17 @@ export enum HttpStatusCode {
|
|||||||
* Intended for use by intercepting proxies used to control access to the network (e.g., "captive portals" used
|
* Intended for use by intercepting proxies used to control access to the network (e.g., "captive portals" used
|
||||||
* to require agreement to Terms of Service before granting full Internet access via a Wi-Fi hotspot).
|
* to require agreement to Terms of Service before granting full Internet access via a Wi-Fi hotspot).
|
||||||
*/
|
*/
|
||||||
NETWORK_AUTHENTICATION_REQUIRED = 511
|
NETWORK_AUTHENTICATION_REQUIRED = 511,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
export default class RequestError extends Error {
|
export default class RequestError extends Error {
|
||||||
constructor(message: any, public status: HttpStatusCode, public nolog: boolean = false, public additional: any = undefined) {
|
constructor(
|
||||||
super("")
|
message: any,
|
||||||
|
public status: HttpStatusCode,
|
||||||
|
public nolog: boolean = false,
|
||||||
|
public additional: any = undefined
|
||||||
|
) {
|
||||||
|
super("");
|
||||||
this.message = message;
|
this.message = message;
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -1,14 +1,18 @@
|
|||||||
// import * as crypto from "crypto-js"
|
// import * as crypto from "crypto-js"
|
||||||
import { IUser } from "../models/user";
|
import { IUser } from "../models/user";
|
||||||
import { IClient } from "../models/client";
|
import { IClient } from "../models/client";
|
||||||
import * as crypto from "crypto"
|
import * as crypto from "crypto";
|
||||||
|
|
||||||
function sha512(text: string) {
|
function sha512(text: string) {
|
||||||
let hash = crypto.createHash("sha512")
|
let hash = crypto.createHash("sha512");
|
||||||
hash.update(text)
|
hash.update(text);
|
||||||
return hash.digest("base64")
|
return hash.digest("base64");
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getEncryptionKey(user: IUser, client: IClient) {
|
export function getEncryptionKey(user: IUser, client: IClient) {
|
||||||
return sha512(sha512(user.encryption_key) + sha512(client._id.toHexString()) + sha512(client.client_id))
|
return sha512(
|
||||||
|
sha512(user.encryption_key) +
|
||||||
|
sha512(client._id.toHexString()) +
|
||||||
|
sha512(client.client_id)
|
||||||
|
);
|
||||||
}
|
}
|
65
src/index.ts
65
src/index.ts
@ -13,41 +13,48 @@ import config from "./config";
|
|||||||
// }
|
// }
|
||||||
|
|
||||||
if (!config.web) {
|
if (!config.web) {
|
||||||
Logging.error("No web config set. Terminating.")
|
Logging.error("No web config set. Terminating.");
|
||||||
process.exit();
|
process.exit();
|
||||||
}
|
}
|
||||||
|
|
||||||
import * as i18n from "i18n"
|
import * as i18n from "i18n";
|
||||||
i18n.configure({
|
i18n.configure({
|
||||||
locales: ["en", "de"],
|
locales: ["en", "de"],
|
||||||
directory: "./locales",
|
directory: "./locales",
|
||||||
})
|
});
|
||||||
|
|
||||||
import Web from "./web";
|
import Web from "./web";
|
||||||
import TestData from "./testdata";
|
import TestData from "./testdata";
|
||||||
import DB from "./database";
|
import DB from "./database";
|
||||||
|
|
||||||
Logging.log("Connecting to Database")
|
Logging.log("Connecting to Database");
|
||||||
if (config.core.dev) {
|
if (config.core.dev) {
|
||||||
Logging.warning("Running in dev mode! Database will be cleared!")
|
Logging.warning("Running in dev mode! Database will be cleared!");
|
||||||
}
|
}
|
||||||
DB.connect().then(async () => {
|
DB.connect()
|
||||||
Logging.log("Database connected")
|
.then(async () => {
|
||||||
if (config.core.dev)
|
Logging.log("Database connected");
|
||||||
await TestData()
|
if (config.core.dev) await TestData();
|
||||||
let web = new Web(config.web)
|
let web = new Web(config.web);
|
||||||
web.listen()
|
web.listen();
|
||||||
|
|
||||||
let already = new Set();
|
let already = new Set();
|
||||||
function print(path, layer) {
|
function print(path, layer) {
|
||||||
if (layer.route) {
|
if (layer.route) {
|
||||||
layer.route.stack.forEach(print.bind(null, path.concat(split(layer.route.path))))
|
layer.route.stack.forEach(
|
||||||
} else if (layer.name === 'router' && layer.handle.stack) {
|
print.bind(null, path.concat(split(layer.route.path)))
|
||||||
layer.handle.stack.forEach(print.bind(null, path.concat(split(layer.regexp))))
|
);
|
||||||
|
} else if (layer.name === "router" && layer.handle.stack) {
|
||||||
|
layer.handle.stack.forEach(
|
||||||
|
print.bind(null, path.concat(split(layer.regexp)))
|
||||||
|
);
|
||||||
} else if (layer.method) {
|
} else if (layer.method) {
|
||||||
let me: string = layer.method.toUpperCase();
|
let me: string = layer.method.toUpperCase();
|
||||||
me += " ".repeat(6 - me.length);
|
me += " ".repeat(6 - me.length);
|
||||||
let msg = `${me} /${path.concat(split(layer.regexp)).filter(Boolean).join('/')}`;
|
let msg = `${me} /${path
|
||||||
|
.concat(split(layer.regexp))
|
||||||
|
.filter(Boolean)
|
||||||
|
.join("/")}`;
|
||||||
if (!already.has(msg)) {
|
if (!already.has(msg)) {
|
||||||
already.add(msg);
|
already.add(msg);
|
||||||
Logging.log(msg);
|
Logging.log(msg);
|
||||||
@ -56,24 +63,28 @@ DB.connect().then(async () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function split(thing) {
|
function split(thing) {
|
||||||
if (typeof thing === 'string') {
|
if (typeof thing === "string") {
|
||||||
return thing.split('/')
|
return thing.split("/");
|
||||||
} else if (thing.fast_slash) {
|
} else if (thing.fast_slash) {
|
||||||
return ''
|
return "";
|
||||||
} else {
|
} else {
|
||||||
var match = thing.toString()
|
var match = thing
|
||||||
.replace('\\/?', '')
|
.toString()
|
||||||
.replace('(?=\\/|$)', '$')
|
.replace("\\/?", "")
|
||||||
.match(/^\/\^((?:\\[.*+?^${}()|[\]\\\/]|[^.*+?^${}()|[\]\\\/])*)\$\//)
|
.replace("(?=\\/|$)", "$")
|
||||||
|
.match(
|
||||||
|
/^\/\^((?:\\[.*+?^${}()|[\]\\\/]|[^.*+?^${}()|[\]\\\/])*)\$\//
|
||||||
|
);
|
||||||
return match
|
return match
|
||||||
? match[1].replace(/\\(.)/g, '$1').split('/')
|
? match[1].replace(/\\(.)/g, "$1").split("/")
|
||||||
: '<complex:' + thing.toString() + '>'
|
: "<complex:" + thing.toString() + ">";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Logging.log("--- Endpoints: ---");
|
// Logging.log("--- Endpoints: ---");
|
||||||
// web.server._router.stack.forEach(print.bind(null, []))
|
// web.server._router.stack.forEach(print.bind(null, []))
|
||||||
// Logging.log("--- Endpoints end ---")
|
// Logging.log("--- Endpoints end ---")
|
||||||
}).catch(e => {
|
})
|
||||||
Logging.error(e)
|
.catch((e) => {
|
||||||
|
Logging.error(e);
|
||||||
process.exit();
|
process.exit();
|
||||||
})
|
});
|
||||||
|
42
src/keys.ts
42
src/keys.ts
@ -1,10 +1,10 @@
|
|||||||
import Logging from "@hibas123/nodelogging";
|
import Logging from "@hibas123/nodelogging";
|
||||||
import * as fs from "fs"
|
import * as fs from "fs";
|
||||||
|
|
||||||
let private_key: string;
|
let private_key: string;
|
||||||
let rsa: RSA;
|
let rsa: RSA;
|
||||||
export function sign(message: Buffer): Buffer {
|
export function sign(message: Buffer): Buffer {
|
||||||
return rsa.sign(message, "buffer")
|
return rsa.sign(message, "buffer");
|
||||||
}
|
}
|
||||||
|
|
||||||
export function verify(message: Buffer, signature: Buffer): boolean {
|
export function verify(message: Buffer, signature: Buffer): boolean {
|
||||||
@ -19,28 +19,28 @@ import config from "./config";
|
|||||||
export function createJWT(payload: any, options: jwt.SignOptions) {
|
export function createJWT(payload: any, options: jwt.SignOptions) {
|
||||||
return new Promise<string>((resolve, reject) => {
|
return new Promise<string>((resolve, reject) => {
|
||||||
return jwt.sign(payload, private_key, options, (err, token) => {
|
return jwt.sign(payload, private_key, options, (err, token) => {
|
||||||
if (err) reject(err)
|
if (err) reject(err);
|
||||||
else resolve(token)
|
else resolve(token);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function validateJWT(data: string) {
|
export async function validateJWT(data: string) {
|
||||||
return new Promise<any>((resolve, reject) => {
|
return new Promise<any>((resolve, reject) => {
|
||||||
jwt.verify(data, public_key, (err, valid) => {
|
jwt.verify(data, public_key, (err, valid) => {
|
||||||
if (err) reject(err)
|
if (err) reject(err);
|
||||||
else resolve(valid)
|
else resolve(valid);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let create = false;
|
let create = false;
|
||||||
if (fs.existsSync("./keys")) {
|
if (fs.existsSync("./keys")) {
|
||||||
if (fs.existsSync("./keys/private.pem")) {
|
if (fs.existsSync("./keys/private.pem")) {
|
||||||
if (fs.existsSync("./keys/public.pem")) {
|
if (fs.existsSync("./keys/public.pem")) {
|
||||||
Logging.log("Using existing private and public key")
|
Logging.log("Using existing private and public key");
|
||||||
private_key = fs.readFileSync("./keys/private.pem").toString("utf8")
|
private_key = fs.readFileSync("./keys/private.pem").toString("utf8");
|
||||||
public_key = fs.readFileSync("./keys/public.pem").toString("utf8")
|
public_key = fs.readFileSync("./keys/public.pem").toString("utf8");
|
||||||
|
|
||||||
if (!private_key || !public_key) {
|
if (!private_key || !public_key) {
|
||||||
create = true;
|
create = true;
|
||||||
@ -49,21 +49,21 @@ if (fs.existsSync("./keys")) {
|
|||||||
} else create = true;
|
} else create = true;
|
||||||
} else create = true;
|
} else create = true;
|
||||||
|
|
||||||
import * as RSA from "node-rsa"
|
import * as RSA from "node-rsa";
|
||||||
|
|
||||||
if (create === true) {
|
if (create === true) {
|
||||||
Logging.log("Started RSA Key gen")
|
Logging.log("Started RSA Key gen");
|
||||||
let rsa = new RSA({ b: 4096 });
|
let rsa = new RSA({ b: 4096 });
|
||||||
private_key = rsa.exportKey("private")
|
private_key = rsa.exportKey("private");
|
||||||
public_key = rsa.exportKey("public")
|
public_key = rsa.exportKey("public");
|
||||||
|
|
||||||
if (!fs.existsSync("./keys")) {
|
if (!fs.existsSync("./keys")) {
|
||||||
fs.mkdirSync("./keys")
|
fs.mkdirSync("./keys");
|
||||||
}
|
}
|
||||||
fs.writeFileSync("./keys/private.pem", private_key)
|
fs.writeFileSync("./keys/private.pem", private_key);
|
||||||
fs.writeFileSync("./keys/public.pem", public_key)
|
fs.writeFileSync("./keys/public.pem", public_key);
|
||||||
Logging.log("Key pair generated")
|
Logging.log("Key pair generated");
|
||||||
}
|
}
|
||||||
|
|
||||||
rsa = new RSA(private_key, "private")
|
rsa = new RSA(private_key, "private");
|
||||||
rsa.importKey(public_key, "public")
|
rsa.importKey(public_key, "public");
|
||||||
|
@ -4,21 +4,21 @@ import { ObjectID } from "mongodb";
|
|||||||
import { v4 } from "uuid";
|
import { v4 } from "uuid";
|
||||||
|
|
||||||
export interface IClient extends ModelDataBase {
|
export interface IClient extends ModelDataBase {
|
||||||
maintainer: ObjectID
|
maintainer: ObjectID;
|
||||||
internal: boolean
|
internal: boolean;
|
||||||
name: string
|
name: string;
|
||||||
redirect_url: string
|
redirect_url: string;
|
||||||
website: string
|
website: string;
|
||||||
logo?: string
|
logo?: string;
|
||||||
client_id: string
|
client_id: string;
|
||||||
client_secret: string
|
client_secret: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const Client = DB.addModel<IClient>({
|
const Client = DB.addModel<IClient>({
|
||||||
name: "client",
|
name: "client",
|
||||||
versions: [
|
versions: [
|
||||||
{
|
{
|
||||||
migration: () => { },
|
migration: () => {},
|
||||||
schema: {
|
schema: {
|
||||||
maintainer: { type: ObjectID },
|
maintainer: { type: ObjectID },
|
||||||
internal: { type: Boolean, default: false },
|
internal: { type: Boolean, default: false },
|
||||||
@ -27,10 +27,10 @@ const Client = DB.addModel<IClient>({
|
|||||||
website: { type: String },
|
website: { type: String },
|
||||||
logo: { type: String, optional: true },
|
logo: { type: String, optional: true },
|
||||||
client_id: { type: String, default: () => v4() },
|
client_id: { type: String, default: () => v4() },
|
||||||
client_secret: { type: String }
|
client_secret: { type: String },
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
]
|
],
|
||||||
})
|
});
|
||||||
|
|
||||||
export default Client;
|
export default Client;
|
@ -4,24 +4,26 @@ import { ObjectID } from "mongodb";
|
|||||||
import { v4 } from "uuid";
|
import { v4 } from "uuid";
|
||||||
|
|
||||||
export interface IClientCode extends ModelDataBase {
|
export interface IClientCode extends ModelDataBase {
|
||||||
user: ObjectID
|
user: ObjectID;
|
||||||
code: string;
|
code: string;
|
||||||
client: ObjectID
|
client: ObjectID;
|
||||||
permissions: ObjectID[]
|
permissions: ObjectID[];
|
||||||
validTill: Date;
|
validTill: Date;
|
||||||
}
|
}
|
||||||
|
|
||||||
const ClientCode = DB.addModel<IClientCode>({
|
const ClientCode = DB.addModel<IClientCode>({
|
||||||
name: "client_code",
|
name: "client_code",
|
||||||
versions: [{
|
versions: [
|
||||||
migration: () => { },
|
{
|
||||||
|
migration: () => {},
|
||||||
schema: {
|
schema: {
|
||||||
user: { type: ObjectID },
|
user: { type: ObjectID },
|
||||||
code: { type: String },
|
code: { type: String },
|
||||||
client: { type: ObjectID },
|
client: { type: ObjectID },
|
||||||
permissions: { type: Array },
|
permissions: { type: Array },
|
||||||
validTill: { type: Date }
|
validTill: { type: Date },
|
||||||
}
|
},
|
||||||
}]
|
},
|
||||||
|
],
|
||||||
});
|
});
|
||||||
export default ClientCode;
|
export default ClientCode;
|
@ -10,14 +10,16 @@ export interface IGrant extends ModelDataBase {
|
|||||||
|
|
||||||
const Grant = DB.addModel<IGrant>({
|
const Grant = DB.addModel<IGrant>({
|
||||||
name: "grant",
|
name: "grant",
|
||||||
versions: [{
|
versions: [
|
||||||
migration: () => { },
|
{
|
||||||
|
migration: () => {},
|
||||||
schema: {
|
schema: {
|
||||||
user: { type: ObjectID },
|
user: { type: ObjectID },
|
||||||
client: { type: ObjectID },
|
client: { type: ObjectID },
|
||||||
permissions: { type: ObjectID, array: true }
|
permissions: { type: ObjectID, array: true },
|
||||||
}
|
},
|
||||||
}]
|
},
|
||||||
})
|
],
|
||||||
|
});
|
||||||
|
|
||||||
export default Grant;
|
export default Grant;
|
||||||
|
@ -16,27 +16,34 @@ export interface ILoginToken extends ModelDataBase {
|
|||||||
}
|
}
|
||||||
const LoginToken = DB.addModel<ILoginToken>({
|
const LoginToken = DB.addModel<ILoginToken>({
|
||||||
name: "login_token",
|
name: "login_token",
|
||||||
versions: [{
|
versions: [
|
||||||
migration: () => { },
|
{
|
||||||
schema: {
|
migration: () => {},
|
||||||
token: { type: String },
|
|
||||||
special: { type: Boolean, default: () => false },
|
|
||||||
user: { type: ObjectID },
|
|
||||||
validTill: { type: Date },
|
|
||||||
valid: { type: Boolean }
|
|
||||||
}
|
|
||||||
}, {
|
|
||||||
migration: (doc: ILoginToken) => { doc.validated = true; },
|
|
||||||
schema: {
|
schema: {
|
||||||
token: { type: String },
|
token: { type: String },
|
||||||
special: { type: Boolean, default: () => false },
|
special: { type: Boolean, default: () => false },
|
||||||
user: { type: ObjectID },
|
user: { type: ObjectID },
|
||||||
validTill: { type: Date },
|
validTill: { type: Date },
|
||||||
valid: { type: Boolean },
|
valid: { type: Boolean },
|
||||||
validated: { type: Boolean, default: false }
|
},
|
||||||
}
|
},
|
||||||
}, {
|
{
|
||||||
migration: (doc: ILoginToken) => { doc.validated = true; },
|
migration: (doc: ILoginToken) => {
|
||||||
|
doc.validated = true;
|
||||||
|
},
|
||||||
|
schema: {
|
||||||
|
token: { type: String },
|
||||||
|
special: { type: Boolean, default: () => false },
|
||||||
|
user: { type: ObjectID },
|
||||||
|
validTill: { type: Date },
|
||||||
|
valid: { type: Boolean },
|
||||||
|
validated: { type: Boolean, default: false },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
migration: (doc: ILoginToken) => {
|
||||||
|
doc.validated = true;
|
||||||
|
},
|
||||||
schema: {
|
schema: {
|
||||||
token: { type: String },
|
token: { type: String },
|
||||||
special: { type: Boolean, default: () => false },
|
special: { type: Boolean, default: () => false },
|
||||||
@ -46,19 +53,21 @@ const LoginToken = DB.addModel<ILoginToken>({
|
|||||||
validated: { type: Boolean, default: false },
|
validated: { type: Boolean, default: false },
|
||||||
data: { type: "any", optional: true },
|
data: { type: "any", optional: true },
|
||||||
ip: { type: String, optional: true },
|
ip: { type: String, optional: true },
|
||||||
browser: { type: String, optional: true }
|
browser: { type: String, optional: true },
|
||||||
}
|
},
|
||||||
}]
|
},
|
||||||
})
|
],
|
||||||
|
});
|
||||||
|
|
||||||
export async function CheckToken(token: ILoginToken, validated: boolean = true): Promise<boolean> {
|
export async function CheckToken(
|
||||||
if (!token || !token.valid)
|
token: ILoginToken,
|
||||||
return false;
|
validated: boolean = true
|
||||||
if (validated && !token.validated)
|
): Promise<boolean> {
|
||||||
return false;
|
if (!token || !token.valid) return false;
|
||||||
|
if (validated && !token.validated) return false;
|
||||||
if (moment().isAfter(token.validTill)) {
|
if (moment().isAfter(token.validTill)) {
|
||||||
token.valid = false;
|
token.valid = false;
|
||||||
await LoginToken.save(token)
|
await LoginToken.save(token);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
|
@ -9,14 +9,16 @@ export interface IMail extends ModelDataBase {
|
|||||||
|
|
||||||
const Mail = DB.addModel<IMail>({
|
const Mail = DB.addModel<IMail>({
|
||||||
name: "mail",
|
name: "mail",
|
||||||
versions: [{
|
versions: [
|
||||||
migration: () => { },
|
{
|
||||||
|
migration: () => {},
|
||||||
schema: {
|
schema: {
|
||||||
mail: { type: String },
|
mail: { type: String },
|
||||||
verified: { type: Boolean, default: false },
|
verified: { type: Boolean, default: false },
|
||||||
primary: { type: Boolean }
|
primary: { type: Boolean },
|
||||||
}
|
},
|
||||||
}]
|
},
|
||||||
})
|
],
|
||||||
|
});
|
||||||
|
|
||||||
export default Mail;
|
export default Mail;
|
@ -11,22 +11,27 @@ export interface IPermission extends ModelDataBase {
|
|||||||
|
|
||||||
const Permission = DB.addModel<IPermission>({
|
const Permission = DB.addModel<IPermission>({
|
||||||
name: "permission",
|
name: "permission",
|
||||||
versions: [{
|
versions: [
|
||||||
migration: () => { },
|
{
|
||||||
schema: {
|
migration: () => {},
|
||||||
name: { type: String },
|
|
||||||
description: { type: String },
|
|
||||||
client: { type: ObjectID }
|
|
||||||
}
|
|
||||||
}, {
|
|
||||||
migration: (old) => { old.grant_type = "user" },
|
|
||||||
schema: {
|
schema: {
|
||||||
name: { type: String },
|
name: { type: String },
|
||||||
description: { type: String },
|
description: { type: String },
|
||||||
client: { type: ObjectID },
|
client: { type: ObjectID },
|
||||||
grant_type: { type: String, default: "user" }
|
},
|
||||||
}
|
},
|
||||||
}]
|
{
|
||||||
})
|
migration: (old) => {
|
||||||
|
old.grant_type = "user";
|
||||||
|
},
|
||||||
|
schema: {
|
||||||
|
name: { type: String },
|
||||||
|
description: { type: String },
|
||||||
|
client: { type: ObjectID },
|
||||||
|
grant_type: { type: String, default: "user" },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
export default Permission;
|
export default Permission;
|
||||||
|
@ -14,17 +14,19 @@ export interface IRefreshToken extends ModelDataBase {
|
|||||||
|
|
||||||
const RefreshToken = DB.addModel<IRefreshToken>({
|
const RefreshToken = DB.addModel<IRefreshToken>({
|
||||||
name: "refresh_token",
|
name: "refresh_token",
|
||||||
versions: [{
|
versions: [
|
||||||
migration: () => { },
|
{
|
||||||
|
migration: () => {},
|
||||||
schema: {
|
schema: {
|
||||||
token: { type: String },
|
token: { type: String },
|
||||||
user: { type: ObjectID },
|
user: { type: ObjectID },
|
||||||
client: { type: ObjectID },
|
client: { type: ObjectID },
|
||||||
permissions: { type: Array },
|
permissions: { type: Array },
|
||||||
validTill: { type: Date },
|
validTill: { type: Date },
|
||||||
valid: { type: Boolean }
|
valid: { type: Boolean },
|
||||||
}
|
},
|
||||||
}]
|
},
|
||||||
})
|
],
|
||||||
|
});
|
||||||
|
|
||||||
export default RefreshToken;
|
export default RefreshToken;
|
@ -11,15 +11,17 @@ export interface IRegCode extends ModelDataBase {
|
|||||||
|
|
||||||
const RegCode = DB.addModel<IRegCode>({
|
const RegCode = DB.addModel<IRegCode>({
|
||||||
name: "reg_code",
|
name: "reg_code",
|
||||||
versions: [{
|
versions: [
|
||||||
migration: () => { },
|
{
|
||||||
|
migration: () => {},
|
||||||
schema: {
|
schema: {
|
||||||
token: { type: String },
|
token: { type: String },
|
||||||
valid: { type: Boolean },
|
valid: { type: Boolean },
|
||||||
validTill: { type: Date }
|
validTill: { type: Date },
|
||||||
}
|
},
|
||||||
}]
|
},
|
||||||
})
|
],
|
||||||
|
});
|
||||||
|
|
||||||
export default RegCode;
|
export default RegCode;
|
||||||
|
|
||||||
|
@ -6,7 +6,7 @@ export enum TFATypes {
|
|||||||
OTC,
|
OTC,
|
||||||
BACKUP_CODE,
|
BACKUP_CODE,
|
||||||
U2F,
|
U2F,
|
||||||
APP_ALLOW
|
APP_ALLOW,
|
||||||
}
|
}
|
||||||
|
|
||||||
export const TFANames = new Map<TFATypes, string>();
|
export const TFANames = new Map<TFATypes, string>();
|
||||||
@ -16,11 +16,11 @@ TFANames.set(TFATypes.U2F, "Security Key (U2F)");
|
|||||||
TFANames.set(TFATypes.APP_ALLOW, "App Push");
|
TFANames.set(TFATypes.APP_ALLOW, "App Push");
|
||||||
|
|
||||||
export interface ITwoFactor extends ModelDataBase {
|
export interface ITwoFactor extends ModelDataBase {
|
||||||
user: ObjectID
|
user: ObjectID;
|
||||||
valid: boolean
|
valid: boolean;
|
||||||
expires?: Date;
|
expires?: Date;
|
||||||
name?: string;
|
name?: string;
|
||||||
type: TFATypes
|
type: TFATypes;
|
||||||
data: any;
|
data: any;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -33,7 +33,7 @@ export interface IYubiKey extends ITwoFactor {
|
|||||||
registration?: any;
|
registration?: any;
|
||||||
publicKey: string;
|
publicKey: string;
|
||||||
keyHandle: string;
|
keyHandle: string;
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IU2F extends ITwoFactor {
|
export interface IU2F extends ITwoFactor {
|
||||||
@ -42,7 +42,7 @@ export interface IU2F extends ITwoFactor {
|
|||||||
publicKey: string;
|
publicKey: string;
|
||||||
keyHandle: string;
|
keyHandle: string;
|
||||||
registration?: string;
|
registration?: string;
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IBackupCode extends ITwoFactor {
|
export interface IBackupCode extends ITwoFactor {
|
||||||
@ -51,8 +51,9 @@ export interface IBackupCode extends ITwoFactor {
|
|||||||
|
|
||||||
const TwoFactor = DB.addModel<ITwoFactor>({
|
const TwoFactor = DB.addModel<ITwoFactor>({
|
||||||
name: "twofactor",
|
name: "twofactor",
|
||||||
versions: [{
|
versions: [
|
||||||
migration: (e) => { },
|
{
|
||||||
|
migration: (e) => {},
|
||||||
schema: {
|
schema: {
|
||||||
user: { type: ObjectID },
|
user: { type: ObjectID },
|
||||||
valid: { type: Boolean },
|
valid: { type: Boolean },
|
||||||
@ -60,8 +61,9 @@ const TwoFactor = DB.addModel<ITwoFactor>({
|
|||||||
name: { type: String, optional: true },
|
name: { type: String, optional: true },
|
||||||
type: { type: Number },
|
type: { type: Number },
|
||||||
data: { type: "any" },
|
data: { type: "any" },
|
||||||
}
|
},
|
||||||
}]
|
},
|
||||||
|
],
|
||||||
});
|
});
|
||||||
|
|
||||||
export default TwoFactor;
|
export default TwoFactor;
|
@ -8,7 +8,7 @@ export enum Gender {
|
|||||||
none,
|
none,
|
||||||
male,
|
male,
|
||||||
female,
|
female,
|
||||||
other
|
other,
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IUser extends ModelDataBase {
|
export interface IUser extends ModelDataBase {
|
||||||
@ -16,20 +16,21 @@ export interface IUser extends ModelDataBase {
|
|||||||
username: string;
|
username: string;
|
||||||
|
|
||||||
name: string;
|
name: string;
|
||||||
birthday?: Date
|
birthday?: Date;
|
||||||
gender: Gender;
|
gender: Gender;
|
||||||
admin: boolean;
|
admin: boolean;
|
||||||
password: string;
|
password: string;
|
||||||
salt: string;
|
salt: string;
|
||||||
mails: ObjectID[];
|
mails: ObjectID[];
|
||||||
phones: { phone: string, verified: boolean, primary: boolean }[];
|
phones: { phone: string; verified: boolean; primary: boolean }[];
|
||||||
encryption_key: string;
|
encryption_key: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const User = DB.addModel<IUser>({
|
const User = DB.addModel<IUser>({
|
||||||
name: "user",
|
name: "user",
|
||||||
versions: [{
|
versions: [
|
||||||
migration: () => { },
|
{
|
||||||
|
migration: () => {},
|
||||||
schema: {
|
schema: {
|
||||||
uid: { type: String, default: () => v4() },
|
uid: { type: String, default: () => v4() },
|
||||||
username: { type: String },
|
username: { type: String },
|
||||||
@ -46,8 +47,8 @@ const User = DB.addModel<IUser>({
|
|||||||
type: {
|
type: {
|
||||||
phone: { type: String },
|
phone: { type: String },
|
||||||
verified: { type: Boolean },
|
verified: { type: Boolean },
|
||||||
primary: { type: Boolean }
|
primary: { type: Boolean },
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
twofactor: {
|
twofactor: {
|
||||||
array: true,
|
array: true,
|
||||||
@ -55,48 +56,15 @@ const User = DB.addModel<IUser>({
|
|||||||
type: {
|
type: {
|
||||||
token: { type: String },
|
token: { type: String },
|
||||||
valid: { type: Boolean },
|
valid: { type: Boolean },
|
||||||
type: { type: Number }
|
type: { type: Number },
|
||||||
}
|
},
|
||||||
}
|
|
||||||
}
|
|
||||||
}, {
|
|
||||||
migration: (e: IUser) => { e.encryption_key = randomString(64) },
|
|
||||||
schema: {
|
|
||||||
uid: { type: String, default: () => v4() },
|
|
||||||
username: { type: String },
|
|
||||||
name: { type: String },
|
|
||||||
birthday: { type: Date, optional: true },
|
|
||||||
gender: { type: Number },
|
|
||||||
admin: { type: Boolean },
|
|
||||||
password: { type: String },
|
|
||||||
salt: { type: String },
|
|
||||||
mails: { type: Array, default: () => [] },
|
|
||||||
phones: {
|
|
||||||
array: true,
|
|
||||||
model: true,
|
|
||||||
type: {
|
|
||||||
phone: { type: String },
|
|
||||||
verified: { type: Boolean },
|
|
||||||
primary: { type: Boolean }
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
twofactor: {
|
|
||||||
array: true,
|
|
||||||
model: true,
|
|
||||||
type: {
|
|
||||||
token: { type: String },
|
|
||||||
valid: { type: Boolean },
|
|
||||||
type: { type: Number }
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
encryption_key: {
|
|
||||||
type: String,
|
|
||||||
default: () => randomString(64)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
migration: (e: any) => { delete e.twofactor },
|
migration: (e: IUser) => {
|
||||||
|
e.encryption_key = randomString(64);
|
||||||
|
},
|
||||||
schema: {
|
schema: {
|
||||||
uid: { type: String, default: () => v4() },
|
uid: { type: String, default: () => v4() },
|
||||||
username: { type: String },
|
username: { type: String },
|
||||||
@ -113,15 +81,54 @@ const User = DB.addModel<IUser>({
|
|||||||
type: {
|
type: {
|
||||||
phone: { type: String },
|
phone: { type: String },
|
||||||
verified: { type: Boolean },
|
verified: { type: Boolean },
|
||||||
primary: { type: Boolean }
|
primary: { type: Boolean },
|
||||||
}
|
},
|
||||||
|
},
|
||||||
|
twofactor: {
|
||||||
|
array: true,
|
||||||
|
model: true,
|
||||||
|
type: {
|
||||||
|
token: { type: String },
|
||||||
|
valid: { type: Boolean },
|
||||||
|
type: { type: Number },
|
||||||
|
},
|
||||||
},
|
},
|
||||||
encryption_key: {
|
encryption_key: {
|
||||||
type: String,
|
type: String,
|
||||||
default: () => randomString(64)
|
default: () => randomString(64),
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
}]
|
},
|
||||||
})
|
{
|
||||||
|
migration: (e: any) => {
|
||||||
|
delete e.twofactor;
|
||||||
|
},
|
||||||
|
schema: {
|
||||||
|
uid: { type: String, default: () => v4() },
|
||||||
|
username: { type: String },
|
||||||
|
name: { type: String },
|
||||||
|
birthday: { type: Date, optional: true },
|
||||||
|
gender: { type: Number },
|
||||||
|
admin: { type: Boolean },
|
||||||
|
password: { type: String },
|
||||||
|
salt: { type: String },
|
||||||
|
mails: { type: Array, default: () => [] },
|
||||||
|
phones: {
|
||||||
|
array: true,
|
||||||
|
model: true,
|
||||||
|
type: {
|
||||||
|
phone: { type: String },
|
||||||
|
verified: { type: Boolean },
|
||||||
|
primary: { type: Boolean },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
encryption_key: {
|
||||||
|
type: String,
|
||||||
|
default: () => randomString(64),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
export default User;
|
export default User;
|
@ -8,7 +8,6 @@ import { ObjectID } from "bson";
|
|||||||
import DB from "./database";
|
import DB from "./database";
|
||||||
import TwoFactor from "./models/twofactor";
|
import TwoFactor from "./models/twofactor";
|
||||||
|
|
||||||
|
|
||||||
import * as speakeasy from "speakeasy";
|
import * as speakeasy from "speakeasy";
|
||||||
import LoginToken from "./models/login_token";
|
import LoginToken from "./models/login_token";
|
||||||
import Mail from "./models/mail";
|
import Mail from "./models/mail";
|
||||||
@ -21,13 +20,12 @@ export default async function TestData() {
|
|||||||
mail = Mail.new({
|
mail = Mail.new({
|
||||||
mail: "test@test.de",
|
mail: "test@test.de",
|
||||||
primary: true,
|
primary: true,
|
||||||
verified: true
|
verified: true,
|
||||||
})
|
});
|
||||||
|
|
||||||
await Mail.save(mail);
|
await Mail.save(mail);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
let u = await User.findOne({ username: "test" });
|
let u = await User.findOne({ username: "test" });
|
||||||
if (!u) {
|
if (!u) {
|
||||||
Logging.log("Adding test user");
|
Logging.log("Adding test user");
|
||||||
@ -36,21 +34,22 @@ export default async function TestData() {
|
|||||||
birthday: new Date(),
|
birthday: new Date(),
|
||||||
gender: Gender.male,
|
gender: Gender.male,
|
||||||
name: "Test Test",
|
name: "Test Test",
|
||||||
password: "125d6d03b32c84d492747f79cf0bf6e179d287f341384eb5d6d3197525ad6be8e6df0116032935698f99a09e265073d1d6c32c274591bf1d0a20ad67cba921bc",
|
password:
|
||||||
|
"125d6d03b32c84d492747f79cf0bf6e179d287f341384eb5d6d3197525ad6be8e6df0116032935698f99a09e265073d1d6c32c274591bf1d0a20ad67cba921bc",
|
||||||
salt: "test",
|
salt: "test",
|
||||||
admin: true,
|
admin: true,
|
||||||
phones: [
|
phones: [
|
||||||
{ phone: "+4915962855955", primary: true, verified: true },
|
{ phone: "+4915962855955", primary: true, verified: true },
|
||||||
{ phone: "+4915962855932", primary: false, verified: false }
|
{ phone: "+4915962855932", primary: false, verified: false },
|
||||||
],
|
],
|
||||||
mails: [mail._id]
|
mails: [mail._id],
|
||||||
})
|
});
|
||||||
await User.save(u);
|
await User.save(u);
|
||||||
}
|
}
|
||||||
|
|
||||||
let c = await Client.findOne({ client_id: "test001" });
|
let c = await Client.findOne({ client_id: "test001" });
|
||||||
if (!c) {
|
if (!c) {
|
||||||
Logging.log("Adding test client")
|
Logging.log("Adding test client");
|
||||||
c = Client.new({
|
c = Client.new({
|
||||||
client_id: "test001",
|
client_id: "test001",
|
||||||
client_secret: "test001",
|
client_secret: "test001",
|
||||||
@ -58,19 +57,19 @@ export default async function TestData() {
|
|||||||
maintainer: u._id,
|
maintainer: u._id,
|
||||||
name: "Test Client",
|
name: "Test Client",
|
||||||
website: "http://example.com",
|
website: "http://example.com",
|
||||||
redirect_url: "http://example.com"
|
redirect_url: "http://example.com",
|
||||||
})
|
});
|
||||||
await Client.save(c);
|
await Client.save(c);
|
||||||
}
|
}
|
||||||
|
|
||||||
let perm = await Permission.findOne({ id: 0 });
|
let perm = await Permission.findOne({ id: 0 });
|
||||||
if (!perm) {
|
if (!perm) {
|
||||||
Logging.log("Adding test permission")
|
Logging.log("Adding test permission");
|
||||||
perm = Permission.new({
|
perm = Permission.new({
|
||||||
_id: new ObjectID("507f1f77bcf86cd799439011"),
|
_id: new ObjectID("507f1f77bcf86cd799439011"),
|
||||||
name: "TestPerm",
|
name: "TestPerm",
|
||||||
description: "Permission just for testing purposes",
|
description: "Permission just for testing purposes",
|
||||||
client: c._id
|
client: c._id,
|
||||||
});
|
});
|
||||||
|
|
||||||
await (await (Permission as any)._collection).insertOne(perm);
|
await (await (Permission as any)._collection).insertOne(perm);
|
||||||
@ -80,30 +79,29 @@ export default async function TestData() {
|
|||||||
|
|
||||||
let r = await RegCode.findOne({ token: "test" });
|
let r = await RegCode.findOne({ token: "test" });
|
||||||
if (!r) {
|
if (!r) {
|
||||||
Logging.log("Adding test reg_code")
|
Logging.log("Adding test reg_code");
|
||||||
r = RegCode.new({
|
r = RegCode.new({
|
||||||
token: "test",
|
token: "test",
|
||||||
valid: true,
|
valid: true,
|
||||||
validTill: moment().add("1", "year").toDate()
|
validTill: moment().add("1", "year").toDate(),
|
||||||
})
|
});
|
||||||
await RegCode.save(r);
|
await RegCode.save(r);
|
||||||
}
|
}
|
||||||
|
|
||||||
let t = await TwoFactor.findOne({ user: u._id, type: 0 })
|
let t = await TwoFactor.findOne({ user: u._id, type: 0 });
|
||||||
if (!t) {
|
if (!t) {
|
||||||
t = TwoFactor.new({
|
t = TwoFactor.new({
|
||||||
user: u._id,
|
user: u._id,
|
||||||
type: 0,
|
type: 0,
|
||||||
valid: true,
|
valid: true,
|
||||||
data: "IIRW2P2UJRDDO2LDIRYW4LSREZLWMOKDNBJES2LLHRREK3R6KZJQ",
|
data: "IIRW2P2UJRDDO2LDIRYW4LSREZLWMOKDNBJES2LLHRREK3R6KZJQ",
|
||||||
expires: null
|
expires: null,
|
||||||
})
|
});
|
||||||
TwoFactor.save(t);
|
TwoFactor.save(t);
|
||||||
}
|
}
|
||||||
|
|
||||||
let login_token = await LoginToken.findOne({ token: "test01" });
|
let login_token = await LoginToken.findOne({ token: "test01" });
|
||||||
if (login_token)
|
if (login_token) await LoginToken.delete(login_token);
|
||||||
await LoginToken.delete(login_token);
|
|
||||||
|
|
||||||
login_token = LoginToken.new({
|
login_token = LoginToken.new({
|
||||||
browser: "DEMO",
|
browser: "DEMO",
|
||||||
@ -113,13 +111,12 @@ export default async function TestData() {
|
|||||||
valid: true,
|
valid: true,
|
||||||
validTill: moment().add("10", "years").toDate(),
|
validTill: moment().add("10", "years").toDate(),
|
||||||
user: u._id,
|
user: u._id,
|
||||||
validated: true
|
validated: true,
|
||||||
});
|
});
|
||||||
await LoginToken.save(login_token);
|
await LoginToken.save(login_token);
|
||||||
|
|
||||||
let special_token = await LoginToken.findOne({ token: "test02" });
|
let special_token = await LoginToken.findOne({ token: "test02" });
|
||||||
if (special_token)
|
if (special_token) await LoginToken.delete(special_token);
|
||||||
await LoginToken.delete(special_token);
|
|
||||||
|
|
||||||
special_token = LoginToken.new({
|
special_token = LoginToken.new({
|
||||||
browser: "DEMO",
|
browser: "DEMO",
|
||||||
@ -129,11 +126,10 @@ export default async function TestData() {
|
|||||||
valid: true,
|
valid: true,
|
||||||
validTill: moment().add("10", "years").toDate(),
|
validTill: moment().add("10", "years").toDate(),
|
||||||
user: u._id,
|
user: u._id,
|
||||||
validated: true
|
validated: true,
|
||||||
});
|
});
|
||||||
await LoginToken.save(special_token);
|
await LoginToken.save(special_token);
|
||||||
|
|
||||||
|
|
||||||
// setInterval(() => {
|
// setInterval(() => {
|
||||||
// let code = speakeasy.totp({
|
// let code = speakeasy.totp({
|
||||||
// secret: t.data,
|
// secret: t.data,
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import * as handlebars from "handlebars"
|
import * as handlebars from "handlebars";
|
||||||
import { readFileSync } from "fs";
|
import { readFileSync } from "fs";
|
||||||
import { __ as i__ } from "i18n"
|
import { __ as i__ } from "i18n";
|
||||||
import config from "../config";
|
import config from "../config";
|
||||||
|
|
||||||
let template: handlebars.TemplateDelegate<any>;
|
let template: handlebars.TemplateDelegate<any>;
|
||||||
@ -11,11 +11,11 @@ function loadStatic() {
|
|||||||
|
|
||||||
export default function GetAdminPage(__: typeof i__): string {
|
export default function GetAdminPage(__: typeof i__): string {
|
||||||
if (config.core.dev) {
|
if (config.core.dev) {
|
||||||
loadStatic()
|
loadStatic();
|
||||||
}
|
}
|
||||||
|
|
||||||
let data = {}
|
let data = {};
|
||||||
return template(data, { helpers: { "i18n": __ } })
|
return template(data, { helpers: { i18n: __ } });
|
||||||
}
|
}
|
||||||
|
|
||||||
loadStatic()
|
loadStatic();
|
||||||
|
@ -1,26 +1,34 @@
|
|||||||
import { compile, TemplateDelegate } from "handlebars"
|
import { compile, TemplateDelegate } from "handlebars";
|
||||||
import { readFileSync } from "fs";
|
import { readFileSync } from "fs";
|
||||||
import { __ as i__ } from "i18n"
|
import { __ as i__ } from "i18n";
|
||||||
import config from "../config";
|
import config from "../config";
|
||||||
|
|
||||||
let template: TemplateDelegate<any>;
|
let template: TemplateDelegate<any>;
|
||||||
function loadStatic() {
|
function loadStatic() {
|
||||||
let html = readFileSync("./views/out/authorize/authorize.html").toString();
|
let html = readFileSync("./views/out/authorize/authorize.html").toString();
|
||||||
template = compile(html)
|
template = compile(html);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function GetAuthPage(__: typeof i__, appname: string, scopes: { name: string, description: string, logo: string }[]): string {
|
export default function GetAuthPage(
|
||||||
|
__: typeof i__,
|
||||||
|
appname: string,
|
||||||
|
scopes: { name: string; description: string; logo: string }[]
|
||||||
|
): string {
|
||||||
if (config.core.dev) {
|
if (config.core.dev) {
|
||||||
loadStatic()
|
loadStatic();
|
||||||
}
|
}
|
||||||
|
|
||||||
return template({
|
return template(
|
||||||
|
{
|
||||||
title: __("Authorize %s", appname),
|
title: __("Authorize %s", appname),
|
||||||
information: __("By clicking on ALLOW, you allow this app to access the requested recources."),
|
information: __(
|
||||||
|
"By clicking on ALLOW, you allow this app to access the requested recources."
|
||||||
|
),
|
||||||
scopes: scopes,
|
scopes: scopes,
|
||||||
// request: request
|
// request: request
|
||||||
}, { helpers: { "i18n": __ } });
|
},
|
||||||
|
{ helpers: { i18n: __ } }
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
loadStatic()
|
loadStatic();
|
||||||
|
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import * as handlebars from "handlebars"
|
import * as handlebars from "handlebars";
|
||||||
import { readFileSync } from "fs";
|
import { readFileSync } from "fs";
|
||||||
import { __ as i__ } from "i18n"
|
import { __ as i__ } from "i18n";
|
||||||
import config from "../config";
|
import config from "../config";
|
||||||
|
|
||||||
let template: handlebars.TemplateDelegate<any>;
|
let template: handlebars.TemplateDelegate<any>;
|
||||||
@ -29,11 +29,11 @@ function loadStatic() {
|
|||||||
|
|
||||||
export default function GetLoginPage(__: typeof i__): string {
|
export default function GetLoginPage(__: typeof i__): string {
|
||||||
if (config.core.dev) {
|
if (config.core.dev) {
|
||||||
loadStatic()
|
loadStatic();
|
||||||
}
|
}
|
||||||
|
|
||||||
let data = {}
|
let data = {};
|
||||||
return template(data, { helpers: { "i18n": __ } });
|
return template(data, { helpers: { i18n: __ } });
|
||||||
}
|
}
|
||||||
|
|
||||||
loadStatic()
|
loadStatic();
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import * as handlebars from "handlebars"
|
import * as handlebars from "handlebars";
|
||||||
import { readFileSync } from "fs";
|
import { readFileSync } from "fs";
|
||||||
import { __ as i__ } from "i18n"
|
import { __ as i__ } from "i18n";
|
||||||
import config from "../config";
|
import config from "../config";
|
||||||
|
|
||||||
let template: handlebars.TemplateDelegate<any>;
|
let template: handlebars.TemplateDelegate<any>;
|
||||||
@ -11,11 +11,11 @@ function loadStatic() {
|
|||||||
|
|
||||||
export default function GetRegistrationPage(__: typeof i__): string {
|
export default function GetRegistrationPage(__: typeof i__): string {
|
||||||
if (config.core.dev) {
|
if (config.core.dev) {
|
||||||
loadStatic()
|
loadStatic();
|
||||||
}
|
}
|
||||||
|
|
||||||
let data = {}
|
let data = {};
|
||||||
return template(data, { helpers: { "i18n": __ } })
|
return template(data, { helpers: { i18n: __ } });
|
||||||
}
|
}
|
||||||
|
|
||||||
loadStatic()
|
loadStatic();
|
||||||
|
@ -1,23 +1,17 @@
|
|||||||
{
|
{
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
/* Basic Options */
|
/* Basic Options */
|
||||||
"target": "es2017", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017','ES2018' or 'ESNEXT'. */
|
"target": "es2017" /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017','ES2018' or 'ESNEXT'. */,
|
||||||
"module": "commonjs", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */
|
"module": "commonjs" /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */,
|
||||||
"declaration": true, /* Generates corresponding '.d.ts' file. */
|
"declaration": true /* Generates corresponding '.d.ts' file. */,
|
||||||
"sourceMap": true, /* Generates corresponding '.map' file. */
|
"sourceMap": true /* Generates corresponding '.map' file. */,
|
||||||
"outDir": "./lib", /* Redirect output structure to the directory. */
|
"outDir": "./lib" /* Redirect output structure to the directory. */,
|
||||||
"strict": false, /* Enable all strict type-checking options. */
|
"strict": false /* Enable all strict type-checking options. */,
|
||||||
"preserveWatchOutput": true,
|
"preserveWatchOutput": true,
|
||||||
"experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */
|
"experimentalDecorators": true /* Enables experimental support for ES7 decorators. */,
|
||||||
"emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */
|
"emitDecoratorMetadata": true /* Enables experimental support for emitting type metadata for decorators. */
|
||||||
},
|
},
|
||||||
"exclude": [
|
"exclude": ["node_modules/"],
|
||||||
"node_modules/"
|
"files": ["src/express.d.ts"],
|
||||||
],
|
"include": ["./src"]
|
||||||
"files": [
|
|
||||||
"src/express.d.ts"
|
|
||||||
],
|
|
||||||
"include": [
|
|
||||||
"./src"
|
|
||||||
]
|
|
||||||
}
|
}
|
120
views/build.js
120
views/build.js
@ -5,49 +5,45 @@ const {
|
|||||||
copyFileSync,
|
copyFileSync,
|
||||||
writeFileSync,
|
writeFileSync,
|
||||||
readFileSync,
|
readFileSync,
|
||||||
exists
|
exists,
|
||||||
} = require('fs')
|
} = require("fs");
|
||||||
const {
|
const { join, basename, dirname } = require("path");
|
||||||
join,
|
|
||||||
basename,
|
|
||||||
dirname
|
|
||||||
} = require('path')
|
|
||||||
|
|
||||||
|
const isDirectory = (source) => lstatSync(source).isDirectory();
|
||||||
|
const getDirectories = (source) =>
|
||||||
const isDirectory = source => lstatSync(source).isDirectory()
|
readdirSync(source)
|
||||||
const getDirectories = source =>
|
.map((name) => join(source, name))
|
||||||
readdirSync(source).map(name => join(source, name)).filter(isDirectory)
|
.filter(isDirectory);
|
||||||
|
|
||||||
function ensureDir(folder) {
|
function ensureDir(folder) {
|
||||||
try {
|
try {
|
||||||
if (!isDirectory(folder)) mkdirSync(folder)
|
if (!isDirectory(folder)) mkdirSync(folder);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
mkdirSync(folder)
|
mkdirSync(folder);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const fileExists = (filename) =>
|
||||||
|
new Promise((yes, no) => exists(filename, (exi) => yes(exi)));
|
||||||
|
ensureDir("./out");
|
||||||
|
|
||||||
const fileExists = (filename) => new Promise((yes, no) => exists(filename, (exi) => yes(exi)));
|
const sass = require("sass");
|
||||||
ensureDir("./out")
|
|
||||||
|
|
||||||
const sass = require('sass');
|
|
||||||
|
|
||||||
function findHead(elm) {
|
function findHead(elm) {
|
||||||
if (elm.tagName === "head") return elm;
|
if (elm.tagName === "head") return elm;
|
||||||
for (let i = 0; i < elm.childNodes.length; i++) {
|
for (let i = 0; i < elm.childNodes.length; i++) {
|
||||||
let res = findHead(elm.childNodes[i])
|
let res = findHead(elm.childNodes[i]);
|
||||||
if (res) return res;
|
if (res) return res;
|
||||||
}
|
}
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
const rollup = require("rollup")
|
const rollup = require("rollup");
|
||||||
const includepaths = require("rollup-plugin-includepaths")
|
const includepaths = require("rollup-plugin-includepaths");
|
||||||
const typescript = require("rollup-plugin-typescript2");
|
const typescript = require("rollup-plugin-typescript2");
|
||||||
const resolve = require("rollup-plugin-node-resolve");
|
const resolve = require("rollup-plugin-node-resolve");
|
||||||
const minify = require("html-minifier").minify
|
const minify = require("html-minifier").minify;
|
||||||
const gzipSize = require('gzip-size');
|
const gzipSize = require("gzip-size");
|
||||||
|
|
||||||
async function file_name(folder, name, exts) {
|
async function file_name(folder, name, exts) {
|
||||||
for (let ext of exts) {
|
for (let ext of exts) {
|
||||||
@ -61,58 +57,57 @@ async function buildPage(folder) {
|
|||||||
const pagename = basename(folder);
|
const pagename = basename(folder);
|
||||||
const outpath = "./out/" + pagename;
|
const outpath = "./out/" + pagename;
|
||||||
|
|
||||||
ensureDir(outpath)
|
ensureDir(outpath);
|
||||||
|
|
||||||
const basefile = await file_name(folder, pagename, ["tsx", "ts", "js"]);
|
const basefile = await file_name(folder, pagename, ["tsx", "ts", "js"]);
|
||||||
|
|
||||||
|
|
||||||
let bundle = await rollup.rollup({
|
let bundle = await rollup.rollup({
|
||||||
input: basefile,
|
input: basefile,
|
||||||
plugins: [
|
plugins: [
|
||||||
includepaths({
|
includepaths({
|
||||||
paths: ["shared", "node_modules"]
|
paths: ["shared", "node_modules"],
|
||||||
}),
|
}),
|
||||||
typescript(),
|
typescript(),
|
||||||
resolve({
|
resolve({
|
||||||
// not all files you want to resolve are .js files
|
// not all files you want to resolve are .js files
|
||||||
extensions: ['.mjs', '.js', '.jsx', '.json'], // Default: [ '.mjs', '.js', '.json', '.node' ]
|
extensions: [".mjs", ".js", ".jsx", ".json"], // Default: [ '.mjs', '.js', '.json', '.node' ]
|
||||||
|
|
||||||
// whether to prefer built-in modules (e.g. `fs`, `path`) or
|
// whether to prefer built-in modules (e.g. `fs`, `path`) or
|
||||||
// local ones with the same names
|
// local ones with the same names
|
||||||
preferBuiltins: false, // Default: true
|
preferBuiltins: false, // Default: true
|
||||||
})
|
}),
|
||||||
],
|
],
|
||||||
treeshake: true
|
treeshake: true,
|
||||||
})
|
});
|
||||||
|
|
||||||
let { output } = await bundle.generate({
|
let { output } = await bundle.generate({
|
||||||
format: "iife",
|
format: "iife",
|
||||||
compact: true
|
compact: true,
|
||||||
})
|
});
|
||||||
let { code } = output[0];
|
let { code } = output[0];
|
||||||
|
|
||||||
let sass_res = sass.renderSync({
|
let sass_res = sass.renderSync({
|
||||||
file: folder + `/${pagename}.scss`,
|
file: folder + `/${pagename}.scss`,
|
||||||
includePaths: ["./node_modules", folder, "./shared"],
|
includePaths: ["./node_modules", folder, "./shared"],
|
||||||
outputStyle: "compressed"
|
outputStyle: "compressed",
|
||||||
})
|
});
|
||||||
|
|
||||||
let css = "<style>\n" + sass_res.css.toString("utf8") + "\n</style>\n";
|
let css = "<style>\n" + sass_res.css.toString("utf8") + "\n</style>\n";
|
||||||
let script = "<script>\n" + code + "\n</script>\n";
|
let script = "<script>\n" + code + "\n</script>\n";
|
||||||
let html = readFileSync(`${folder}/${pagename}.hbs`).toString("utf8");
|
let html = readFileSync(`${folder}/${pagename}.hbs`).toString("utf8");
|
||||||
|
|
||||||
let idx = html.indexOf("</head>")
|
let idx = html.indexOf("</head>");
|
||||||
if (idx < 0) throw new Error("No head element found")
|
if (idx < 0) throw new Error("No head element found");
|
||||||
let idx2 = html.indexOf("</body>")
|
let idx2 = html.indexOf("</body>");
|
||||||
if (idx2 < 0) throw new Error("No body element found")
|
if (idx2 < 0) throw new Error("No body element found");
|
||||||
|
|
||||||
if (idx < idx2) {
|
if (idx < idx2) {
|
||||||
let part1 = html.slice(0, idx)
|
let part1 = html.slice(0, idx);
|
||||||
let part2 = html.slice(idx, idx2);
|
let part2 = html.slice(idx, idx2);
|
||||||
let part3 = html.slice(idx2, html.length);
|
let part3 = html.slice(idx2, html.length);
|
||||||
html = part1 + css + part2 + script + part3;
|
html = part1 + css + part2 + script + part3;
|
||||||
} else {
|
} else {
|
||||||
let part1 = html.slice(0, idx2)
|
let part1 = html.slice(0, idx2);
|
||||||
let part2 = html.slice(idx2, idx);
|
let part2 = html.slice(idx2, idx);
|
||||||
let part3 = html.slice(idx, html.length);
|
let part3 = html.slice(idx, html.length);
|
||||||
html = part1 + script + part2 + css + part3;
|
html = part1 + script + part2 + css + part3;
|
||||||
@ -126,45 +121,50 @@ async function buildPage(folder) {
|
|||||||
minifyCSS: false,
|
minifyCSS: false,
|
||||||
minifyJS: false,
|
minifyJS: false,
|
||||||
removeComments: true,
|
removeComments: true,
|
||||||
useShortDoctype: true
|
useShortDoctype: true,
|
||||||
})
|
});
|
||||||
|
|
||||||
let gzips = await gzipSize(result)
|
let gzips = await gzipSize(result);
|
||||||
writeFileSync(`${outpath}/${pagename}.html`, result)
|
writeFileSync(`${outpath}/${pagename}.html`, result);
|
||||||
let stats = {
|
let stats = {
|
||||||
sass: sass_res.stats,
|
sass: sass_res.stats,
|
||||||
js: {
|
js: {
|
||||||
chars: code.length
|
chars: code.length,
|
||||||
},
|
},
|
||||||
css: {
|
css: {
|
||||||
chars: css.length
|
chars: css.length,
|
||||||
},
|
},
|
||||||
bundle_size: result.length,
|
bundle_size: result.length,
|
||||||
gzip_size: gzips
|
gzip_size: gzips,
|
||||||
}
|
};
|
||||||
|
|
||||||
writeFileSync(outpath + `/stats.json`, JSON.stringify(stats, null, " "))
|
writeFileSync(outpath + `/stats.json`, JSON.stringify(stats, null, " "));
|
||||||
}
|
}
|
||||||
|
|
||||||
async function run() {
|
async function run() {
|
||||||
console.log("Start compiling!");
|
console.log("Start compiling!");
|
||||||
let pages = getDirectories("./src");
|
let pages = getDirectories("./src");
|
||||||
await Promise.all(pages.map(async e => {
|
await Promise.all(
|
||||||
|
pages.map(async (e) => {
|
||||||
try {
|
try {
|
||||||
await buildPage(e)
|
await buildPage(e);
|
||||||
} catch (er) {
|
} catch (er) {
|
||||||
console.error("Failed compiling", basename(e))
|
console.error("Failed compiling", basename(e));
|
||||||
console.log(er)
|
console.log(er);
|
||||||
}
|
}
|
||||||
}))
|
})
|
||||||
console.log("Finished compiling!")
|
);
|
||||||
|
console.log("Finished compiling!");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
const chokidar = require("chokidar");
|
const chokidar = require("chokidar");
|
||||||
if (process.argv.join(" ").toLowerCase().indexOf("watch") >= 0)
|
if (process.argv.join(" ").toLowerCase().indexOf("watch") >= 0)
|
||||||
chokidar.watch(["./src", "./node_modules", "./package.json", "./package-lock.json"], {
|
chokidar
|
||||||
ignoreInitial: true
|
.watch(
|
||||||
})
|
["./src", "./node_modules", "./package.json", "./package-lock.json"],
|
||||||
|
{
|
||||||
|
ignoreInitial: true,
|
||||||
|
}
|
||||||
|
)
|
||||||
.on("all", () => run());
|
.on("all", () => run());
|
||||||
run()
|
run();
|
||||||
|
@ -1,15 +1,15 @@
|
|||||||
export function setCookie(cname, cvalue, exdate) {
|
export function setCookie(cname, cvalue, exdate) {
|
||||||
var expires = exdate ? `;expires=${exdate}` : "";
|
var expires = exdate ? `;expires=${exdate}` : "";
|
||||||
document.cookie = `${cname}=${cvalue}${expires}`
|
document.cookie = `${cname}=${cvalue}${expires}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getCookie(cname) {
|
export function getCookie(cname) {
|
||||||
var name = cname + "=";
|
var name = cname + "=";
|
||||||
var decodedCookie = decodeURIComponent(document.cookie);
|
var decodedCookie = decodeURIComponent(document.cookie);
|
||||||
var ca = decodedCookie.split(';');
|
var ca = decodedCookie.split(";");
|
||||||
for (var i = 0; i < ca.length; i++) {
|
for (var i = 0; i < ca.length; i++) {
|
||||||
var c = ca[i];
|
var c = ca[i];
|
||||||
while (c.charAt(0) == ' ') {
|
while (c.charAt(0) == " ") {
|
||||||
c = c.substring(1);
|
c = c.substring(1);
|
||||||
}
|
}
|
||||||
if (c.indexOf(name) == 0) {
|
if (c.indexOf(name) == 0) {
|
||||||
|
@ -2,9 +2,8 @@ export default function fireEvent(element, event) {
|
|||||||
if (document.createEventObject) {
|
if (document.createEventObject) {
|
||||||
// dispatch for IE
|
// dispatch for IE
|
||||||
var evt = document.createEventObject();
|
var evt = document.createEventObject();
|
||||||
return element.fireEvent('on' + event, evt)
|
return element.fireEvent("on" + event, evt);
|
||||||
}
|
} else {
|
||||||
else {
|
|
||||||
// dispatch for firefox + others
|
// dispatch for firefox + others
|
||||||
var evt = document.createEvent("HTMLEvents");
|
var evt = document.createEvent("HTMLEvents");
|
||||||
evt.initEvent(event, true, true); // event type,bubbling,cancelable
|
evt.initEvent(event, true, true); // event type,bubbling,cancelable
|
||||||
|
@ -1,14 +1,18 @@
|
|||||||
export default function getFormData(element) {
|
export default function getFormData(element) {
|
||||||
let data = {};
|
let data = {};
|
||||||
if (element.name !== undefined && element.name !== null && element.name !== "") {
|
if (
|
||||||
|
element.name !== undefined &&
|
||||||
|
element.name !== null &&
|
||||||
|
element.name !== ""
|
||||||
|
) {
|
||||||
if (typeof element.name === "string") {
|
if (typeof element.name === "string") {
|
||||||
if (element.type === "checkbox") data[element.name] = element.checked;
|
if (element.type === "checkbox") data[element.name] = element.checked;
|
||||||
else data[element.name] = element.value;
|
else data[element.name] = element.value;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
element.childNodes.forEach(child => {
|
element.childNodes.forEach((child) => {
|
||||||
let res = getFormData(child);
|
let res = getFormData(child);
|
||||||
data = Object.assign(data, res);
|
data = Object.assign(data, res);
|
||||||
})
|
});
|
||||||
return data;
|
return data;
|
||||||
}
|
}
|
@ -1,24 +1,24 @@
|
|||||||
(() => {
|
(() => {
|
||||||
const run = () => {
|
const run = () => {
|
||||||
document.querySelectorAll(".floating>input").forEach(e => {
|
document.querySelectorAll(".floating>input").forEach((e) => {
|
||||||
function checkState() {
|
function checkState() {
|
||||||
if (e.value !== "") {
|
if (e.value !== "") {
|
||||||
if (e.classList.contains("used")) return;
|
if (e.classList.contains("used")) return;
|
||||||
e.classList.add("used")
|
e.classList.add("used");
|
||||||
} else {
|
} else {
|
||||||
if (e.classList.contains("used")) e.classList.remove("used")
|
if (e.classList.contains("used")) e.classList.remove("used");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
e.addEventListener("change", () => checkState())
|
e.addEventListener("change", () => checkState());
|
||||||
checkState()
|
checkState();
|
||||||
})
|
});
|
||||||
}
|
};
|
||||||
|
|
||||||
run();
|
run();
|
||||||
|
|
||||||
var mutationObserver = new MutationObserver(() => {
|
var mutationObserver = new MutationObserver(() => {
|
||||||
run()
|
run();
|
||||||
});
|
});
|
||||||
|
|
||||||
mutationObserver.observe(document.documentElement, {
|
mutationObserver.observe(document.documentElement, {
|
||||||
@ -28,6 +28,6 @@
|
|||||||
subtree: true,
|
subtree: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
window.Mutt
|
window.Mutt;
|
||||||
window.addEventListener("DOMNodeInserted", () => run())
|
window.addEventListener("DOMNodeInserted", () => run());
|
||||||
})();
|
})();
|
@ -5,7 +5,7 @@
|
|||||||
min-height: 45px;
|
min-height: 45px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.floating>input {
|
.floating > input {
|
||||||
font-size: 18px;
|
font-size: 18px;
|
||||||
padding: 10px 10px 10px 5px;
|
padding: 10px 10px 10px 5px;
|
||||||
appearance: none;
|
appearance: none;
|
||||||
@ -19,13 +19,13 @@
|
|||||||
border-bottom: 1px solid #757575;
|
border-bottom: 1px solid #757575;
|
||||||
}
|
}
|
||||||
|
|
||||||
.floating>input:focus {
|
.floating > input:focus {
|
||||||
outline: none;
|
outline: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Label */
|
/* Label */
|
||||||
|
|
||||||
.floating>label {
|
.floating > label {
|
||||||
color: #999;
|
color: #999;
|
||||||
font-size: 18px;
|
font-size: 18px;
|
||||||
font-weight: normal;
|
font-weight: normal;
|
||||||
@ -38,10 +38,10 @@
|
|||||||
|
|
||||||
/* active */
|
/* active */
|
||||||
|
|
||||||
.floating>input:focus~label,
|
.floating > input:focus ~ label,
|
||||||
.floating>input.used~label {
|
.floating > input.used ~ label {
|
||||||
top: -.75em;
|
top: -0.75em;
|
||||||
transform: scale(.75);
|
transform: scale(0.75);
|
||||||
left: -2px;
|
left: -2px;
|
||||||
/* font-size: 14px; */
|
/* font-size: 14px; */
|
||||||
color: $primary;
|
color: $primary;
|
||||||
@ -58,7 +58,7 @@
|
|||||||
|
|
||||||
.bar:before,
|
.bar:before,
|
||||||
.bar:after {
|
.bar:after {
|
||||||
content: '';
|
content: "";
|
||||||
height: 2px;
|
height: 2px;
|
||||||
width: 0;
|
width: 0;
|
||||||
bottom: 1px;
|
bottom: 1px;
|
||||||
@ -77,8 +77,8 @@
|
|||||||
|
|
||||||
/* active */
|
/* active */
|
||||||
|
|
||||||
.floating>input:focus~.bar:before,
|
.floating > input:focus ~ .bar:before,
|
||||||
.floating>input:focus~.bar:after {
|
.floating > input:focus ~ .bar:after {
|
||||||
width: 50%;
|
width: 50%;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -96,7 +96,7 @@
|
|||||||
|
|
||||||
/* active */
|
/* active */
|
||||||
|
|
||||||
.floating>input:focus~.highlight {
|
.floating > input:focus ~ .highlight {
|
||||||
animation: inputHighlighter 0.3s ease;
|
animation: inputHighlighter 0.3s ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
2
views/shared/preact.min.js
vendored
2
views/shared/preact.min.js
vendored
File diff suppressed because one or more lines are too long
@ -1,16 +1,26 @@
|
|||||||
export default function request(endpoint, method, data) {
|
export default function request(endpoint, method, data) {
|
||||||
var headers = new Headers();
|
var headers = new Headers();
|
||||||
headers.set('Content-Type', 'application/json');
|
headers.set("Content-Type", "application/json");
|
||||||
return fetch(endpoint, {
|
return fetch(endpoint, {
|
||||||
method: method,
|
method: method,
|
||||||
body: JSON.stringify(data),
|
body: JSON.stringify(data),
|
||||||
headers: headers,
|
headers: headers,
|
||||||
credentials: "include"
|
credentials: "include",
|
||||||
}).then(async e => {
|
|
||||||
if (e.status !== 200) throw new Error(await e.text() || e.statusText);
|
|
||||||
return e.json()
|
|
||||||
}).then(e => {
|
|
||||||
if (e.error) return Promise.reject(new Error(typeof e.error === "string" ? e.error : JSON.stringify(e.error)));
|
|
||||||
return e;
|
|
||||||
})
|
})
|
||||||
|
.then(async (e) => {
|
||||||
|
if (e.status !== 200)
|
||||||
|
throw new Error((await e.text()) || e.statusText);
|
||||||
|
return e.json();
|
||||||
|
})
|
||||||
|
.then((e) => {
|
||||||
|
if (e.error)
|
||||||
|
return Promise.reject(
|
||||||
|
new Error(
|
||||||
|
typeof e.error === "string"
|
||||||
|
? e.error
|
||||||
|
: JSON.stringify(e.error)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
return e;
|
||||||
|
});
|
||||||
}
|
}
|
File diff suppressed because one or more lines are too long
@ -1,5 +1,5 @@
|
|||||||
// $primary: #4a89dc;
|
// $primary: #4a89dc;
|
||||||
$primary: #1E88E5;
|
$primary: #1e88e5;
|
||||||
$error: #ff2f00;
|
$error: #ff2f00;
|
||||||
.btn-primary {
|
.btn-primary {
|
||||||
color: white !important;
|
color: white !important;
|
||||||
|
@ -26,9 +26,8 @@ Handlebars.registerHelper("formatDate", function (datetime, format) {
|
|||||||
document.getElementById("sitename").innerText = title;
|
document.getElementById("sitename").innerText = title;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const cc = document.getElementById("custom_data");
|
||||||
const cc = document.getElementById("custom_data")
|
const ccc = document.getElementById("custom_data_cont");
|
||||||
const ccc = document.getElementById("custom_data_cont")
|
|
||||||
|
|
||||||
function setCustomCard(content) {
|
function setCustomCard(content) {
|
||||||
if (!content) {
|
if (!content) {
|
||||||
@ -40,8 +39,8 @@ Handlebars.registerHelper("formatDate", function (datetime, format) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const error_cont = document.getElementById("error_cont")
|
const error_cont = document.getElementById("error_cont");
|
||||||
const error_msg = document.getElementById("error_msg")
|
const error_msg = document.getElementById("error_msg");
|
||||||
|
|
||||||
function catchError(error) {
|
function catchError(error) {
|
||||||
error_cont.style.display = "";
|
error_cont.style.display = "";
|
||||||
@ -50,40 +49,53 @@ Handlebars.registerHelper("formatDate", function (datetime, format) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function renderUser() {
|
async function renderUser() {
|
||||||
console.log("Rendering User")
|
console.log("Rendering User");
|
||||||
setTitle("User")
|
setTitle("User");
|
||||||
const listt = Handlebars.compile(document.getElementById("template-user-list").innerText)
|
const listt = Handlebars.compile(
|
||||||
|
document.getElementById("template-user-list").innerText
|
||||||
|
);
|
||||||
|
|
||||||
async function loadList() {
|
async function loadList() {
|
||||||
let data = await request("/api/admin/user", "GET");
|
let data = await request("/api/admin/user", "GET");
|
||||||
tableb.innerHTML = listt({
|
tableb.innerHTML = listt({
|
||||||
users: data
|
users: data,
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
window.userOnChangeType = (id) => {
|
window.userOnChangeType = (id) => {
|
||||||
request("/api/admin/user?id=" + id, "PUT").then(() => loadList()).catch(catchError)
|
request("/api/admin/user?id=" + id, "PUT")
|
||||||
}
|
.then(() => loadList())
|
||||||
|
.catch(catchError);
|
||||||
|
};
|
||||||
|
|
||||||
window.deleteUser = (id) => {
|
window.deleteUser = (id) => {
|
||||||
request("/api/admin/user?id=" + id, "DELETE").then(() => loadList()).catch(catchError)
|
request("/api/admin/user?id=" + id, "DELETE")
|
||||||
}
|
.then(() => loadList())
|
||||||
|
.catch(catchError);
|
||||||
|
};
|
||||||
|
|
||||||
await loadList();
|
await loadList();
|
||||||
}
|
}
|
||||||
|
|
||||||
async function renderPermissions(client_id, client_name) {
|
async function renderPermissions(client_id, client_name) {
|
||||||
const listt = Handlebars.compile(document.getElementById("template-permission-list").innerText);
|
const listt = Handlebars.compile(
|
||||||
const formt = Handlebars.compile(document.getElementById("template-permission-form").innerText);
|
document.getElementById("template-permission-list").innerText
|
||||||
|
);
|
||||||
|
const formt = Handlebars.compile(
|
||||||
|
document.getElementById("template-permission-form").innerText
|
||||||
|
);
|
||||||
setCustomCard();
|
setCustomCard();
|
||||||
async function loadList() {
|
async function loadList() {
|
||||||
try {
|
try {
|
||||||
let data = await request("/api/admin/permission?client=" + client_id, "GET");
|
let data = await request(
|
||||||
|
"/api/admin/permission?client=" + client_id,
|
||||||
|
"GET"
|
||||||
|
);
|
||||||
tableb.innerHTML = listt({
|
tableb.innerHTML = listt({
|
||||||
client_id: client_id,
|
client_id: client_id,
|
||||||
client_name: client_name,
|
client_name: client_name,
|
||||||
permissions: data
|
permissions: data,
|
||||||
})
|
});
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
catchError(err);
|
catchError(err);
|
||||||
}
|
}
|
||||||
@ -91,11 +103,13 @@ Handlebars.registerHelper("formatDate", function (datetime, format) {
|
|||||||
|
|
||||||
window.gotoClients = () => {
|
window.gotoClients = () => {
|
||||||
renderClient();
|
renderClient();
|
||||||
}
|
};
|
||||||
|
|
||||||
window.deletePermission = (id) => {
|
window.deletePermission = (id) => {
|
||||||
request("/api/admin/permission?id=" + id, "DELETE").then(() => loadList()).catch(catchError)
|
request("/api/admin/permission?id=" + id, "DELETE")
|
||||||
}
|
.then(() => loadList())
|
||||||
|
.catch(catchError);
|
||||||
|
};
|
||||||
|
|
||||||
window.createPermission = () => {
|
window.createPermission = () => {
|
||||||
try {
|
try {
|
||||||
@ -103,41 +117,49 @@ Handlebars.registerHelper("formatDate", function (datetime, format) {
|
|||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.log("Err", err);
|
console.log("Err", err);
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
|
|
||||||
window.createPermissionSubmit = (elm) => {
|
window.createPermissionSubmit = (elm) => {
|
||||||
console.log(elm);
|
console.log(elm);
|
||||||
let data = getFormData(elm);
|
let data = getFormData(elm);
|
||||||
console.log(data);
|
console.log(data);
|
||||||
request("/api/admin/permission", "POST", data).then(() => setCustomCard()).then(() => loadList()).catch(catchError)
|
request("/api/admin/permission", "POST", data)
|
||||||
}
|
.then(() => setCustomCard())
|
||||||
await loadList()
|
.then(() => loadList())
|
||||||
|
.catch(catchError);
|
||||||
|
};
|
||||||
|
await loadList();
|
||||||
}
|
}
|
||||||
|
|
||||||
async function renderClient() {
|
async function renderClient() {
|
||||||
console.log("Rendering Client")
|
console.log("Rendering Client");
|
||||||
setTitle("Client")
|
setTitle("Client");
|
||||||
|
|
||||||
const listt = Handlebars.compile(document.getElementById("template-client-list").innerText)
|
const listt = Handlebars.compile(
|
||||||
const formt = Handlebars.compile(document.getElementById("template-client-form").innerText)
|
document.getElementById("template-client-list").innerText
|
||||||
|
);
|
||||||
|
const formt = Handlebars.compile(
|
||||||
|
document.getElementById("template-client-form").innerText
|
||||||
|
);
|
||||||
|
|
||||||
let clients = [];
|
let clients = [];
|
||||||
async function loadList() {
|
async function loadList() {
|
||||||
let data = await request("/api/admin/client", "GET");
|
let data = await request("/api/admin/client", "GET");
|
||||||
clients = data;
|
clients = data;
|
||||||
tableb.innerHTML = listt({
|
tableb.innerHTML = listt({
|
||||||
clients: data
|
clients: data,
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
window.permissionsClient = (id) => {
|
window.permissionsClient = (id) => {
|
||||||
renderPermissions(id, clients.find(e => e._id === id).name);
|
renderPermissions(id, clients.find((e) => e._id === id).name);
|
||||||
}
|
};
|
||||||
|
|
||||||
window.deleteClient = (id) => {
|
window.deleteClient = (id) => {
|
||||||
request("/api/admin/client/id=" + id, "DELETE").then(() => loadList()).catch(catchError)
|
request("/api/admin/client/id=" + id, "DELETE")
|
||||||
}
|
.then(() => loadList())
|
||||||
|
.catch(catchError);
|
||||||
|
};
|
||||||
|
|
||||||
window.createClientSubmit = (elm) => {
|
window.createClientSubmit = (elm) => {
|
||||||
console.log(elm);
|
console.log(elm);
|
||||||
@ -146,45 +168,57 @@ Handlebars.registerHelper("formatDate", function (datetime, format) {
|
|||||||
let id = data.id;
|
let id = data.id;
|
||||||
delete data.id;
|
delete data.id;
|
||||||
if (id && id !== "") {
|
if (id && id !== "") {
|
||||||
request("/api/admin/client?id=" + id, "PUT", data).then(() => setCustomCard()).then(() => loadList()).catch(catchError)
|
request("/api/admin/client?id=" + id, "PUT", data)
|
||||||
|
.then(() => setCustomCard())
|
||||||
|
.then(() => loadList())
|
||||||
|
.catch(catchError);
|
||||||
} else {
|
} else {
|
||||||
request("/api/admin/client", "POST", data).then(() => setCustomCard()).then(() => loadList()).catch(catchError)
|
request("/api/admin/client", "POST", data)
|
||||||
}
|
.then(() => setCustomCard())
|
||||||
|
.then(() => loadList())
|
||||||
|
.catch(catchError);
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
|
||||||
window.createClient = () => {
|
window.createClient = () => {
|
||||||
setCustomCard(formt());
|
setCustomCard(formt());
|
||||||
}
|
};
|
||||||
|
|
||||||
window.editClient = (id) => {
|
window.editClient = (id) => {
|
||||||
let client = clients.find(e => e._id === id);
|
let client = clients.find((e) => e._id === id);
|
||||||
if (!client) return catchError(new Error("Client does not exist!!"))
|
if (!client) return catchError(new Error("Client does not exist!!"));
|
||||||
setCustomCard(formt(client));
|
setCustomCard(formt(client));
|
||||||
}
|
};
|
||||||
|
|
||||||
await loadList().catch(catchError);
|
await loadList().catch(catchError);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function renderRegCode() {
|
async function renderRegCode() {
|
||||||
console.log("Rendering RegCode")
|
console.log("Rendering RegCode");
|
||||||
setTitle("RegCode")
|
setTitle("RegCode");
|
||||||
|
|
||||||
const listt = Handlebars.compile(document.getElementById("template-regcode-list").innerText)
|
const listt = Handlebars.compile(
|
||||||
|
document.getElementById("template-regcode-list").innerText
|
||||||
|
);
|
||||||
|
|
||||||
async function loadList() {
|
async function loadList() {
|
||||||
let data = await request("/api/admin/regcode", "GET");
|
let data = await request("/api/admin/regcode", "GET");
|
||||||
tableb.innerHTML = listt({
|
tableb.innerHTML = listt({
|
||||||
regcodes: data
|
regcodes: data,
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
window.deleteRegcode = (id) => {
|
window.deleteRegcode = (id) => {
|
||||||
request("/api/admin/regcode?id=" + id, "DELETE").then(() => loadList()).catch(catchError)
|
request("/api/admin/regcode?id=" + id, "DELETE")
|
||||||
}
|
.then(() => loadList())
|
||||||
|
.catch(catchError);
|
||||||
|
};
|
||||||
|
|
||||||
window.createRegcode = () => {
|
window.createRegcode = () => {
|
||||||
request("/api/admin/regcode", "POST").then(() => loadList()).catch(catchError);
|
request("/api/admin/regcode", "POST")
|
||||||
}
|
.then(() => loadList())
|
||||||
|
.catch(catchError);
|
||||||
|
};
|
||||||
|
|
||||||
await loadList().catch(catchError);
|
await loadList().catch(catchError);
|
||||||
}
|
}
|
||||||
@ -192,14 +226,14 @@ Handlebars.registerHelper("formatDate", function (datetime, format) {
|
|||||||
const type = new URL(window.location.href).searchParams.get("type");
|
const type = new URL(window.location.href).searchParams.get("type");
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case "client":
|
case "client":
|
||||||
renderClient().catch(catchError)
|
renderClient().catch(catchError);
|
||||||
break
|
break;
|
||||||
case "regcode":
|
case "regcode":
|
||||||
renderRegCode().catch(catchError)
|
renderRegCode().catch(catchError);
|
||||||
break;
|
break;
|
||||||
case "user":
|
case "user":
|
||||||
default:
|
default:
|
||||||
renderUser().catch(catchError);
|
renderUser().catch(catchError);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
})()
|
})();
|
||||||
|
@ -1,4 +1,6 @@
|
|||||||
document.getElementById("hidden_form").action += window.location.href.split("?")[1];
|
document.getElementById("hidden_form").action += window.location.href.split(
|
||||||
|
"?"
|
||||||
|
)[1];
|
||||||
|
|
||||||
function submit() {
|
function submit() {
|
||||||
document.getElementById("hidden_form").submit();
|
document.getElementById("hidden_form").submit();
|
||||||
@ -10,9 +12,10 @@ document.getElementById("cancel").onclick = () => {
|
|||||||
if (uri === "$local") {
|
if (uri === "$local") {
|
||||||
uri = "/code";
|
uri = "/code";
|
||||||
}
|
}
|
||||||
window.location.href = uri + "?error=access_denied&state=" + u.searchParams.get("state");
|
window.location.href =
|
||||||
}
|
uri + "?error=access_denied&state=" + u.searchParams.get("state");
|
||||||
|
};
|
||||||
|
|
||||||
document.getElementById("allow").onclick = () => {
|
document.getElementById("allow").onclick = () => {
|
||||||
submit()
|
submit();
|
||||||
}
|
};
|
||||||
|
@ -22,12 +22,17 @@ body {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.title {
|
.title {
|
||||||
text-align:center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
h1, h3 { font-weight: 300; }
|
h1,
|
||||||
|
h3 {
|
||||||
|
font-weight: 300;
|
||||||
|
}
|
||||||
|
|
||||||
h1 { color: #636363; }
|
h1 {
|
||||||
|
color: #636363;
|
||||||
|
}
|
||||||
|
|
||||||
ul {
|
ul {
|
||||||
list-style: none;
|
list-style: none;
|
||||||
@ -75,5 +80,6 @@ ul {
|
|||||||
padding: 3em 2em 2em 2em;
|
padding: 3em 2em 2em 2em;
|
||||||
background: #fafafa;
|
background: #fafafa;
|
||||||
border: 1px solid #ebebeb;
|
border: 1px solid #ebebeb;
|
||||||
box-shadow: rgba(0,0,0,0.14902) 0px 1px 1px 0px,rgba(0,0,0,0.09804) 0px 1px 2px 0px;
|
box-shadow: rgba(0, 0, 0, 0.14902) 0px 1px 1px 0px,
|
||||||
|
rgba(0, 0, 0, 0.09804) 0px 1px 2px 0px;
|
||||||
}
|
}
|
@ -1,114 +1,123 @@
|
|||||||
import sha from "sha512";
|
import sha from "sha512";
|
||||||
import {
|
import { setCookie, getCookie } from "cookie";
|
||||||
setCookie,
|
import "inputs";
|
||||||
getCookie
|
|
||||||
} from "cookie"
|
|
||||||
import "inputs"
|
|
||||||
|
|
||||||
const loader = document.getElementById("loader")
|
const loader = document.getElementById("loader");
|
||||||
const container = document.getElementById("container")
|
const container = document.getElementById("container");
|
||||||
const usernameinput = document.getElementById("username")
|
const usernameinput = document.getElementById("username");
|
||||||
const usernamegroup = document.getElementById("usernamegroup")
|
const usernamegroup = document.getElementById("usernamegroup");
|
||||||
const uerrorfield = document.getElementById("uerrorfield")
|
const uerrorfield = document.getElementById("uerrorfield");
|
||||||
const passwordinput = document.getElementById("password")
|
const passwordinput = document.getElementById("password");
|
||||||
const passwordgroup = document.getElementById("passwordgroup")
|
const passwordgroup = document.getElementById("passwordgroup");
|
||||||
const perrorfield = document.getElementById("perrorfield")
|
const perrorfield = document.getElementById("perrorfield");
|
||||||
const nextbutton = document.getElementById("nextbutton")
|
const nextbutton = document.getElementById("nextbutton");
|
||||||
const loginbutton = document.getElementById("loginbutton")
|
const loginbutton = document.getElementById("loginbutton");
|
||||||
|
|
||||||
let username;
|
let username;
|
||||||
let salt;
|
let salt;
|
||||||
|
|
||||||
usernameinput.focus()
|
usernameinput.focus();
|
||||||
|
|
||||||
const loading = () => {
|
const loading = () => {
|
||||||
container.style.filter = "blur(2px)";
|
container.style.filter = "blur(2px)";
|
||||||
loader.style.display = "";
|
loader.style.display = "";
|
||||||
}
|
};
|
||||||
|
|
||||||
const loading_fin = () => {
|
const loading_fin = () => {
|
||||||
container.style.filter = ""
|
container.style.filter = "";
|
||||||
loader.style.display = "none";
|
loader.style.display = "none";
|
||||||
}
|
};
|
||||||
loading_fin();
|
loading_fin();
|
||||||
|
|
||||||
usernameinput.onkeydown = (e) => {
|
usernameinput.onkeydown = (e) => {
|
||||||
var keycode = e.keyCode ? e.keyCode : e.which;
|
var keycode = e.keyCode ? e.keyCode : e.which;
|
||||||
if (keycode === 13) nextbutton.click();
|
if (keycode === 13) nextbutton.click();
|
||||||
clearError(uerrorfield);
|
clearError(uerrorfield);
|
||||||
}
|
};
|
||||||
|
|
||||||
nextbutton.onclick = async () => {
|
nextbutton.onclick = async () => {
|
||||||
loading();
|
loading();
|
||||||
username = usernameinput.value;
|
username = usernameinput.value;
|
||||||
try {
|
try {
|
||||||
let res = await fetch("/api/user/login?type=username&username=" + username, {
|
let res = await fetch(
|
||||||
method: "POST"
|
"/api/user/login?type=username&username=" + username,
|
||||||
}).then(e => {
|
{
|
||||||
if (e.status !== 200) throw new Error(e.statusText)
|
method: "POST",
|
||||||
return e.json()
|
}
|
||||||
}).then(data => {
|
)
|
||||||
|
.then((e) => {
|
||||||
|
if (e.status !== 200) throw new Error(e.statusText);
|
||||||
|
return e.json();
|
||||||
|
})
|
||||||
|
.then((data) => {
|
||||||
if (data.error) {
|
if (data.error) {
|
||||||
return Promise.reject(new Error(data.error))
|
return Promise.reject(new Error(data.error));
|
||||||
}
|
}
|
||||||
return data;
|
return data;
|
||||||
})
|
});
|
||||||
salt = res.salt;
|
salt = res.salt;
|
||||||
usernamegroup.classList.add("invisible")
|
usernamegroup.classList.add("invisible");
|
||||||
passwordgroup.classList.remove("invisible")
|
passwordgroup.classList.remove("invisible");
|
||||||
passwordinput.focus()
|
passwordinput.focus();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
showError(uerrorfield, e.message)
|
showError(uerrorfield, e.message);
|
||||||
}
|
}
|
||||||
loading_fin()
|
loading_fin();
|
||||||
}
|
};
|
||||||
|
|
||||||
passwordinput.onkeydown = (e) => {
|
passwordinput.onkeydown = (e) => {
|
||||||
var keycode = e.keyCode ? e.keyCode : e.which;
|
var keycode = e.keyCode ? e.keyCode : e.which;
|
||||||
if (keycode === 13) loginbutton.click();
|
if (keycode === 13) loginbutton.click();
|
||||||
clearError(perrorfield);
|
clearError(perrorfield);
|
||||||
}
|
};
|
||||||
|
|
||||||
loginbutton.onclick = async () => {
|
loginbutton.onclick = async () => {
|
||||||
loading();
|
loading();
|
||||||
let pw = sha(salt + passwordinput.value);
|
let pw = sha(salt + passwordinput.value);
|
||||||
try {
|
try {
|
||||||
let { login, special, tfa } = await fetch("/api/user/login?type=password", {
|
let { login, special, tfa } = await fetch(
|
||||||
|
"/api/user/login?type=password",
|
||||||
|
{
|
||||||
method: "POST",
|
method: "POST",
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
username: usernameinput.value,
|
username: usernameinput.value,
|
||||||
password: pw
|
password: pw,
|
||||||
}),
|
}),
|
||||||
headers: {
|
headers: {
|
||||||
'content-type': 'application/json'
|
"content-type": "application/json",
|
||||||
},
|
},
|
||||||
}).then(e => {
|
}
|
||||||
if (e.status !== 200) throw new Error(e.statusText)
|
)
|
||||||
return e.json()
|
.then((e) => {
|
||||||
}).then(data => {
|
if (e.status !== 200) throw new Error(e.statusText);
|
||||||
|
return e.json();
|
||||||
|
})
|
||||||
|
.then((data) => {
|
||||||
if (data.error) {
|
if (data.error) {
|
||||||
return Promise.reject(new Error(data.error))
|
return Promise.reject(new Error(data.error));
|
||||||
}
|
}
|
||||||
return data;
|
return data;
|
||||||
})
|
});
|
||||||
|
|
||||||
setCookie("login", login.token, new Date(login.expires).toUTCString());
|
setCookie("login", login.token, new Date(login.expires).toUTCString());
|
||||||
setCookie("special", special.token, new Date(special.expires).toUTCString());
|
setCookie(
|
||||||
let d = new Date()
|
"special",
|
||||||
d.setTime(d.getTime() + (30 * 24 * 60 * 60 * 1000)); //Keep the username 30 days
|
special.token,
|
||||||
|
new Date(special.expires).toUTCString()
|
||||||
|
);
|
||||||
|
let d = new Date();
|
||||||
|
d.setTime(d.getTime() + 30 * 24 * 60 * 60 * 1000); //Keep the username 30 days
|
||||||
setCookie("username", username, d.toUTCString());
|
setCookie("username", username, d.toUTCString());
|
||||||
let url = new URL(window.location.href);
|
let url = new URL(window.location.href);
|
||||||
let state = url.searchParams.get("state")
|
let state = url.searchParams.get("state");
|
||||||
let red = "/"
|
let red = "/";
|
||||||
|
|
||||||
if (tfa) twofactor(tfa);
|
if (tfa) twofactor(tfa);
|
||||||
else {
|
else {
|
||||||
if (state) {
|
if (state) {
|
||||||
let base64 = url.searchParams.get("base64")
|
let base64 = url.searchParams.get("base64");
|
||||||
if (base64)
|
if (base64) red = atob(state);
|
||||||
red = atob(state)
|
else red = state;
|
||||||
else
|
|
||||||
red = state
|
|
||||||
}
|
}
|
||||||
window.location.href = red;
|
window.location.href = red;
|
||||||
}
|
}
|
||||||
@ -117,19 +126,19 @@ loginbutton.onclick = async () => {
|
|||||||
showError(perrorfield, e.message);
|
showError(perrorfield, e.message);
|
||||||
}
|
}
|
||||||
loading_fin();
|
loading_fin();
|
||||||
}
|
};
|
||||||
|
|
||||||
function clearError(field) {
|
function clearError(field) {
|
||||||
field.innerText = "";
|
field.innerText = "";
|
||||||
field.classList.add("invisible")
|
field.classList.add("invisible");
|
||||||
}
|
}
|
||||||
|
|
||||||
function showError(field, error) {
|
function showError(field, error) {
|
||||||
field.innerText = error;
|
field.innerText = error;
|
||||||
field.classList.remove("invisible")
|
field.classList.remove("invisible");
|
||||||
}
|
}
|
||||||
|
|
||||||
username = getCookie("username")
|
username = getCookie("username");
|
||||||
if (username) {
|
if (username) {
|
||||||
usernameinput.value = username;
|
usernameinput.value = username;
|
||||||
|
|
||||||
@ -138,10 +147,9 @@ if (username) {
|
|||||||
usernameinput.dispatchEvent(evt);
|
usernameinput.dispatchEvent(evt);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
function twofactor(tfa) {
|
function twofactor(tfa) {
|
||||||
let list = tfa
|
let list = tfa
|
||||||
.map(entry => {
|
.map((entry) => {
|
||||||
switch (entry) {
|
switch (entry) {
|
||||||
case 0: // OTC
|
case 0: // OTC
|
||||||
return "Authenticator App";
|
return "Authenticator App";
|
||||||
@ -150,7 +158,7 @@ function twofactor(tfa) {
|
|||||||
}
|
}
|
||||||
return undefined;
|
return undefined;
|
||||||
})
|
})
|
||||||
.filter(e => e !== undefined)
|
.filter((e) => e !== undefined)
|
||||||
.reduce((p, c) => p + `<li>${c}</li>`, "");
|
.reduce((p, c) => p + `<li>${c}</li>`, "");
|
||||||
|
|
||||||
let tfl = document.getElementById("tflist");
|
let tfl = document.getElementById("tflist");
|
||||||
|
@ -41,7 +41,8 @@ form {
|
|||||||
padding: 3em 2em 2em 2em;
|
padding: 3em 2em 2em 2em;
|
||||||
background: #fafafa;
|
background: #fafafa;
|
||||||
border: 1px solid #ebebeb;
|
border: 1px solid #ebebeb;
|
||||||
box-shadow: rgba(0, 0, 0, 0.14902) 0px 1px 1px 0px, rgba(0, 0, 0, 0.09804) 0px 1px 2px 0px;
|
box-shadow: rgba(0, 0, 0, 0.14902) 0px 1px 1px 0px,
|
||||||
|
rgba(0, 0, 0, 0.09804) 0px 1px 2px 0px;
|
||||||
position: relative;
|
position: relative;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -50,10 +51,13 @@ form {
|
|||||||
height: 64px;
|
height: 64px;
|
||||||
margin: auto;
|
margin: auto;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 0; left: 0; bottom: 0; right: 0;
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
bottom: 0;
|
||||||
|
right: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.loader{
|
.loader {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
position: relative;
|
position: relative;
|
||||||
z-index: 100;
|
z-index: 100;
|
||||||
@ -78,7 +82,6 @@ form {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
footer {
|
footer {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
@ -86,13 +89,13 @@ footer {
|
|||||||
footer p {
|
footer p {
|
||||||
color: #888;
|
color: #888;
|
||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
letter-spacing: .4px;
|
letter-spacing: 0.4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
footer a {
|
footer a {
|
||||||
color: $primary;
|
color: $primary;
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
transition: all .2s ease;
|
transition: all 0.2s ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
footer a:hover {
|
footer a:hover {
|
||||||
@ -102,11 +105,11 @@ footer a:hover {
|
|||||||
|
|
||||||
footer img {
|
footer img {
|
||||||
width: 80px;
|
width: 80px;
|
||||||
transition: all .2s ease;
|
transition: all 0.2s ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
footer img:hover {
|
footer img:hover {
|
||||||
opacity: .83;
|
opacity: 0.83;
|
||||||
}
|
}
|
||||||
|
|
||||||
footer img:focus,
|
footer img:focus,
|
||||||
|
@ -1,68 +1,96 @@
|
|||||||
import { h, Component, render } from "preact"
|
import { h, Component, render } from "preact";
|
||||||
import "inputs"
|
import "inputs";
|
||||||
import "./u2f-api-polyfill"
|
import "./u2f-api-polyfill";
|
||||||
|
|
||||||
import sha from "sha512";
|
import sha from "sha512";
|
||||||
import {
|
import { setCookie, getCookie } from "cookie";
|
||||||
setCookie,
|
|
||||||
getCookie
|
|
||||||
} from "cookie"
|
|
||||||
|
|
||||||
let appname = "test";
|
let appname = "test";
|
||||||
|
|
||||||
function Loader() {
|
function Loader() {
|
||||||
return <div class="loader_box" id="loader">
|
return (
|
||||||
|
<div class="loader_box" id="loader">
|
||||||
<div class="loader"></div>
|
<div class="loader"></div>
|
||||||
</div>
|
</div>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
class Username extends Component<{ username: string, onNext: (username: string, salt: string) => void }, { error: string, loading: boolean }> {
|
class Username extends Component<
|
||||||
|
{ username: string; onNext: (username: string, salt: string) => void },
|
||||||
|
{ error: string; loading: boolean }
|
||||||
|
> {
|
||||||
username_input: HTMLInputElement;
|
username_input: HTMLInputElement;
|
||||||
constructor() {
|
constructor() {
|
||||||
super();
|
super();
|
||||||
this.state = { error: undefined, loading: false }
|
this.state = { error: undefined, loading: false };
|
||||||
}
|
}
|
||||||
|
|
||||||
async onClick() {
|
async onClick() {
|
||||||
this.setState({ loading: true });
|
this.setState({ loading: true });
|
||||||
try {
|
try {
|
||||||
let res = await fetch("/api/user/login?type=username&username=" + this.username_input.value, {
|
let res = await fetch(
|
||||||
method: "POST"
|
"/api/user/login?type=username&username=" +
|
||||||
}).then(e => {
|
this.username_input.value,
|
||||||
if (e.status !== 200) throw new Error(e.statusText)
|
{
|
||||||
return e.json()
|
method: "POST",
|
||||||
}).then(data => {
|
}
|
||||||
|
)
|
||||||
|
.then((e) => {
|
||||||
|
if (e.status !== 200) throw new Error(e.statusText);
|
||||||
|
return e.json();
|
||||||
|
})
|
||||||
|
.then((data) => {
|
||||||
if (data.error) {
|
if (data.error) {
|
||||||
return Promise.reject(new Error(data.error))
|
return Promise.reject(new Error(data.error));
|
||||||
}
|
}
|
||||||
return data;
|
return data;
|
||||||
})
|
});
|
||||||
let salt = res.salt;
|
let salt = res.salt;
|
||||||
this.props.onNext(this.username_input.value, salt);
|
this.props.onNext(this.username_input.value, salt);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
this.setState({
|
this.setState({
|
||||||
error: err.message
|
error: err.message,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
this.setState({ loading: false });
|
this.setState({ loading: false });
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
if (this.state.loading) return <Loader />
|
if (this.state.loading) return <Loader />;
|
||||||
return <div>
|
return (
|
||||||
|
<div>
|
||||||
<div class="floating group">
|
<div class="floating group">
|
||||||
<input onKeyDown={e => {
|
<input
|
||||||
|
onKeyDown={(e) => {
|
||||||
let k = e.keyCode | e.which;
|
let k = e.keyCode | e.which;
|
||||||
if (k === 13) this.onClick();
|
if (k === 13) this.onClick();
|
||||||
this.setState({ error: undefined })
|
this.setState({ error: undefined });
|
||||||
}} type="text" value={this.username_input ? this.username_input.value : this.props.username} autofocus ref={elm => elm ? this.username_input = elm : undefined} />
|
}}
|
||||||
|
type="text"
|
||||||
|
value={
|
||||||
|
this.username_input
|
||||||
|
? this.username_input.value
|
||||||
|
: this.props.username
|
||||||
|
}
|
||||||
|
autofocus
|
||||||
|
ref={(elm) => (elm ? (this.username_input = elm) : undefined)}
|
||||||
|
/>
|
||||||
<span class="highlight"></span>
|
<span class="highlight"></span>
|
||||||
<span class="bar"></span>
|
<span class="bar"></span>
|
||||||
<label>Username or Email</label>
|
<label>Username or Email</label>
|
||||||
{this.state.error ? <div class="error"> {this.state.error}</div> : undefined}
|
{this.state.error ? (
|
||||||
|
<div class="error"> {this.state.error}</div>
|
||||||
|
) : undefined}
|
||||||
</div>
|
</div>
|
||||||
<button type="button" class="mdc-button mdc-button--raised spanned-btn" onClick={() => this.onClick()}>Next</button>
|
<button
|
||||||
|
type="button"
|
||||||
|
class="mdc-button mdc-button--raised spanned-btn"
|
||||||
|
onClick={() => this.onClick()}
|
||||||
|
>
|
||||||
|
Next
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -70,7 +98,7 @@ enum TFATypes {
|
|||||||
OTC,
|
OTC,
|
||||||
BACKUP_CODE,
|
BACKUP_CODE,
|
||||||
YUBI_KEY,
|
YUBI_KEY,
|
||||||
APP_ALLOW
|
APP_ALLOW,
|
||||||
}
|
}
|
||||||
|
|
||||||
interface TwoFactors {
|
interface TwoFactors {
|
||||||
@ -79,38 +107,50 @@ interface TwoFactors {
|
|||||||
type: TFATypes;
|
type: TFATypes;
|
||||||
}
|
}
|
||||||
|
|
||||||
class Password extends Component<{ username: string, salt: string, onNext: (login: Token, special: Token, tfa: TwoFactors[]) => void }, { error: string, loading: boolean }> {
|
class Password extends Component<
|
||||||
|
{
|
||||||
|
username: string;
|
||||||
|
salt: string;
|
||||||
|
onNext: (login: Token, special: Token, tfa: TwoFactors[]) => void;
|
||||||
|
},
|
||||||
|
{ error: string; loading: boolean }
|
||||||
|
> {
|
||||||
password_input: HTMLInputElement;
|
password_input: HTMLInputElement;
|
||||||
constructor() {
|
constructor() {
|
||||||
super();
|
super();
|
||||||
this.state = { error: undefined, loading: false }
|
this.state = { error: undefined, loading: false };
|
||||||
}
|
}
|
||||||
|
|
||||||
async onClick() {
|
async onClick() {
|
||||||
this.setState({
|
this.setState({
|
||||||
loading: true
|
loading: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
try {
|
try {
|
||||||
let pw = sha(this.props.salt + this.password_input.value);
|
let pw = sha(this.props.salt + this.password_input.value);
|
||||||
let { login, special, tfa } = await fetch("/api/user/login?type=password", {
|
let { login, special, tfa } = await fetch(
|
||||||
|
"/api/user/login?type=password",
|
||||||
|
{
|
||||||
method: "POST",
|
method: "POST",
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
username: this.props.username,
|
username: this.props.username,
|
||||||
password: pw
|
password: pw,
|
||||||
}),
|
}),
|
||||||
headers: {
|
headers: {
|
||||||
'content-type': 'application/json'
|
"content-type": "application/json",
|
||||||
},
|
},
|
||||||
}).then(e => {
|
}
|
||||||
if (e.status !== 200) throw new Error(e.statusText)
|
)
|
||||||
return e.json()
|
.then((e) => {
|
||||||
}).then(data => {
|
if (e.status !== 200) throw new Error(e.statusText);
|
||||||
|
return e.json();
|
||||||
|
})
|
||||||
|
.then((data) => {
|
||||||
if (data.error) {
|
if (data.error) {
|
||||||
return Promise.reject(new Error(data.error))
|
return Promise.reject(new Error(data.error));
|
||||||
}
|
}
|
||||||
return data;
|
return data;
|
||||||
})
|
});
|
||||||
this.props.onNext(login, special, tfa);
|
this.props.onNext(login, special, tfa);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
this.setState({ error: err.messagae });
|
this.setState({ error: err.messagae });
|
||||||
@ -119,65 +159,85 @@ class Password extends Component<{ username: string, salt: string, onNext: (logi
|
|||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
if (this.state.loading) return <Loader />
|
if (this.state.loading) return <Loader />;
|
||||||
return <div>
|
return (
|
||||||
<div class="floating group" >
|
<div>
|
||||||
<input onKeyDown={e => {
|
<div class="floating group">
|
||||||
|
<input
|
||||||
|
onKeyDown={(e) => {
|
||||||
let k = e.keyCode | e.which;
|
let k = e.keyCode | e.which;
|
||||||
if (k === 13) this.onClick();
|
if (k === 13) this.onClick();
|
||||||
this.setState({ error: undefined })
|
this.setState({ error: undefined });
|
||||||
}} type="password" ref={(elm: HTMLInputElement) => {
|
}}
|
||||||
|
type="password"
|
||||||
|
ref={(elm: HTMLInputElement) => {
|
||||||
if (elm) {
|
if (elm) {
|
||||||
this.password_input = elm
|
this.password_input = elm;
|
||||||
setTimeout(() => elm.focus(), 200)
|
setTimeout(() => elm.focus(), 200);
|
||||||
// elm.focus();
|
// elm.focus();
|
||||||
}
|
}
|
||||||
}
|
}}
|
||||||
} />
|
/>
|
||||||
<span class="highlight"></span>
|
<span class="highlight"></span>
|
||||||
<span class="bar"></span>
|
<span class="bar"></span>
|
||||||
<label>Password</label>
|
<label>Password</label>
|
||||||
{this.state.error ? <div class="error"> {this.state.error}</div> : undefined}
|
{this.state.error ? (
|
||||||
|
<div class="error"> {this.state.error}</div>
|
||||||
|
) : undefined}
|
||||||
</div>
|
</div>
|
||||||
<button type="button" class="mdc-button mdc-button--raised spanned-btn" onClick={() => this.onClick()}>Login</button>
|
<button
|
||||||
|
type="button"
|
||||||
|
class="mdc-button mdc-button--raised spanned-btn"
|
||||||
|
onClick={() => this.onClick()}
|
||||||
|
>
|
||||||
|
Login
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class TwoFactor extends Component<{ twofactors: TwoFactors[], next: (id: string, type: TFATypes) => void }, {}> {
|
class TwoFactor extends Component<
|
||||||
|
{ twofactors: TwoFactors[]; next: (id: string, type: TFATypes) => void },
|
||||||
|
{}
|
||||||
|
> {
|
||||||
render() {
|
render() {
|
||||||
let tfs = this.props.twofactors.map(fac => {
|
let tfs = this.props.twofactors.map((fac) => {
|
||||||
let name: string;
|
let name: string;
|
||||||
switch (fac.type) {
|
switch (fac.type) {
|
||||||
case TFATypes.OTC:
|
case TFATypes.OTC:
|
||||||
name = "Authenticator"
|
name = "Authenticator";
|
||||||
break;
|
break;
|
||||||
case TFATypes.BACKUP_CODE:
|
case TFATypes.BACKUP_CODE:
|
||||||
name = "Backup code";
|
name = "Backup code";
|
||||||
break;
|
break;
|
||||||
case TFATypes.APP_ALLOW:
|
case TFATypes.APP_ALLOW:
|
||||||
name = "Use App: %s"
|
name = "Use App: %s";
|
||||||
break;
|
break;
|
||||||
case TFATypes.YUBI_KEY:
|
case TFATypes.YUBI_KEY:
|
||||||
name = "Use Yubikey: %s"
|
name = "Use Yubikey: %s";
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
name = name.replace("%s", fac.name ? fac.name : "");
|
name = name.replace("%s", fac.name ? fac.name : "");
|
||||||
|
|
||||||
return <li onClick={() => {
|
return (
|
||||||
console.log("Click on Solution")
|
<li
|
||||||
this.props.next(fac.id, fac.type)
|
onClick={() => {
|
||||||
}}>
|
console.log("Click on Solution");
|
||||||
|
this.props.next(fac.id, fac.type);
|
||||||
|
}}
|
||||||
|
>
|
||||||
{name}
|
{name}
|
||||||
</li>
|
</li>
|
||||||
})
|
);
|
||||||
return <div>
|
});
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
<h1>Select one</h1>
|
<h1>Select one</h1>
|
||||||
<ul>
|
<ul>{tfs}</ul>
|
||||||
{tfs}
|
|
||||||
</ul>
|
|
||||||
</div>
|
</div>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -191,7 +251,7 @@ enum Page {
|
|||||||
username,
|
username,
|
||||||
password,
|
password,
|
||||||
twofactor,
|
twofactor,
|
||||||
yubikey
|
yubikey,
|
||||||
}
|
}
|
||||||
|
|
||||||
interface Token {
|
interface Token {
|
||||||
@ -199,54 +259,80 @@ interface Token {
|
|||||||
expires: string;
|
expires: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function apiRequest(endpoint: string, method: "GET" | "POST" | "DELETE" | "PUT" = "GET", body: string = undefined) {
|
async function apiRequest(
|
||||||
|
endpoint: string,
|
||||||
|
method: "GET" | "POST" | "DELETE" | "PUT" = "GET",
|
||||||
|
body: string = undefined
|
||||||
|
) {
|
||||||
return fetch(endpoint, {
|
return fetch(endpoint, {
|
||||||
method,
|
method,
|
||||||
body,
|
body,
|
||||||
credentials: "same-origin",
|
credentials: "same-origin",
|
||||||
headers: {
|
headers: {
|
||||||
"content-type": "application/json"
|
"content-type": "application/json",
|
||||||
}
|
},
|
||||||
}).then(e => {
|
})
|
||||||
if (e.status !== 200) throw new Error(e.statusText)
|
.then((e) => {
|
||||||
return e.json()
|
if (e.status !== 200) throw new Error(e.statusText);
|
||||||
}).then(data => {
|
return e.json();
|
||||||
|
})
|
||||||
|
.then((data) => {
|
||||||
if (data.error) {
|
if (data.error) {
|
||||||
return Promise.reject(new Error(data.error))
|
return Promise.reject(new Error(data.error));
|
||||||
}
|
}
|
||||||
return data;
|
return data;
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class App extends Component<
|
||||||
class App extends Component<{}, { page: Page, username: string, salt: string, twofactor: TwoFactors[], twofactor_id: string }> {
|
{},
|
||||||
|
{
|
||||||
|
page: Page;
|
||||||
|
username: string;
|
||||||
|
salt: string;
|
||||||
|
twofactor: TwoFactors[];
|
||||||
|
twofactor_id: string;
|
||||||
|
}
|
||||||
|
> {
|
||||||
login: Token;
|
login: Token;
|
||||||
special: Token;
|
special: Token;
|
||||||
constructor() {
|
constructor() {
|
||||||
super();
|
super();
|
||||||
this.state = { page: Page.username, username: getCookie("username"), salt: undefined, twofactor: [], twofactor_id: null }
|
this.state = {
|
||||||
|
page: Page.username,
|
||||||
|
username: getCookie("username"),
|
||||||
|
salt: undefined,
|
||||||
|
twofactor: [],
|
||||||
|
twofactor_id: null,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
setCookies() {
|
setCookies() {
|
||||||
setCookie("login", this.login.token, new Date(this.login.expires).toUTCString());
|
setCookie(
|
||||||
setCookie("special", this.special.token, new Date(this.special.expires).toUTCString());
|
"login",
|
||||||
|
this.login.token,
|
||||||
|
new Date(this.login.expires).toUTCString()
|
||||||
|
);
|
||||||
|
setCookie(
|
||||||
|
"special",
|
||||||
|
this.special.token,
|
||||||
|
new Date(this.special.expires).toUTCString()
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
finish() {
|
finish() {
|
||||||
this.setCookies();
|
this.setCookies();
|
||||||
let d = new Date()
|
let d = new Date();
|
||||||
d.setTime(d.getTime() + (30 * 24 * 60 * 60 * 1000)); //Keep the username 30 days
|
d.setTime(d.getTime() + 30 * 24 * 60 * 60 * 1000); //Keep the username 30 days
|
||||||
setCookie("username", this.state.username, d.toUTCString());
|
setCookie("username", this.state.username, d.toUTCString());
|
||||||
let url = new URL(window.location.href);
|
let url = new URL(window.location.href);
|
||||||
let state = url.searchParams.get("state")
|
let state = url.searchParams.get("state");
|
||||||
let red = "/"
|
let red = "/";
|
||||||
|
|
||||||
if (state) {
|
if (state) {
|
||||||
let base64 = url.searchParams.get("base64")
|
let base64 = url.searchParams.get("base64");
|
||||||
if (base64)
|
if (base64) red = atob(state);
|
||||||
red = atob(state)
|
else red = state;
|
||||||
else
|
|
||||||
red = state
|
|
||||||
}
|
}
|
||||||
window.location.href = red;
|
window.location.href = red;
|
||||||
}
|
}
|
||||||
@ -255,13 +341,22 @@ class App extends Component<{}, { page: Page, username: string, salt: string, tw
|
|||||||
let cont;
|
let cont;
|
||||||
switch (this.state.page) {
|
switch (this.state.page) {
|
||||||
case Page.username:
|
case Page.username:
|
||||||
cont = <Username username={this.state.username} onNext={(username, salt) => {
|
cont = (
|
||||||
this.setState({ username, salt, page: Page.password })
|
<Username
|
||||||
|
username={this.state.username}
|
||||||
|
onNext={(username, salt) => {
|
||||||
|
this.setState({ username, salt, page: Page.password });
|
||||||
localStorage.setItem("username", username);
|
localStorage.setItem("username", username);
|
||||||
}} />
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
break;
|
break;
|
||||||
case Page.password:
|
case Page.password:
|
||||||
cont = <Password username={this.state.username} salt={this.state.salt} onNext={(login, special, twofactor) => {
|
cont = (
|
||||||
|
<Password
|
||||||
|
username={this.state.username}
|
||||||
|
salt={this.state.salt}
|
||||||
|
onNext={(login, special, twofactor) => {
|
||||||
this.login = login;
|
this.login = login;
|
||||||
this.special = special;
|
this.special = special;
|
||||||
this.setCookies();
|
this.setCookies();
|
||||||
@ -271,23 +366,42 @@ class App extends Component<{}, { page: Page, username: string, salt: string, tw
|
|||||||
} else {
|
} else {
|
||||||
this.setState({ twofactor, page: Page.twofactor });
|
this.setState({ twofactor, page: Page.twofactor });
|
||||||
}
|
}
|
||||||
}} />
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
break;
|
break;
|
||||||
case Page.twofactor:
|
case Page.twofactor:
|
||||||
cont = <TwoFactor twofactors={this.state.twofactor} next={async (id, type) => {
|
cont = (
|
||||||
|
<TwoFactor
|
||||||
|
twofactors={this.state.twofactor}
|
||||||
|
next={async (id, type) => {
|
||||||
if (type === TFATypes.YUBI_KEY) {
|
if (type === TFATypes.YUBI_KEY) {
|
||||||
let { request } = await apiRequest("/api/user/twofactor/yubikey", "GET");
|
let { request } = await apiRequest(
|
||||||
|
"/api/user/twofactor/yubikey",
|
||||||
|
"GET"
|
||||||
|
);
|
||||||
console.log(request);
|
console.log(request);
|
||||||
(window as any).u2f.sign(request.appId, [request.challenge], [request], async (response) => {
|
(window as any).u2f.sign(
|
||||||
let res = await apiRequest("/api/user/twofactor/yubikey", "PUT", JSON.stringify({ response }));
|
request.appId,
|
||||||
|
[request.challenge],
|
||||||
|
[request],
|
||||||
|
async (response) => {
|
||||||
|
let res = await apiRequest(
|
||||||
|
"/api/user/twofactor/yubikey",
|
||||||
|
"PUT",
|
||||||
|
JSON.stringify({ response })
|
||||||
|
);
|
||||||
if (res.success) {
|
if (res.success) {
|
||||||
this.login.expires = res.login_exp;
|
this.login.expires = res.login_exp;
|
||||||
this.special.expires = res.special_exp;
|
this.special.expires = res.special_exp;
|
||||||
this.finish();
|
this.finish();
|
||||||
}
|
}
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}} />
|
);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
break;
|
break;
|
||||||
// case Page.yubikey:
|
// case Page.yubikey:
|
||||||
// cont = <TFA_YubiKey id={this.state.twofactor_id} login={this.login} special={this.special} next={(login, special) => {
|
// cont = <TFA_YubiKey id={this.state.twofactor_id} login={this.login} special={this.special} next={(login, special) => {
|
||||||
@ -297,20 +411,24 @@ class App extends Component<{}, { page: Page, username: string, salt: string, tw
|
|||||||
// }} />
|
// }} />
|
||||||
// break;
|
// break;
|
||||||
}
|
}
|
||||||
return <div>
|
return (
|
||||||
|
<div>
|
||||||
<header>
|
<header>
|
||||||
<h1>Login</h1>
|
<h1>Login</h1>
|
||||||
</header>
|
</header>
|
||||||
<form action="JavaScript:void(0)">
|
<form action="JavaScript:void(0)">{cont}</form>
|
||||||
{cont}
|
|
||||||
</form>
|
|
||||||
<footer>
|
<footer>
|
||||||
<p>Powered by {appname}</p>
|
<p>Powered by {appname}</p>
|
||||||
</footer>
|
</footer>
|
||||||
</div>
|
</div>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
document.addEventListener('DOMContentLoaded', function () {
|
document.addEventListener(
|
||||||
render(<App />, document.body.querySelector("#content"))
|
"DOMContentLoaded",
|
||||||
}, false)
|
function () {
|
||||||
|
render(<App />, document.body.querySelector("#content"));
|
||||||
|
},
|
||||||
|
false
|
||||||
|
);
|
||||||
|
@ -12,13 +12,14 @@
|
|||||||
/**
|
/**
|
||||||
* @fileoverview The U2F api.
|
* @fileoverview The U2F api.
|
||||||
*/
|
*/
|
||||||
'use strict';
|
"use strict";
|
||||||
|
|
||||||
// NECESSARY CHANGE: wrap the whole file in a closure
|
// NECESSARY CHANGE: wrap the whole file in a closure
|
||||||
(function (){
|
(function () {
|
||||||
// NECESSARY CHANGE: detect UA to avoid clobbering other browser's U2F API.
|
// NECESSARY CHANGE: detect UA to avoid clobbering other browser's U2F API.
|
||||||
var isChrome = 'chrome' in window && window.navigator.userAgent.indexOf('Edge') < 0;
|
var isChrome =
|
||||||
if ('u2f' in window || !isChrome) {
|
"chrome" in window && window.navigator.userAgent.indexOf("Edge") < 0;
|
||||||
|
if ("u2f" in window || !isChrome) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -27,7 +28,7 @@
|
|||||||
* @type {Object}
|
* @type {Object}
|
||||||
*/
|
*/
|
||||||
// NECESSARY CHANGE: define the window.u2f API.
|
// NECESSARY CHANGE: define the window.u2f API.
|
||||||
var u2f = window.u2f = {};
|
var u2f = (window.u2f = {});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* FIDO U2F Javascript API Version
|
* FIDO U2F Javascript API Version
|
||||||
@ -42,43 +43,40 @@
|
|||||||
// The Chrome packaged app extension ID.
|
// The Chrome packaged app extension ID.
|
||||||
// Uncomment this if you want to deploy a server instance that uses
|
// Uncomment this if you want to deploy a server instance that uses
|
||||||
// the package Chrome app and does not require installing the U2F Chrome extension.
|
// the package Chrome app and does not require installing the U2F Chrome extension.
|
||||||
u2f.EXTENSION_ID = 'kmendfapggjehodndflmmgagdbamhnfd';
|
u2f.EXTENSION_ID = "kmendfapggjehodndflmmgagdbamhnfd";
|
||||||
// The U2F Chrome extension ID.
|
// The U2F Chrome extension ID.
|
||||||
// Uncomment this if you want to deploy a server instance that uses
|
// Uncomment this if you want to deploy a server instance that uses
|
||||||
// the U2F Chrome extension to authenticate.
|
// the U2F Chrome extension to authenticate.
|
||||||
// u2f.EXTENSION_ID = 'pfboblefjcgdjicmnffhdgionmgcdmne';
|
// u2f.EXTENSION_ID = 'pfboblefjcgdjicmnffhdgionmgcdmne';
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Message types for messsages to/from the extension
|
* Message types for messsages to/from the extension
|
||||||
* @const
|
* @const
|
||||||
* @enum {string}
|
* @enum {string}
|
||||||
*/
|
*/
|
||||||
u2f.MessageTypes = {
|
u2f.MessageTypes = {
|
||||||
'U2F_REGISTER_REQUEST': 'u2f_register_request',
|
U2F_REGISTER_REQUEST: "u2f_register_request",
|
||||||
'U2F_REGISTER_RESPONSE': 'u2f_register_response',
|
U2F_REGISTER_RESPONSE: "u2f_register_response",
|
||||||
'U2F_SIGN_REQUEST': 'u2f_sign_request',
|
U2F_SIGN_REQUEST: "u2f_sign_request",
|
||||||
'U2F_SIGN_RESPONSE': 'u2f_sign_response',
|
U2F_SIGN_RESPONSE: "u2f_sign_response",
|
||||||
'U2F_GET_API_VERSION_REQUEST': 'u2f_get_api_version_request',
|
U2F_GET_API_VERSION_REQUEST: "u2f_get_api_version_request",
|
||||||
'U2F_GET_API_VERSION_RESPONSE': 'u2f_get_api_version_response'
|
U2F_GET_API_VERSION_RESPONSE: "u2f_get_api_version_response",
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Response status codes
|
* Response status codes
|
||||||
* @const
|
* @const
|
||||||
* @enum {number}
|
* @enum {number}
|
||||||
*/
|
*/
|
||||||
u2f.ErrorCodes = {
|
u2f.ErrorCodes = {
|
||||||
'OK': 0,
|
OK: 0,
|
||||||
'OTHER_ERROR': 1,
|
OTHER_ERROR: 1,
|
||||||
'BAD_REQUEST': 2,
|
BAD_REQUEST: 2,
|
||||||
'CONFIGURATION_UNSUPPORTED': 3,
|
CONFIGURATION_UNSUPPORTED: 3,
|
||||||
'DEVICE_INELIGIBLE': 4,
|
DEVICE_INELIGIBLE: 4,
|
||||||
'TIMEOUT': 5
|
TIMEOUT: 5,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A message for registration requests
|
* A message for registration requests
|
||||||
* @typedef {{
|
* @typedef {{
|
||||||
@ -90,7 +88,6 @@
|
|||||||
*/
|
*/
|
||||||
u2f.U2fRequest;
|
u2f.U2fRequest;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A message for registration responses
|
* A message for registration responses
|
||||||
* @typedef {{
|
* @typedef {{
|
||||||
@ -101,7 +98,6 @@
|
|||||||
*/
|
*/
|
||||||
u2f.U2fResponse;
|
u2f.U2fResponse;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An error object for responses
|
* An error object for responses
|
||||||
* @typedef {{
|
* @typedef {{
|
||||||
@ -117,7 +113,6 @@
|
|||||||
*/
|
*/
|
||||||
u2f.Transport;
|
u2f.Transport;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Data object for a single sign request.
|
* Data object for a single sign request.
|
||||||
* @typedef {Array<u2f.Transport>}
|
* @typedef {Array<u2f.Transport>}
|
||||||
@ -135,7 +130,6 @@
|
|||||||
*/
|
*/
|
||||||
u2f.SignRequest;
|
u2f.SignRequest;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Data object for a sign response.
|
* Data object for a sign response.
|
||||||
* @typedef {{
|
* @typedef {{
|
||||||
@ -146,7 +140,6 @@
|
|||||||
*/
|
*/
|
||||||
u2f.SignResponse;
|
u2f.SignResponse;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Data object for a registration request.
|
* Data object for a registration request.
|
||||||
* @typedef {{
|
* @typedef {{
|
||||||
@ -156,7 +149,6 @@
|
|||||||
*/
|
*/
|
||||||
u2f.RegisterRequest;
|
u2f.RegisterRequest;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Data object for a registration response.
|
* Data object for a registration response.
|
||||||
* @typedef {{
|
* @typedef {{
|
||||||
@ -168,7 +160,6 @@
|
|||||||
*/
|
*/
|
||||||
u2f.RegisterResponse;
|
u2f.RegisterResponse;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Data object for a registered key.
|
* Data object for a registered key.
|
||||||
* @typedef {{
|
* @typedef {{
|
||||||
@ -180,7 +171,6 @@
|
|||||||
*/
|
*/
|
||||||
u2f.RegisteredKey;
|
u2f.RegisteredKey;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Data object for a get API register response.
|
* Data object for a get API register response.
|
||||||
* @typedef {{
|
* @typedef {{
|
||||||
@ -189,7 +179,6 @@
|
|||||||
*/
|
*/
|
||||||
u2f.GetJsApiVersionResponse;
|
u2f.GetJsApiVersionResponse;
|
||||||
|
|
||||||
|
|
||||||
//Low level MessagePort API support
|
//Low level MessagePort API support
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -197,16 +186,16 @@
|
|||||||
* available mechanisms.
|
* available mechanisms.
|
||||||
* @param {function((MessagePort|u2f.WrappedChromeRuntimePort_))} callback
|
* @param {function((MessagePort|u2f.WrappedChromeRuntimePort_))} callback
|
||||||
*/
|
*/
|
||||||
u2f.getMessagePort = function(callback) {
|
u2f.getMessagePort = function (callback) {
|
||||||
if (typeof chrome != 'undefined' && chrome.runtime) {
|
if (typeof chrome != "undefined" && chrome.runtime) {
|
||||||
// The actual message here does not matter, but we need to get a reply
|
// The actual message here does not matter, but we need to get a reply
|
||||||
// for the callback to run. Thus, send an empty signature request
|
// for the callback to run. Thus, send an empty signature request
|
||||||
// in order to get a failure response.
|
// in order to get a failure response.
|
||||||
var msg = {
|
var msg = {
|
||||||
type: u2f.MessageTypes.U2F_SIGN_REQUEST,
|
type: u2f.MessageTypes.U2F_SIGN_REQUEST,
|
||||||
signRequests: []
|
signRequests: [],
|
||||||
};
|
};
|
||||||
chrome.runtime.sendMessage(u2f.EXTENSION_ID, msg, function() {
|
chrome.runtime.sendMessage(u2f.EXTENSION_ID, msg, function () {
|
||||||
if (!chrome.runtime.lastError) {
|
if (!chrome.runtime.lastError) {
|
||||||
// We are on a whitelisted origin and can talk directly
|
// We are on a whitelisted origin and can talk directly
|
||||||
// with the extension.
|
// with the extension.
|
||||||
@ -232,17 +221,18 @@
|
|||||||
* Detect chrome running on android based on the browser's useragent.
|
* Detect chrome running on android based on the browser's useragent.
|
||||||
* @private
|
* @private
|
||||||
*/
|
*/
|
||||||
u2f.isAndroidChrome_ = function() {
|
u2f.isAndroidChrome_ = function () {
|
||||||
var userAgent = navigator.userAgent;
|
var userAgent = navigator.userAgent;
|
||||||
return userAgent.indexOf('Chrome') != -1 &&
|
return (
|
||||||
userAgent.indexOf('Android') != -1;
|
userAgent.indexOf("Chrome") != -1 && userAgent.indexOf("Android") != -1
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Detect chrome running on iOS based on the browser's platform.
|
* Detect chrome running on iOS based on the browser's platform.
|
||||||
* @private
|
* @private
|
||||||
*/
|
*/
|
||||||
u2f.isIosChrome_ = function() {
|
u2f.isIosChrome_ = function () {
|
||||||
return ["iPhone", "iPad", "iPod"].indexOf(navigator.platform) > -1;
|
return ["iPhone", "iPad", "iPod"].indexOf(navigator.platform) > -1;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -251,10 +241,11 @@
|
|||||||
* @param {function(u2f.WrappedChromeRuntimePort_)} callback
|
* @param {function(u2f.WrappedChromeRuntimePort_)} callback
|
||||||
* @private
|
* @private
|
||||||
*/
|
*/
|
||||||
u2f.getChromeRuntimePort_ = function(callback) {
|
u2f.getChromeRuntimePort_ = function (callback) {
|
||||||
var port = chrome.runtime.connect(u2f.EXTENSION_ID,
|
var port = chrome.runtime.connect(u2f.EXTENSION_ID, {
|
||||||
{'includeTlsChannelId': true});
|
includeTlsChannelId: true,
|
||||||
setTimeout(function() {
|
});
|
||||||
|
setTimeout(function () {
|
||||||
callback(new u2f.WrappedChromeRuntimePort_(port));
|
callback(new u2f.WrappedChromeRuntimePort_(port));
|
||||||
}, 0);
|
}, 0);
|
||||||
};
|
};
|
||||||
@ -264,8 +255,8 @@
|
|||||||
* @param {function(u2f.WrappedAuthenticatorPort_)} callback
|
* @param {function(u2f.WrappedAuthenticatorPort_)} callback
|
||||||
* @private
|
* @private
|
||||||
*/
|
*/
|
||||||
u2f.getAuthenticatorPort_ = function(callback) {
|
u2f.getAuthenticatorPort_ = function (callback) {
|
||||||
setTimeout(function() {
|
setTimeout(function () {
|
||||||
callback(new u2f.WrappedAuthenticatorPort_());
|
callback(new u2f.WrappedAuthenticatorPort_());
|
||||||
}, 0);
|
}, 0);
|
||||||
};
|
};
|
||||||
@ -275,8 +266,8 @@
|
|||||||
* @param {function(u2f.WrappedIosPort_)} callback
|
* @param {function(u2f.WrappedIosPort_)} callback
|
||||||
* @private
|
* @private
|
||||||
*/
|
*/
|
||||||
u2f.getIosPort_ = function(callback) {
|
u2f.getIosPort_ = function (callback) {
|
||||||
setTimeout(function() {
|
setTimeout(function () {
|
||||||
callback(new u2f.WrappedIosPort_());
|
callback(new u2f.WrappedIosPort_());
|
||||||
}, 0);
|
}, 0);
|
||||||
};
|
};
|
||||||
@ -287,7 +278,7 @@
|
|||||||
* @constructor
|
* @constructor
|
||||||
* @private
|
* @private
|
||||||
*/
|
*/
|
||||||
u2f.WrappedChromeRuntimePort_ = function(port) {
|
u2f.WrappedChromeRuntimePort_ = function (port) {
|
||||||
this.port_ = port;
|
this.port_ = port;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -298,8 +289,13 @@
|
|||||||
* @param {number} reqId
|
* @param {number} reqId
|
||||||
* @return {Object}
|
* @return {Object}
|
||||||
*/
|
*/
|
||||||
u2f.formatSignRequest_ =
|
u2f.formatSignRequest_ = function (
|
||||||
function(appId, challenge, registeredKeys, timeoutSeconds, reqId) {
|
appId,
|
||||||
|
challenge,
|
||||||
|
registeredKeys,
|
||||||
|
timeoutSeconds,
|
||||||
|
reqId
|
||||||
|
) {
|
||||||
if (js_api_version === undefined || js_api_version < 1.1) {
|
if (js_api_version === undefined || js_api_version < 1.1) {
|
||||||
// Adapt request to the 1.0 JS API
|
// Adapt request to the 1.0 JS API
|
||||||
var signRequests = [];
|
var signRequests = [];
|
||||||
@ -308,14 +304,14 @@
|
|||||||
version: registeredKeys[i].version,
|
version: registeredKeys[i].version,
|
||||||
challenge: challenge,
|
challenge: challenge,
|
||||||
keyHandle: registeredKeys[i].keyHandle,
|
keyHandle: registeredKeys[i].keyHandle,
|
||||||
appId: appId
|
appId: appId,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
type: u2f.MessageTypes.U2F_SIGN_REQUEST,
|
type: u2f.MessageTypes.U2F_SIGN_REQUEST,
|
||||||
signRequests: signRequests,
|
signRequests: signRequests,
|
||||||
timeoutSeconds: timeoutSeconds,
|
timeoutSeconds: timeoutSeconds,
|
||||||
requestId: reqId
|
requestId: reqId,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
// JS 1.1 API
|
// JS 1.1 API
|
||||||
@ -325,7 +321,7 @@
|
|||||||
challenge: challenge,
|
challenge: challenge,
|
||||||
registeredKeys: registeredKeys,
|
registeredKeys: registeredKeys,
|
||||||
timeoutSeconds: timeoutSeconds,
|
timeoutSeconds: timeoutSeconds,
|
||||||
requestId: reqId
|
requestId: reqId,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -337,8 +333,13 @@
|
|||||||
* @param {number} reqId
|
* @param {number} reqId
|
||||||
* @return {Object}
|
* @return {Object}
|
||||||
*/
|
*/
|
||||||
u2f.formatRegisterRequest_ =
|
u2f.formatRegisterRequest_ = function (
|
||||||
function(appId, registeredKeys, registerRequests, timeoutSeconds, reqId) {
|
appId,
|
||||||
|
registeredKeys,
|
||||||
|
registerRequests,
|
||||||
|
timeoutSeconds,
|
||||||
|
reqId
|
||||||
|
) {
|
||||||
if (js_api_version === undefined || js_api_version < 1.1) {
|
if (js_api_version === undefined || js_api_version < 1.1) {
|
||||||
// Adapt request to the 1.0 JS API
|
// Adapt request to the 1.0 JS API
|
||||||
for (var i = 0; i < registerRequests.length; i++) {
|
for (var i = 0; i < registerRequests.length; i++) {
|
||||||
@ -350,7 +351,7 @@
|
|||||||
version: registeredKeys[i].version,
|
version: registeredKeys[i].version,
|
||||||
challenge: registerRequests[0],
|
challenge: registerRequests[0],
|
||||||
keyHandle: registeredKeys[i].keyHandle,
|
keyHandle: registeredKeys[i].keyHandle,
|
||||||
appId: appId
|
appId: appId,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
@ -358,7 +359,7 @@
|
|||||||
signRequests: signRequests,
|
signRequests: signRequests,
|
||||||
registerRequests: registerRequests,
|
registerRequests: registerRequests,
|
||||||
timeoutSeconds: timeoutSeconds,
|
timeoutSeconds: timeoutSeconds,
|
||||||
requestId: reqId
|
requestId: reqId,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
// JS 1.1 API
|
// JS 1.1 API
|
||||||
@ -368,36 +369,36 @@
|
|||||||
registerRequests: registerRequests,
|
registerRequests: registerRequests,
|
||||||
registeredKeys: registeredKeys,
|
registeredKeys: registeredKeys,
|
||||||
timeoutSeconds: timeoutSeconds,
|
timeoutSeconds: timeoutSeconds,
|
||||||
requestId: reqId
|
requestId: reqId,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Posts a message on the underlying channel.
|
* Posts a message on the underlying channel.
|
||||||
* @param {Object} message
|
* @param {Object} message
|
||||||
*/
|
*/
|
||||||
u2f.WrappedChromeRuntimePort_.prototype.postMessage = function(message) {
|
u2f.WrappedChromeRuntimePort_.prototype.postMessage = function (message) {
|
||||||
this.port_.postMessage(message);
|
this.port_.postMessage(message);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Emulates the HTML 5 addEventListener interface. Works only for the
|
* Emulates the HTML 5 addEventListener interface. Works only for the
|
||||||
* onmessage event, which is hooked up to the chrome.runtime.Port.onMessage.
|
* onmessage event, which is hooked up to the chrome.runtime.Port.onMessage.
|
||||||
* @param {string} eventName
|
* @param {string} eventName
|
||||||
* @param {function({data: Object})} handler
|
* @param {function({data: Object})} handler
|
||||||
*/
|
*/
|
||||||
u2f.WrappedChromeRuntimePort_.prototype.addEventListener =
|
u2f.WrappedChromeRuntimePort_.prototype.addEventListener = function (
|
||||||
function(eventName, handler) {
|
eventName,
|
||||||
|
handler
|
||||||
|
) {
|
||||||
var name = eventName.toLowerCase();
|
var name = eventName.toLowerCase();
|
||||||
if (name == 'message' || name == 'onmessage') {
|
if (name == "message" || name == "onmessage") {
|
||||||
this.port_.onMessage.addListener(function(message) {
|
this.port_.onMessage.addListener(function (message) {
|
||||||
// Emulate a minimal MessageEvent object
|
// Emulate a minimal MessageEvent object
|
||||||
handler({'data': message});
|
handler({ data: message });
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
console.error('WrappedChromeRuntimePort only supports onMessage');
|
console.error("WrappedChromeRuntimePort only supports onMessage");
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -406,20 +407,21 @@
|
|||||||
* @constructor
|
* @constructor
|
||||||
* @private
|
* @private
|
||||||
*/
|
*/
|
||||||
u2f.WrappedAuthenticatorPort_ = function() {
|
u2f.WrappedAuthenticatorPort_ = function () {
|
||||||
this.requestId_ = -1;
|
this.requestId_ = -1;
|
||||||
this.requestObject_ = null;
|
this.requestObject_ = null;
|
||||||
}
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Launch the Authenticator intent.
|
* Launch the Authenticator intent.
|
||||||
* @param {Object} message
|
* @param {Object} message
|
||||||
*/
|
*/
|
||||||
u2f.WrappedAuthenticatorPort_.prototype.postMessage = function(message) {
|
u2f.WrappedAuthenticatorPort_.prototype.postMessage = function (message) {
|
||||||
var intentUrl =
|
var intentUrl =
|
||||||
u2f.WrappedAuthenticatorPort_.INTENT_URL_BASE_ +
|
u2f.WrappedAuthenticatorPort_.INTENT_URL_BASE_ +
|
||||||
';S.request=' + encodeURIComponent(JSON.stringify(message)) +
|
";S.request=" +
|
||||||
';end';
|
encodeURIComponent(JSON.stringify(message)) +
|
||||||
|
";end";
|
||||||
document.location = intentUrl;
|
document.location = intentUrl;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -427,26 +429,31 @@
|
|||||||
* Tells what type of port this is.
|
* Tells what type of port this is.
|
||||||
* @return {String} port type
|
* @return {String} port type
|
||||||
*/
|
*/
|
||||||
u2f.WrappedAuthenticatorPort_.prototype.getPortType = function() {
|
u2f.WrappedAuthenticatorPort_.prototype.getPortType = function () {
|
||||||
return "WrappedAuthenticatorPort_";
|
return "WrappedAuthenticatorPort_";
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Emulates the HTML 5 addEventListener interface.
|
* Emulates the HTML 5 addEventListener interface.
|
||||||
* @param {string} eventName
|
* @param {string} eventName
|
||||||
* @param {function({data: Object})} handler
|
* @param {function({data: Object})} handler
|
||||||
*/
|
*/
|
||||||
u2f.WrappedAuthenticatorPort_.prototype.addEventListener = function(eventName, handler) {
|
u2f.WrappedAuthenticatorPort_.prototype.addEventListener = function (
|
||||||
|
eventName,
|
||||||
|
handler
|
||||||
|
) {
|
||||||
var name = eventName.toLowerCase();
|
var name = eventName.toLowerCase();
|
||||||
if (name == 'message') {
|
if (name == "message") {
|
||||||
var self = this;
|
var self = this;
|
||||||
/* Register a callback to that executes when
|
/* Register a callback to that executes when
|
||||||
* chrome injects the response. */
|
* chrome injects the response. */
|
||||||
window.addEventListener(
|
window.addEventListener(
|
||||||
'message', self.onRequestUpdate_.bind(self, handler), false);
|
"message",
|
||||||
|
self.onRequestUpdate_.bind(self, handler),
|
||||||
|
false
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
console.error('WrappedAuthenticatorPort only supports message');
|
console.error("WrappedAuthenticatorPort only supports message");
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -455,19 +462,22 @@
|
|||||||
* @param function({data: Object}) callback
|
* @param function({data: Object}) callback
|
||||||
* @param {Object} message message Object
|
* @param {Object} message message Object
|
||||||
*/
|
*/
|
||||||
u2f.WrappedAuthenticatorPort_.prototype.onRequestUpdate_ =
|
u2f.WrappedAuthenticatorPort_.prototype.onRequestUpdate_ = function (
|
||||||
function(callback, message) {
|
callback,
|
||||||
|
message
|
||||||
|
) {
|
||||||
var messageObject = JSON.parse(message.data);
|
var messageObject = JSON.parse(message.data);
|
||||||
var intentUrl = messageObject['intentURL'];
|
var intentUrl = messageObject["intentURL"];
|
||||||
|
|
||||||
var errorCode = messageObject['errorCode'];
|
var errorCode = messageObject["errorCode"];
|
||||||
var responseObject = null;
|
var responseObject = null;
|
||||||
if (messageObject.hasOwnProperty('data')) {
|
if (messageObject.hasOwnProperty("data")) {
|
||||||
responseObject = /** @type {Object} */ (
|
responseObject = /** @type {Object} */ (JSON.parse(
|
||||||
JSON.parse(messageObject['data']));
|
messageObject["data"]
|
||||||
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
callback({'data': responseObject});
|
callback({ data: responseObject });
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -476,20 +486,20 @@
|
|||||||
* @private
|
* @private
|
||||||
*/
|
*/
|
||||||
u2f.WrappedAuthenticatorPort_.INTENT_URL_BASE_ =
|
u2f.WrappedAuthenticatorPort_.INTENT_URL_BASE_ =
|
||||||
'intent:#Intent;action=com.google.android.apps.authenticator.AUTHENTICATE';
|
"intent:#Intent;action=com.google.android.apps.authenticator.AUTHENTICATE";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Wrap the iOS client app with a MessagePort interface.
|
* Wrap the iOS client app with a MessagePort interface.
|
||||||
* @constructor
|
* @constructor
|
||||||
* @private
|
* @private
|
||||||
*/
|
*/
|
||||||
u2f.WrappedIosPort_ = function() {};
|
u2f.WrappedIosPort_ = function () {};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Launch the iOS client app request
|
* Launch the iOS client app request
|
||||||
* @param {Object} message
|
* @param {Object} message
|
||||||
*/
|
*/
|
||||||
u2f.WrappedIosPort_.prototype.postMessage = function(message) {
|
u2f.WrappedIosPort_.prototype.postMessage = function (message) {
|
||||||
var str = JSON.stringify(message);
|
var str = JSON.stringify(message);
|
||||||
var url = "u2f://auth?" + encodeURI(str);
|
var url = "u2f://auth?" + encodeURI(str);
|
||||||
location.replace(url);
|
location.replace(url);
|
||||||
@ -499,7 +509,7 @@
|
|||||||
* Tells what type of port this is.
|
* Tells what type of port this is.
|
||||||
* @return {String} port type
|
* @return {String} port type
|
||||||
*/
|
*/
|
||||||
u2f.WrappedIosPort_.prototype.getPortType = function() {
|
u2f.WrappedIosPort_.prototype.getPortType = function () {
|
||||||
return "WrappedIosPort_";
|
return "WrappedIosPort_";
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -508,10 +518,13 @@
|
|||||||
* @param {string} eventName
|
* @param {string} eventName
|
||||||
* @param {function({data: Object})} handler
|
* @param {function({data: Object})} handler
|
||||||
*/
|
*/
|
||||||
u2f.WrappedIosPort_.prototype.addEventListener = function(eventName, handler) {
|
u2f.WrappedIosPort_.prototype.addEventListener = function (
|
||||||
|
eventName,
|
||||||
|
handler
|
||||||
|
) {
|
||||||
var name = eventName.toLowerCase();
|
var name = eventName.toLowerCase();
|
||||||
if (name !== 'message') {
|
if (name !== "message") {
|
||||||
console.error('WrappedIosPort only supports message');
|
console.error("WrappedIosPort only supports message");
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -520,33 +533,34 @@
|
|||||||
* @param {function(MessagePort)} callback
|
* @param {function(MessagePort)} callback
|
||||||
* @private
|
* @private
|
||||||
*/
|
*/
|
||||||
u2f.getIframePort_ = function(callback) {
|
u2f.getIframePort_ = function (callback) {
|
||||||
// Create the iframe
|
// Create the iframe
|
||||||
var iframeOrigin = 'chrome-extension://' + u2f.EXTENSION_ID;
|
var iframeOrigin = "chrome-extension://" + u2f.EXTENSION_ID;
|
||||||
var iframe = document.createElement('iframe');
|
var iframe = document.createElement("iframe");
|
||||||
iframe.src = iframeOrigin + '/u2f-comms.html';
|
iframe.src = iframeOrigin + "/u2f-comms.html";
|
||||||
iframe.setAttribute('style', 'display:none');
|
iframe.setAttribute("style", "display:none");
|
||||||
document.body.appendChild(iframe);
|
document.body.appendChild(iframe);
|
||||||
|
|
||||||
var channel = new MessageChannel();
|
var channel = new MessageChannel();
|
||||||
var ready = function(message) {
|
var ready = function (message) {
|
||||||
if (message.data == 'ready') {
|
if (message.data == "ready") {
|
||||||
channel.port1.removeEventListener('message', ready);
|
channel.port1.removeEventListener("message", ready);
|
||||||
callback(channel.port1);
|
callback(channel.port1);
|
||||||
} else {
|
} else {
|
||||||
console.error('First event on iframe port was not "ready"');
|
console.error('First event on iframe port was not "ready"');
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
channel.port1.addEventListener('message', ready);
|
channel.port1.addEventListener("message", ready);
|
||||||
channel.port1.start();
|
channel.port1.start();
|
||||||
|
|
||||||
iframe.addEventListener('load', function() {
|
iframe.addEventListener("load", function () {
|
||||||
// Deliver the port to the iframe and initialize
|
// Deliver the port to the iframe and initialize
|
||||||
iframe.contentWindow.postMessage('init', iframeOrigin, [channel.port2]);
|
iframe.contentWindow.postMessage("init", iframeOrigin, [
|
||||||
|
channel.port2,
|
||||||
|
]);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
//High-level JS API
|
//High-level JS API
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -589,15 +603,17 @@
|
|||||||
* @param {function((MessagePort|u2f.WrappedChromeRuntimePort_))} callback
|
* @param {function((MessagePort|u2f.WrappedChromeRuntimePort_))} callback
|
||||||
* @private
|
* @private
|
||||||
*/
|
*/
|
||||||
u2f.getPortSingleton_ = function(callback) {
|
u2f.getPortSingleton_ = function (callback) {
|
||||||
if (u2f.port_) {
|
if (u2f.port_) {
|
||||||
callback(u2f.port_);
|
callback(u2f.port_);
|
||||||
} else {
|
} else {
|
||||||
if (u2f.waitingForPort_.length == 0) {
|
if (u2f.waitingForPort_.length == 0) {
|
||||||
u2f.getMessagePort(function(port) {
|
u2f.getMessagePort(function (port) {
|
||||||
u2f.port_ = port;
|
u2f.port_ = port;
|
||||||
u2f.port_.addEventListener('message',
|
u2f.port_.addEventListener(
|
||||||
/** @type {function(Event)} */ (u2f.responseHandler_));
|
"message",
|
||||||
|
/** @type {function(Event)} */ (u2f.responseHandler_)
|
||||||
|
);
|
||||||
|
|
||||||
// Careful, here be async callbacks. Maybe.
|
// Careful, here be async callbacks. Maybe.
|
||||||
while (u2f.waitingForPort_.length)
|
while (u2f.waitingForPort_.length)
|
||||||
@ -613,16 +629,16 @@
|
|||||||
* @param {MessageEvent.<u2f.Response>} message
|
* @param {MessageEvent.<u2f.Response>} message
|
||||||
* @private
|
* @private
|
||||||
*/
|
*/
|
||||||
u2f.responseHandler_ = function(message) {
|
u2f.responseHandler_ = function (message) {
|
||||||
var response = message.data;
|
var response = message.data;
|
||||||
var reqId = response['requestId'];
|
var reqId = response["requestId"];
|
||||||
if (!reqId || !u2f.callbackMap_[reqId]) {
|
if (!reqId || !u2f.callbackMap_[reqId]) {
|
||||||
console.error('Unknown or missing requestId in response.');
|
console.error("Unknown or missing requestId in response.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
var cb = u2f.callbackMap_[reqId];
|
var cb = u2f.callbackMap_[reqId];
|
||||||
delete u2f.callbackMap_[reqId];
|
delete u2f.callbackMap_[reqId];
|
||||||
cb(response['responseData']);
|
cb(response["responseData"]);
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -636,18 +652,38 @@
|
|||||||
* @param {function((u2f.Error|u2f.SignResponse))} callback
|
* @param {function((u2f.Error|u2f.SignResponse))} callback
|
||||||
* @param {number=} opt_timeoutSeconds
|
* @param {number=} opt_timeoutSeconds
|
||||||
*/
|
*/
|
||||||
u2f.sign = function(appId, challenge, registeredKeys, callback, opt_timeoutSeconds) {
|
u2f.sign = function (
|
||||||
|
appId,
|
||||||
|
challenge,
|
||||||
|
registeredKeys,
|
||||||
|
callback,
|
||||||
|
opt_timeoutSeconds
|
||||||
|
) {
|
||||||
if (js_api_version === undefined) {
|
if (js_api_version === undefined) {
|
||||||
// Send a message to get the extension to JS API version, then send the actual sign request.
|
// Send a message to get the extension to JS API version, then send the actual sign request.
|
||||||
u2f.getApiVersion(
|
u2f.getApiVersion(function (response) {
|
||||||
function (response) {
|
js_api_version =
|
||||||
js_api_version = response['js_api_version'] === undefined ? 0 : response['js_api_version'];
|
response["js_api_version"] === undefined
|
||||||
|
? 0
|
||||||
|
: response["js_api_version"];
|
||||||
console.log("Extension JS API Version: ", js_api_version);
|
console.log("Extension JS API Version: ", js_api_version);
|
||||||
u2f.sendSignRequest(appId, challenge, registeredKeys, callback, opt_timeoutSeconds);
|
u2f.sendSignRequest(
|
||||||
|
appId,
|
||||||
|
challenge,
|
||||||
|
registeredKeys,
|
||||||
|
callback,
|
||||||
|
opt_timeoutSeconds
|
||||||
|
);
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
// We know the JS API version. Send the actual sign request in the supported API version.
|
// We know the JS API version. Send the actual sign request in the supported API version.
|
||||||
u2f.sendSignRequest(appId, challenge, registeredKeys, callback, opt_timeoutSeconds);
|
u2f.sendSignRequest(
|
||||||
|
appId,
|
||||||
|
challenge,
|
||||||
|
registeredKeys,
|
||||||
|
callback,
|
||||||
|
opt_timeoutSeconds
|
||||||
|
);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -659,13 +695,27 @@
|
|||||||
* @param {function((u2f.Error|u2f.SignResponse))} callback
|
* @param {function((u2f.Error|u2f.SignResponse))} callback
|
||||||
* @param {number=} opt_timeoutSeconds
|
* @param {number=} opt_timeoutSeconds
|
||||||
*/
|
*/
|
||||||
u2f.sendSignRequest = function(appId, challenge, registeredKeys, callback, opt_timeoutSeconds) {
|
u2f.sendSignRequest = function (
|
||||||
u2f.getPortSingleton_(function(port) {
|
appId,
|
||||||
|
challenge,
|
||||||
|
registeredKeys,
|
||||||
|
callback,
|
||||||
|
opt_timeoutSeconds
|
||||||
|
) {
|
||||||
|
u2f.getPortSingleton_(function (port) {
|
||||||
var reqId = ++u2f.reqCounter_;
|
var reqId = ++u2f.reqCounter_;
|
||||||
u2f.callbackMap_[reqId] = callback;
|
u2f.callbackMap_[reqId] = callback;
|
||||||
var timeoutSeconds = (typeof opt_timeoutSeconds !== 'undefined' ?
|
var timeoutSeconds =
|
||||||
opt_timeoutSeconds : u2f.EXTENSION_TIMEOUT_SEC);
|
typeof opt_timeoutSeconds !== "undefined"
|
||||||
var req = u2f.formatSignRequest_(appId, challenge, registeredKeys, timeoutSeconds, reqId);
|
? opt_timeoutSeconds
|
||||||
|
: u2f.EXTENSION_TIMEOUT_SEC;
|
||||||
|
var req = u2f.formatSignRequest_(
|
||||||
|
appId,
|
||||||
|
challenge,
|
||||||
|
registeredKeys,
|
||||||
|
timeoutSeconds,
|
||||||
|
reqId
|
||||||
|
);
|
||||||
port.postMessage(req);
|
port.postMessage(req);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
@ -682,20 +732,38 @@
|
|||||||
* @param {function((u2f.Error|u2f.RegisterResponse))} callback
|
* @param {function((u2f.Error|u2f.RegisterResponse))} callback
|
||||||
* @param {number=} opt_timeoutSeconds
|
* @param {number=} opt_timeoutSeconds
|
||||||
*/
|
*/
|
||||||
u2f.register = function(appId, registerRequests, registeredKeys, callback, opt_timeoutSeconds) {
|
u2f.register = function (
|
||||||
|
appId,
|
||||||
|
registerRequests,
|
||||||
|
registeredKeys,
|
||||||
|
callback,
|
||||||
|
opt_timeoutSeconds
|
||||||
|
) {
|
||||||
if (js_api_version === undefined) {
|
if (js_api_version === undefined) {
|
||||||
// Send a message to get the extension to JS API version, then send the actual register request.
|
// Send a message to get the extension to JS API version, then send the actual register request.
|
||||||
u2f.getApiVersion(
|
u2f.getApiVersion(function (response) {
|
||||||
function (response) {
|
js_api_version =
|
||||||
js_api_version = response['js_api_version'] === undefined ? 0: response['js_api_version'];
|
response["js_api_version"] === undefined
|
||||||
|
? 0
|
||||||
|
: response["js_api_version"];
|
||||||
console.log("Extension JS API Version: ", js_api_version);
|
console.log("Extension JS API Version: ", js_api_version);
|
||||||
u2f.sendRegisterRequest(appId, registerRequests, registeredKeys,
|
u2f.sendRegisterRequest(
|
||||||
callback, opt_timeoutSeconds);
|
appId,
|
||||||
|
registerRequests,
|
||||||
|
registeredKeys,
|
||||||
|
callback,
|
||||||
|
opt_timeoutSeconds
|
||||||
|
);
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
// We know the JS API version. Send the actual register request in the supported API version.
|
// We know the JS API version. Send the actual register request in the supported API version.
|
||||||
u2f.sendRegisterRequest(appId, registerRequests, registeredKeys,
|
u2f.sendRegisterRequest(
|
||||||
callback, opt_timeoutSeconds);
|
appId,
|
||||||
|
registerRequests,
|
||||||
|
registeredKeys,
|
||||||
|
callback,
|
||||||
|
opt_timeoutSeconds
|
||||||
|
);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -708,19 +776,31 @@
|
|||||||
* @param {function((u2f.Error|u2f.RegisterResponse))} callback
|
* @param {function((u2f.Error|u2f.RegisterResponse))} callback
|
||||||
* @param {number=} opt_timeoutSeconds
|
* @param {number=} opt_timeoutSeconds
|
||||||
*/
|
*/
|
||||||
u2f.sendRegisterRequest = function(appId, registerRequests, registeredKeys, callback, opt_timeoutSeconds) {
|
u2f.sendRegisterRequest = function (
|
||||||
u2f.getPortSingleton_(function(port) {
|
appId,
|
||||||
|
registerRequests,
|
||||||
|
registeredKeys,
|
||||||
|
callback,
|
||||||
|
opt_timeoutSeconds
|
||||||
|
) {
|
||||||
|
u2f.getPortSingleton_(function (port) {
|
||||||
var reqId = ++u2f.reqCounter_;
|
var reqId = ++u2f.reqCounter_;
|
||||||
u2f.callbackMap_[reqId] = callback;
|
u2f.callbackMap_[reqId] = callback;
|
||||||
var timeoutSeconds = (typeof opt_timeoutSeconds !== 'undefined' ?
|
var timeoutSeconds =
|
||||||
opt_timeoutSeconds : u2f.EXTENSION_TIMEOUT_SEC);
|
typeof opt_timeoutSeconds !== "undefined"
|
||||||
|
? opt_timeoutSeconds
|
||||||
|
: u2f.EXTENSION_TIMEOUT_SEC;
|
||||||
var req = u2f.formatRegisterRequest_(
|
var req = u2f.formatRegisterRequest_(
|
||||||
appId, registeredKeys, registerRequests, timeoutSeconds, reqId);
|
appId,
|
||||||
|
registeredKeys,
|
||||||
|
registerRequests,
|
||||||
|
timeoutSeconds,
|
||||||
|
reqId
|
||||||
|
);
|
||||||
port.postMessage(req);
|
port.postMessage(req);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Dispatches a message to the extension to find out the supported
|
* Dispatches a message to the extension to find out the supported
|
||||||
* JS API version.
|
* JS API version.
|
||||||
@ -729,15 +809,15 @@
|
|||||||
* @param {function((u2f.Error|u2f.GetJsApiVersionResponse))} callback
|
* @param {function((u2f.Error|u2f.GetJsApiVersionResponse))} callback
|
||||||
* @param {number=} opt_timeoutSeconds
|
* @param {number=} opt_timeoutSeconds
|
||||||
*/
|
*/
|
||||||
u2f.getApiVersion = function(callback, opt_timeoutSeconds) {
|
u2f.getApiVersion = function (callback, opt_timeoutSeconds) {
|
||||||
u2f.getPortSingleton_(function(port) {
|
u2f.getPortSingleton_(function (port) {
|
||||||
// If we are using Android Google Authenticator or iOS client app,
|
// If we are using Android Google Authenticator or iOS client app,
|
||||||
// do not fire an intent to ask which JS API version to use.
|
// do not fire an intent to ask which JS API version to use.
|
||||||
if (port.getPortType) {
|
if (port.getPortType) {
|
||||||
var apiVersion;
|
var apiVersion;
|
||||||
switch (port.getPortType()) {
|
switch (port.getPortType()) {
|
||||||
case 'WrappedIosPort_':
|
case "WrappedIosPort_":
|
||||||
case 'WrappedAuthenticatorPort_':
|
case "WrappedAuthenticatorPort_":
|
||||||
apiVersion = 1.1;
|
apiVersion = 1.1;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
@ -745,16 +825,18 @@
|
|||||||
apiVersion = 0;
|
apiVersion = 0;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
callback({ 'js_api_version': apiVersion });
|
callback({ js_api_version: apiVersion });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
var reqId = ++u2f.reqCounter_;
|
var reqId = ++u2f.reqCounter_;
|
||||||
u2f.callbackMap_[reqId] = callback;
|
u2f.callbackMap_[reqId] = callback;
|
||||||
var req = {
|
var req = {
|
||||||
type: u2f.MessageTypes.U2F_GET_API_VERSION_REQUEST,
|
type: u2f.MessageTypes.U2F_GET_API_VERSION_REQUEST,
|
||||||
timeoutSeconds: (typeof opt_timeoutSeconds !== 'undefined' ?
|
timeoutSeconds:
|
||||||
opt_timeoutSeconds : u2f.EXTENSION_TIMEOUT_SEC),
|
typeof opt_timeoutSeconds !== "undefined"
|
||||||
requestId: reqId
|
? opt_timeoutSeconds
|
||||||
|
: u2f.EXTENSION_TIMEOUT_SEC,
|
||||||
|
requestId: reqId,
|
||||||
};
|
};
|
||||||
port.postMessage(req);
|
port.postMessage(req);
|
||||||
});
|
});
|
||||||
|
@ -1 +1 @@
|
|||||||
console.log("Hello World")
|
console.log("Hello World");
|
||||||
|
@ -1,23 +1,25 @@
|
|||||||
import "inputs";
|
import "inputs";
|
||||||
import sha from "sha512";
|
import sha from "sha512";
|
||||||
import fireEvent from "event"
|
import fireEvent from "event";
|
||||||
|
|
||||||
(() => {
|
(() => {
|
||||||
const translations = JSON.parse(document.getElementById("error_codes").innerText)
|
const translations = JSON.parse(
|
||||||
|
document.getElementById("error_codes").innerText
|
||||||
|
);
|
||||||
|
|
||||||
const regcode = document.getElementById("regcode")
|
const regcode = document.getElementById("regcode");
|
||||||
regcode.value = new URL(window.location.href).searchParams.get("regcode")
|
regcode.value = new URL(window.location.href).searchParams.get("regcode");
|
||||||
fireEvent(regcode, "change");
|
fireEvent(regcode, "change");
|
||||||
|
|
||||||
function showError(element, message) {
|
function showError(element, message) {
|
||||||
if (typeof element === "string")
|
if (typeof element === "string")
|
||||||
element = document.getElementById(element)
|
element = document.getElementById(element);
|
||||||
|
|
||||||
if (!element) console.error("Element not found,", element)
|
if (!element) console.error("Element not found,", element);
|
||||||
element.innerText = message;
|
element.innerText = message;
|
||||||
if (!message) {
|
if (!message) {
|
||||||
if (!element.classList.contains("invisible"))
|
if (!element.classList.contains("invisible"))
|
||||||
element.classList.add("invisible")
|
element.classList.add("invisible");
|
||||||
} else {
|
} else {
|
||||||
element.classList.remove("invisible");
|
element.classList.remove("invisible");
|
||||||
}
|
}
|
||||||
@ -25,7 +27,8 @@ import fireEvent from "event"
|
|||||||
|
|
||||||
function makeid(length) {
|
function makeid(length) {
|
||||||
var text = "";
|
var text = "";
|
||||||
var possible = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
|
var possible =
|
||||||
|
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
|
||||||
|
|
||||||
for (var i = 0; i < length; i++)
|
for (var i = 0; i < length; i++)
|
||||||
text += possible.charAt(Math.floor(Math.random() * possible.length));
|
text += possible.charAt(Math.floor(Math.random() * possible.length));
|
||||||
@ -33,61 +36,61 @@ import fireEvent from "event"
|
|||||||
return text;
|
return text;
|
||||||
}
|
}
|
||||||
|
|
||||||
const username = document.getElementById("username")
|
const username = document.getElementById("username");
|
||||||
const name = document.getElementById("name")
|
const name = document.getElementById("name");
|
||||||
const mail = document.getElementById("mail")
|
const mail = document.getElementById("mail");
|
||||||
const password = document.getElementById("password")
|
const password = document.getElementById("password");
|
||||||
const passwordrep = document.getElementById("passwordrep")
|
const passwordrep = document.getElementById("passwordrep");
|
||||||
|
|
||||||
const radio_male = document.getElementById("radio-male")
|
const radio_male = document.getElementById("radio-male");
|
||||||
const radio_female = document.getElementById("radio-female")
|
const radio_female = document.getElementById("radio-female");
|
||||||
const radio_other = document.getElementById("radio-other")
|
const radio_other = document.getElementById("radio-other");
|
||||||
|
|
||||||
const registerButton = document.getElementById("registerbutton")
|
const registerButton = document.getElementById("registerbutton");
|
||||||
registerButton.onclick = () => {
|
registerButton.onclick = () => {
|
||||||
console.log("Register")
|
console.log("Register");
|
||||||
showError("error");
|
showError("error");
|
||||||
let error = false;
|
let error = false;
|
||||||
if (!regcode.value) {
|
if (!regcode.value) {
|
||||||
showError("err_regcode", translations["noregcode"])
|
showError("err_regcode", translations["noregcode"]);
|
||||||
error = true;
|
error = true;
|
||||||
} else {
|
} else {
|
||||||
showError("err_regcode")
|
showError("err_regcode");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!username.value) {
|
if (!username.value) {
|
||||||
showError("err_username", translations["nousername"])
|
showError("err_username", translations["nousername"]);
|
||||||
error = true;
|
error = true;
|
||||||
} else {
|
} else {
|
||||||
showError("err_username")
|
showError("err_username");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!name.value) {
|
if (!name.value) {
|
||||||
showError("err_name", translations["noname"])
|
showError("err_name", translations["noname"]);
|
||||||
error = true;
|
error = true;
|
||||||
} else {
|
} else {
|
||||||
showError("err_name")
|
showError("err_name");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!mail.value) {
|
if (!mail.value) {
|
||||||
showError("err_mail", translations["nomail"])
|
showError("err_mail", translations["nomail"]);
|
||||||
error = true;
|
error = true;
|
||||||
} else {
|
} else {
|
||||||
showError("err_mail")
|
showError("err_mail");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!password.value) {
|
if (!password.value) {
|
||||||
showError("err_password", translations["nopassword"])
|
showError("err_password", translations["nopassword"]);
|
||||||
error = true;
|
error = true;
|
||||||
} else {
|
} else {
|
||||||
showError("err_password")
|
showError("err_password");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (password.value !== passwordrep.value) {
|
if (password.value !== passwordrep.value) {
|
||||||
showError("err_passwordrep", translations["nomatch"])
|
showError("err_passwordrep", translations["nomatch"]);
|
||||||
error = true;
|
error = true;
|
||||||
} else {
|
} else {
|
||||||
showError("err_passwordrep")
|
showError("err_passwordrep");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (error) return;
|
if (error) return;
|
||||||
@ -95,14 +98,14 @@ import fireEvent from "event"
|
|||||||
let gender;
|
let gender;
|
||||||
|
|
||||||
if (radio_male.checked) {
|
if (radio_male.checked) {
|
||||||
gender = "male"
|
gender = "male";
|
||||||
} else if (radio_female.checked) {
|
} else if (radio_female.checked) {
|
||||||
gender = "female"
|
gender = "female";
|
||||||
} else {
|
} else {
|
||||||
gender = "other"
|
gender = "other";
|
||||||
}
|
}
|
||||||
|
|
||||||
let salt = makeid(10)
|
let salt = makeid(10);
|
||||||
|
|
||||||
//username, password, salt, mail, gender, name, birthday, regcode
|
//username, password, salt, mail, gender, name, birthday, regcode
|
||||||
|
|
||||||
@ -113,37 +116,44 @@ import fireEvent from "event"
|
|||||||
name: name.value,
|
name: name.value,
|
||||||
regcode: regcode.value,
|
regcode: regcode.value,
|
||||||
salt: salt,
|
salt: salt,
|
||||||
password: sha(salt + password.value)
|
password: sha(salt + password.value),
|
||||||
}
|
};
|
||||||
|
|
||||||
fetch("/api/user/register", {
|
fetch("/api/user/register", {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
body: JSON.stringify(body),
|
body: JSON.stringify(body),
|
||||||
headers: {
|
headers: {
|
||||||
'content-type': 'application/json'
|
"content-type": "application/json",
|
||||||
},
|
},
|
||||||
}).then(async e => {
|
})
|
||||||
if (e.status !== 200) return Promise.reject(new Error(await e.text() || e.statusText));
|
.then(async (e) => {
|
||||||
return e.json()
|
if (e.status !== 200)
|
||||||
}).then(data => {
|
return Promise.reject(
|
||||||
|
new Error((await e.text()) || e.statusText)
|
||||||
|
);
|
||||||
|
return e.json();
|
||||||
|
})
|
||||||
|
.then((data) => {
|
||||||
if (data.error) {
|
if (data.error) {
|
||||||
if (!Array.isArray(data.error)) return Promise.reject(new Error(data.error));
|
if (!Array.isArray(data.error))
|
||||||
|
return Promise.reject(new Error(data.error));
|
||||||
let ce = [];
|
let ce = [];
|
||||||
data.error.forEach(e => {
|
data.error.forEach((e) => {
|
||||||
let ef = document.getElementById("err_" + e.field);
|
let ef = document.getElementById("err_" + e.field);
|
||||||
if (!ef) ce.push(e);
|
if (!ef) ce.push(e);
|
||||||
else {
|
else {
|
||||||
showError(ef, e.message);
|
showError(ef, e.message);
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
if (ce.length > 0) {
|
if (ce.length > 0) {
|
||||||
showError("error", ce.join("<br>"));
|
showError("error", ce.join("<br>"));
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
window.location.href = "/login";
|
window.location.href = "/login";
|
||||||
}
|
}
|
||||||
}).catch(e => {
|
|
||||||
showError("error", e.message);
|
|
||||||
})
|
})
|
||||||
}
|
.catch((e) => {
|
||||||
})()
|
showError("error", e.message);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
})();
|
||||||
|
@ -36,13 +36,14 @@ form {
|
|||||||
padding: 3em 2em 2em 2em;
|
padding: 3em 2em 2em 2em;
|
||||||
background: #fafafa;
|
background: #fafafa;
|
||||||
border: 1px solid #ebebeb;
|
border: 1px solid #ebebeb;
|
||||||
box-shadow: rgba(0, 0, 0, 0.14902) 0px 1px 1px 0px, rgba(0, 0, 0, 0.09804) 0px 1px 2px 0px;
|
box-shadow: rgba(0, 0, 0, 0.14902) 0px 1px 1px 0px,
|
||||||
|
rgba(0, 0, 0, 0.09804) 0px 1px 2px 0px;
|
||||||
}
|
}
|
||||||
|
|
||||||
#registerbutton {
|
#registerbutton {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
background: $primary;
|
background: $primary;
|
||||||
text-shadow: 1px 1px 0 rgba(39, 110, 204, .5);
|
text-shadow: 1px 1px 0 rgba(39, 110, 204, 0.5);
|
||||||
}
|
}
|
||||||
|
|
||||||
footer {
|
footer {
|
||||||
@ -52,13 +53,13 @@ footer {
|
|||||||
footer p {
|
footer p {
|
||||||
color: #888;
|
color: #888;
|
||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
letter-spacing: .4px;
|
letter-spacing: 0.4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
footer a {
|
footer a {
|
||||||
color: $primary;
|
color: $primary;
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
transition: all .2s ease;
|
transition: all 0.2s ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
footer a:hover {
|
footer a:hover {
|
||||||
@ -68,11 +69,11 @@ footer a:hover {
|
|||||||
|
|
||||||
footer img {
|
footer img {
|
||||||
width: 80px;
|
width: 80px;
|
||||||
transition: all .2s ease;
|
transition: all 0.2s ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
footer img:hover {
|
footer img:hover {
|
||||||
opacity: .83;
|
opacity: 0.83;
|
||||||
}
|
}
|
||||||
|
|
||||||
footer img:focus,
|
footer img:focus,
|
||||||
|
@ -1,18 +1,9 @@
|
|||||||
{
|
{
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"lib": [
|
"lib": ["dom", "es2015", "es6", "es7", "es2018", "esnext"],
|
||||||
"dom",
|
|
||||||
"es2015",
|
|
||||||
"es6",
|
|
||||||
"es7",
|
|
||||||
"es2018",
|
|
||||||
"esnext"
|
|
||||||
],
|
|
||||||
"jsxFactory": "h",
|
"jsxFactory": "h",
|
||||||
"jsx": "react",
|
"jsx": "react",
|
||||||
"module": "esnext"
|
"module": "esnext"
|
||||||
},
|
},
|
||||||
"include": [
|
"include": ["./types.d.ts"]
|
||||||
"./types.d.ts"
|
|
||||||
]
|
|
||||||
}
|
}
|
@ -1 +1 @@
|
|||||||
Subproject commit e4d08dcbf93f58ff85f54f7770ae7db490ff14d6
|
Subproject commit 4191522b24334f20f7dcda811ca43959b02fef7a
|
Loading…
Reference in New Issue
Block a user