├── BUILD ├── npm ├── BUILD ├── ReadMe.md ├── package.json └── yarn.lock ├── frontend ├── BUILD ├── .gitignore ├── app │ ├── .gitignore │ ├── .babelrc │ ├── assets │ │ └── frappe.png │ ├── src │ │ ├── util │ │ │ └── url.js │ │ ├── index.html │ │ ├── index.js │ │ ├── protoHelper.js │ │ ├── components │ │ │ └── Protected.js │ │ ├── HelloWorld.js │ │ └── hooks │ │ │ └── useAuthData.js │ ├── entry.sh │ ├── nginx.conf │ ├── nginxDev.conf │ ├── package.json │ ├── webpack.config.js │ ├── webpack.dev.js │ ├── webpack.prod.js │ └── BUILD ├── next_app │ ├── public │ │ ├── favicon.ico │ │ └── vercel.svg │ ├── pages │ │ ├── api │ │ │ ├── hello.js │ │ │ └── auth │ │ │ │ └── [...nextauth].js │ │ ├── _app.js │ │ └── index.js │ ├── postcss.config.js │ ├── next.config.js │ ├── util │ │ ├── protoHelper.js │ │ └── ReadMe.md │ ├── tailwind.config.js │ ├── .gitignore │ ├── entry.sh │ ├── package.json │ ├── components │ │ └── Login.js │ ├── README.md │ └── BUILD ├── envoy │ ├── BUILD │ └── envoy.yaml └── ReadMe.md ├── nodejs ├── .bazelversion ├── .gitignore ├── .bazelignore ├── ReadMe.md ├── package.json ├── BUILD ├── gateway.js └── .bazelrc ├── local ├── ReadMe.md ├── init.js └── BUILD ├── java ├── README.md ├── com │ ├── user │ │ ├── util │ │ │ ├── SSOValidator.java │ │ │ ├── FakeSSOValidator.java │ │ │ ├── BUILD │ │ │ ├── MainSSOValidator.java │ │ │ └── JWTUtil.java │ │ ├── db │ │ │ ├── UserDBHandler.java │ │ │ ├── BUILD │ │ │ ├── FakeUserDBHandler.java │ │ │ └── MainUserDBHandler.java │ │ └── management │ │ │ ├── UserManagementService.java │ │ │ ├── BUILD │ │ │ └── UserManagementServiceImpl.java │ ├── task │ │ ├── README.md │ │ ├── TaskService.java │ │ ├── Constants.java │ │ ├── TaskServiceImpl.java │ │ ├── BUILD │ │ └── AuthenticatedInterceptor.java │ └── util │ │ ├── ServiceModule.java │ │ ├── FakeServiceModule.java │ │ ├── SetupUtil.java │ │ └── BUILD └── test │ ├── java_testing_generator.bzl │ └── com │ ├── task │ ├── BUILD │ └── TaskServiceTest.java │ └── user │ └── management │ ├── BUILD │ └── UserManagementServiceTest.java ├── spec_local ├── ns_slot.yaml ├── service_task.yaml ├── service_mongodb.yaml ├── service_envoy.yaml ├── service_frapp.yaml ├── service_user_management.yaml ├── service_gateway.yaml ├── service_next_frapp.yaml ├── ReadMe.md ├── deployment_mongodb.yaml ├── deployment_task.yaml ├── deployment_envoy.yaml ├── deployment_frapp.yaml ├── deployment_gateway.yaml ├── deployment_user_management.yaml ├── deployment_next_frapp.yaml ├── ingress.yaml ├── startup.sh └── BUILD ├── monorepo-diagram.png ├── README.md ├── proto ├── task │ ├── task_service.proto │ └── BUILD └── user │ ├── user.proto │ ├── BUILD │ ├── user_management_service.proto │ └── db.proto ├── .bazelrc ├── .gitignore └── WORKSPACE /BUILD: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /npm/BUILD: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /frontend/BUILD: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /frontend/.gitignore: -------------------------------------------------------------------------------- 1 | genProto/** -------------------------------------------------------------------------------- /nodejs/.bazelversion: -------------------------------------------------------------------------------- 1 | 3.0.0 2 | 3 | -------------------------------------------------------------------------------- /local/ReadMe.md: -------------------------------------------------------------------------------- 1 | Local specific db for testing 2 | -------------------------------------------------------------------------------- /nodejs/.gitignore: -------------------------------------------------------------------------------- 1 | 2 | dist 3 | bazel-out 4 | node_modules 5 | -------------------------------------------------------------------------------- /nodejs/.bazelignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | dist 3 | bazel-out 4 | 5 | -------------------------------------------------------------------------------- /frontend/app/.gitignore: -------------------------------------------------------------------------------- 1 | dist/ 2 | devDist/ 3 | 4 | src/genProto/** 5 | 6 | .env -------------------------------------------------------------------------------- /java/README.md: -------------------------------------------------------------------------------- 1 | # Backend 2 | 3 | gRPC servers mainly written in Java 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /frontend/app/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["@babel/preset-env", "@babel/preset-react"] 3 | } -------------------------------------------------------------------------------- /spec_local/ns_slot.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Namespace 3 | metadata: 4 | name: slot 5 | -------------------------------------------------------------------------------- /monorepo-diagram.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kunal-rp/monorepo_template/HEAD/monorepo-diagram.png -------------------------------------------------------------------------------- /frontend/app/assets/frappe.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kunal-rp/monorepo_template/HEAD/frontend/app/assets/frappe.png -------------------------------------------------------------------------------- /frontend/next_app/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kunal-rp/monorepo_template/HEAD/frontend/next_app/public/favicon.ico -------------------------------------------------------------------------------- /frontend/app/src/util/url.js: -------------------------------------------------------------------------------- 1 | 2 | 3 | module.exports = { 4 | gatewayUrl: () => process.env.LOCAL_URL != undefined ? process.env.LOCAL_URL : "PH_REACT_APP_BASE_URL" 5 | } -------------------------------------------------------------------------------- /frontend/app/src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Slot Frapp 6 | 7 | 8 |
9 | 10 | -------------------------------------------------------------------------------- /nodejs/ReadMe.md: -------------------------------------------------------------------------------- 1 | ##Example nodejs rest gateway for grpc services 2 | 3 | to create image, will need to specify platofrm : bazel run --platforms @build_bazel_rules_nodejs//toolchains/node:linux_amd64 TARGET 4 | -------------------------------------------------------------------------------- /npm/ReadMe.md: -------------------------------------------------------------------------------- 1 | This npm repo is used for hardcoded tools in grpc proto libraries 2 | 3 | https://github.com/rules-proto-grpc/rules_proto_grpc/blob/caa83f1d78b1b7ac3b891887771c0de7330503a1/js/BUILD.bazel 4 | 5 | -------------------------------------------------------------------------------- /frontend/next_app/pages/api/hello.js: -------------------------------------------------------------------------------- 1 | // Next.js API route support: https://nextjs.org/docs/api-routes/introduction 2 | 3 | export default function helloAPI(req, res) { 4 | res.status(200).json({ name: 'John Doe' }) 5 | } 6 | -------------------------------------------------------------------------------- /java/com/user/util/SSOValidator.java: -------------------------------------------------------------------------------- 1 | package com.user.util; 2 | 3 | import java.util.Optional; 4 | import com.user.UserProto.User; 5 | 6 | public interface SSOValidator { 7 | 8 | public Optional validateGoogleIdToken(String idToken); 9 | } -------------------------------------------------------------------------------- /spec_local/service_task.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | name: task 5 | namespace: slot 6 | spec: 7 | selector: 8 | app: task 9 | ports: 10 | - protocol: "TCP" 11 | port: 80 12 | targetPort: 80 -------------------------------------------------------------------------------- /java/test/java_testing_generator.bzl: -------------------------------------------------------------------------------- 1 | def java_unit_test(name, pkg,srcs, deps): 2 | for src in srcs: 3 | src_name = src[:-5] 4 | native.java_test(name=src_name , test_class=pkg+'.'+src_name , srcs=srcs, deps=deps, size="small") 5 | 6 | 7 | -------------------------------------------------------------------------------- /frontend/next_app/postcss.config.js: -------------------------------------------------------------------------------- 1 | // If you want to use other PostCSS plugins, see the following: 2 | // https://tailwindcss.com/docs/using-with-preprocessors 3 | module.exports = { 4 | plugins: { 5 | tailwindcss: {}, 6 | autoprefixer: {}, 7 | }, 8 | } 9 | -------------------------------------------------------------------------------- /spec_local/service_mongodb.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | name: mongodb 5 | namespace: slot 6 | spec: 7 | selector: 8 | app: mongodb 9 | ports: 10 | - protocol: "TCP" 11 | port: 80 12 | targetPort: 27017 13 | -------------------------------------------------------------------------------- /spec_local/service_envoy.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | name: envoy 5 | namespace: slot 6 | spec: 7 | type: NodePort 8 | selector: 9 | app: envoy 10 | ports: 11 | - nodePort: 30002 12 | port: 8080 13 | targetPort: 80 14 | -------------------------------------------------------------------------------- /spec_local/service_frapp.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | name: frapp 5 | namespace: slot 6 | spec: 7 | type: NodePort 8 | selector: 9 | app: frapp 10 | ports: 11 | - nodePort: 30001 12 | port: 8080 13 | targetPort: 80 14 | -------------------------------------------------------------------------------- /spec_local/service_user_management.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | name: user-management 5 | namespace: slot 6 | spec: 7 | selector: 8 | app: user-management 9 | ports: 10 | - protocol: "TCP" 11 | port: 80 12 | targetPort: 80 -------------------------------------------------------------------------------- /spec_local/service_gateway.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | name: gateway 5 | namespace: slot 6 | spec: 7 | type: NodePort 8 | selector: 9 | app: gateway 10 | ports: 11 | - nodePort: 30000 12 | port: 8080 13 | targetPort: 3000 14 | -------------------------------------------------------------------------------- /spec_local/service_next_frapp.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | name: next-frapp 5 | namespace: slot 6 | spec: 7 | type: NodePort 8 | selector: 9 | app: next-frapp 10 | ports: 11 | - nodePort: 30003 12 | port: 8080 13 | targetPort: 3000 14 | -------------------------------------------------------------------------------- /frontend/app/entry.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | echo "Check that we have REACT_APP_BASE_URL vars" 4 | test -n "$REACT_APP_BASE_URL" 5 | 6 | find /dist \( -type d -name .git -prune \) -o -type f -print0 | xargs -0 sed -i "s#PH_REACT_APP_BASE_URL#$REACT_APP_BASE_URL#g" 7 | 8 | echo "Starting React" 9 | exec "$@" -------------------------------------------------------------------------------- /frontend/next_app/next.config.js: -------------------------------------------------------------------------------- 1 | 2 | module.exports = { 3 | distDir: 'nextBuild', 4 | publicRuntimeConfig : { 5 | LOCAL_URL : process.env.LOCAL_URL, 6 | NEXTAUTH_URL: process.env.NEXTAUTH_URL, 7 | NEXT_PUBLIC_REACT_APP_BASE_URL: process.env.NEXT_PUBLIC_REACT_APP_BASE_URL, 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /frontend/next_app/pages/_app.js: -------------------------------------------------------------------------------- 1 | import 'tailwindcss/tailwind.css' 2 | import { SessionProvider } from "next-auth/react" 3 | 4 | export default function MyApp({ Component, pageProps: { session, ...pageProps } }) { 5 | return ( 6 | 7 | 8 | ) 9 | } 10 | -------------------------------------------------------------------------------- /frontend/next_app/util/protoHelper.js: -------------------------------------------------------------------------------- 1 | 2 | var isLocal = process.env.NODE_ENV == "development" 3 | 4 | module.exports = { 5 | omitAuthCalls: () => process.env.OMIT_GATEWAY_AUTH_CALL == "true", 6 | getUrl: () => (process.env.NEXT_PUBLIC_REACT_APP_BASE_URL != undefined ? process.env.NEXT_PUBLIC_REACT_APP_BASE_URL : "PH_NEXT_PUBLIC_REACT_APP_BASE_URL") 7 | 8 | } -------------------------------------------------------------------------------- /java/com/task/README.md: -------------------------------------------------------------------------------- 1 | # template Service 2 | 3 | All services regarding generation of schedules and evaludations of tasks. 4 | 5 | ## API 6 | 7 | - GenerateScheduleEntries: 8 | * For a given time slot generates task entries based on task templates 9 | * INPUT : (int) slot unix start time  , (int) slot unix end time 10 | * OUTPUT : List -------------------------------------------------------------------------------- /frontend/next_app/util/ReadMe.md: -------------------------------------------------------------------------------- 1 | This dir stores all js_out generated files to be used in frontend js applications. NEVER PUSH generated files to repo 2 | 3 | To generate all task service proto files for example , run : 4 | - protoc proto/task/*.proto --js_out=import_style=commonjs,binary:frontend/genProto --grpc-web_out=import_style=commonjs,mode=grpcwebtext:frontend/genProto/ -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Mono Repo Template 2 | 3 | - Bazel 4 | - gRPC + protobuf 5 | - java microservices 6 | - NextJS + React frontend apps 7 | - monogodb (container) for local testing 8 | 9 | ![Alt text](/monorepo-diagram.png "Stack Flow") 10 | 11 | 12 | Start @ : 13 | - spec_local, should have container image target references 14 | - proto, should define microservices 15 | 16 | -------------------------------------------------------------------------------- /frontend/envoy/BUILD: -------------------------------------------------------------------------------- 1 | package(default_visibility = ["//visibility:public"]) 2 | 3 | load("@io_bazel_rules_docker//container:container.bzl", "container_image") 4 | 5 | container_image( 6 | name = "envoy_image", 7 | base = "@envoy_base//image", 8 | files = [ 9 | "envoy.yaml", 10 | ], 11 | cmd = " && ".join([ 12 | "envoy -c ./envoy.yaml;" 13 | ]), 14 | ) -------------------------------------------------------------------------------- /frontend/app/src/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { render } from 'react-dom'; 3 | import { BrowserRouter } from "react-router-dom"; 4 | 5 | import HelloWorld from './HelloWorld'; 6 | import Protected from './components/Protected'; 7 | 8 | render( 9 | 10 | 11 | 12 | 13 | , document.getElementById('root')); -------------------------------------------------------------------------------- /local/init.js: -------------------------------------------------------------------------------- 1 | var db = connect('127.0.0.1/localSlot'); 2 | 3 | db.users.insert({ 4 | 'user_id' : 11111, 5 | 'name': "Kunal Sample", 6 | 'email': "krp@sample.com" }); 7 | 8 | db.refresh.insert({ 9 | 'user_id' : 11111, 10 | 'token': "eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiIxMTExMSIsImlhdCI6MTY0MDMzNzcxNn0.DnBBwTC2z1fv6VItgFnt7kS5_KEwt0LmPxaW0VtAq7I"}); 11 | 12 | db.users.createIndex({user_id:1}, {unique: true}); 13 | -------------------------------------------------------------------------------- /spec_local/ReadMe.md: -------------------------------------------------------------------------------- 1 | # Local Cluster of STack 2 | 3 | 4 | 1) start up minikube 5 | 2) ensure that cluser ip (`minikube ip`) points to `slotlocal.com` 6 | - add registry in /etc/hosts 7 | - this is the domain that GOOGLE SSO will redirect to 8 | 3) enable ingress : `minikube addons enable ingress` 9 | 4) run startup.sh 10 | 11 | 12 | As a placeholder, 'slot' was used as the namespace/image name (for registries) 13 | 14 | -------------------------------------------------------------------------------- /proto/task/task_service.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package task; 4 | 5 | option java_package = "com.task"; 6 | option java_outer_classname = "TaskServiceProto"; 7 | 8 | 9 | service TaskService { 10 | 11 | rpc SomeAction(ActionRequest) returns (ActionResponse); 12 | 13 | } 14 | 15 | message ActionRequest { 16 | string some_data = 1; 17 | } 18 | 19 | message ActionResponse{ 20 | repeated string result_data = 2; 21 | } -------------------------------------------------------------------------------- /npm/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "npm", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "author": "", 10 | "license": "ISC", 11 | "dependencies": { 12 | "@grpc/grpc-js": "^1.3.4", 13 | "google-protobuf": "^3.17.3", 14 | "grpc-tools": "^1.11.2", 15 | "ts-protoc-gen": "^0.15.0" 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /proto/task/BUILD: -------------------------------------------------------------------------------- 1 | package(default_visibility = ["//visibility:public"]) 2 | 3 | load("@rules_proto//proto:defs.bzl", "proto_library") 4 | load("@rules_proto_grpc//java:defs.bzl", "java_grpc_library") 5 | 6 | 7 | proto_library( 8 | name = "task_service_proto", 9 | srcs = ["task_service.proto"], 10 | ) 11 | 12 | java_grpc_library( 13 | name = "task_service_java_proto", 14 | protos = [ 15 | ":task_service_proto", 16 | ], 17 | ) 18 | -------------------------------------------------------------------------------- /frontend/next_app/tailwind.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | mode: 'jit', 3 | purge: ['./pages/**/*.{js,ts,jsx,tsx}', './components/**/*.{js,ts,jsx,tsx}'], 4 | darkMode: false, // or 'media' or 'class' 5 | theme: { 6 | minWidth: { 7 | '0': '0', 8 | '1/12': '8%', 9 | '1/6': '17%', 10 | '1/4': '25%', 11 | 'full': '100%', 12 | } 13 | }, 14 | variants: { 15 | extend: {}, 16 | }, 17 | plugins: [], 18 | } 19 | -------------------------------------------------------------------------------- /.bazelrc: -------------------------------------------------------------------------------- 1 | build --incompatible_restrict_string_escapes=false --define=CLUSTER_CR=def_cluster --define=CLUSTER_NAME=def_cluster --define=CLUSTER_CONTEXT=def_cluster --define=CLUSTER_USER=def_cluster --define=CLUSTER_INGRESS_IP=def_cluster --define=GOOGLE_CLIENT_ID=def_google_client_id --define=GOOGLE_CLIENT_SECRET=def_google_client_secret --define=MONGODB_URI=def_mongodb_uri --define=MONGODB_DB=def_mongodb_db --define=DOMAIN=def_domain 2 | query --incompatible_restrict_string_escapes=false -------------------------------------------------------------------------------- /spec_local/deployment_mongodb.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: mongodb 5 | namespace: slot 6 | spec: 7 | replicas: 1 8 | selector: 9 | matchLabels: 10 | app: mongodb 11 | template: 12 | metadata: 13 | labels: 14 | app: mongodb 15 | spec: 16 | containers: 17 | - name: mongodb 18 | image: YOUR_REGISTRY_NAME/slot_mongodb 19 | ports: 20 | - containerPort: 80 21 | -------------------------------------------------------------------------------- /spec_local/deployment_task.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: task 5 | namespace: slot 6 | spec: 7 | replicas: 1 8 | selector: 9 | matchLabels: 10 | app: task 11 | template: 12 | metadata: 13 | annotations: 14 | linkerd.io/inject: enabled 15 | labels: 16 | app: task 17 | spec: 18 | containers: 19 | - name: task 20 | image: YOUR_REGISTRY_NAME/slot_task 21 | ports: 22 | - containerPort: 80 23 | -------------------------------------------------------------------------------- /java/com/util/ServiceModule.java: -------------------------------------------------------------------------------- 1 | package com.util; 2 | 3 | import com.google.inject.AbstractModule; 4 | import com.user.db.UserDBHandler; 5 | import com.user.db.MainUserDBHandler; 6 | import com.user.util.SSOValidator; 7 | import com.user.util.MainSSOValidator; 8 | 9 | public class ServiceModule extends AbstractModule{ 10 | 11 | @Override 12 | protected void configure() { 13 | bind(UserDBHandler.class).to(MainUserDBHandler.class); 14 | bind(SSOValidator.class).to(MainSSOValidator.class); 15 | } 16 | } -------------------------------------------------------------------------------- /spec_local/deployment_envoy.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: envoy 5 | namespace: slot 6 | spec: 7 | replicas: 1 8 | selector: 9 | matchLabels: 10 | app: envoy 11 | template: 12 | metadata: 13 | annotations: 14 | linkerd.io/inject: enabled 15 | labels: 16 | app: envoy 17 | spec: 18 | containers: 19 | - name: envoy 20 | image: YOUR_REGISTRY_NAME/slot_envoy 21 | ports: 22 | - containerPort: 80 23 | -------------------------------------------------------------------------------- /java/com/util/FakeServiceModule.java: -------------------------------------------------------------------------------- 1 | package com.util; 2 | 3 | import com.google.inject.AbstractModule; 4 | import com.user.db.UserDBHandler; 5 | import com.user.db.FakeUserDBHandler; 6 | import com.user.util.SSOValidator; 7 | import com.user.util.FakeSSOValidator; 8 | 9 | public class FakeServiceModule extends AbstractModule{ 10 | 11 | @Override 12 | protected void configure() { 13 | bind(UserDBHandler.class).to(FakeUserDBHandler.class); 14 | bind(SSOValidator.class).to(FakeSSOValidator.class); 15 | } 16 | } -------------------------------------------------------------------------------- /frontend/next_app/pages/index.js: -------------------------------------------------------------------------------- 1 | import Head from 'next/head' 2 | import Login from '../components/Login' 3 | 4 | import { useState,useEffect} from 'react'; 5 | 6 | 7 | export default function Home(props) { 8 | 9 | 10 | return ( 11 |
12 | 13 | Next Frapp 14 | 15 | 16 | 17 |
18 | Index 19 | 20 |
21 |
22 | ) 23 | } 24 | 25 | -------------------------------------------------------------------------------- /proto/user/user.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package user; 4 | 5 | option java_package = "com.user"; 6 | option java_outer_classname = "UserProto"; 7 | 8 | message UserId { 9 | int32 id = 1; 10 | } 11 | 12 | message UserRefreshToken { 13 | string data = 1; 14 | } 15 | 16 | message UserAccessToken { 17 | string data = 1; 18 | } 19 | 20 | message User{ 21 | UserId user_id = 1; 22 | string name = 2; 23 | string email = 3; 24 | string profile_url = 4; 25 | string locale = 5; 26 | oneof sso_id { 27 | string google_user_id = 6; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Ignore backup files. 2 | *~ 3 | # Ignore Vim swap files. 4 | .*.swp 5 | # Ignore files generated by IDEs. 6 | /.classpath 7 | /.factorypath 8 | /.idea/ 9 | /.ijwb/ 10 | /.project 11 | /.settings 12 | /.vscode/ 13 | /bazel.iml 14 | # Ignore all bazel-* symlinks. There is no full list since this can change 15 | # based on the name of the directory bazel is cloned into. 16 | /bazel-* 17 | 18 | .vagrant/ 19 | Vagrantfile 20 | .idea/** 21 | *.iml 22 | .DS_Store 23 | **/.DS_Store 24 | node_modules/ 25 | 26 | spec_*/config 27 | 28 | **/genProto 29 | 30 | 31 | -------------------------------------------------------------------------------- /frontend/next_app/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # next.js 12 | /.next/ 13 | /out/ 14 | 15 | # production 16 | 17 | # misc 18 | .DS_Store 19 | *.pem 20 | 21 | # debug 22 | npm-debug.log* 23 | yarn-debug.log* 24 | yarn-error.log* 25 | 26 | # local env files 27 | .env.local 28 | .env.development.local 29 | .env.test.local 30 | .env.production.local 31 | 32 | # vercel 33 | .vercel 34 | 35 | nextBuild 36 | 37 | .env -------------------------------------------------------------------------------- /frontend/app/src/protoHelper.js: -------------------------------------------------------------------------------- 1 | const LOCAL_PATH = './genProto/proto/task/' 2 | const BAZEL_PATH = './proto/task/' 3 | 4 | const PATH = process.env.LOCAL_URL ? LOCAL_PATH : BAZEL_PATH 5 | 6 | const taskServiceProto = require(PATH+'task_service_grpc_web_pb.js') 7 | 8 | var getUrl = (process.env.LOCAL_URL ? process.env.LOCAL_URL : "PH_REACT_APP_BASE_URL"); 9 | 10 | module.exports = { 11 | getUrl: () => getUrl, 12 | getTaskServiceProto: () => taskServiceProto, 13 | getTaskServiceClient: () => new taskServiceProto.TaskServiceClient( 14 | getUrl + "/gapi", null, {'withCredentials': true}) 15 | } -------------------------------------------------------------------------------- /spec_local/deployment_frapp.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: frapp 5 | namespace: slot 6 | spec: 7 | replicas: 1 8 | selector: 9 | matchLabels: 10 | app: frapp 11 | template: 12 | metadata: 13 | annotations: 14 | linkerd.io/inject: enabled 15 | labels: 16 | app: frapp 17 | spec: 18 | containers: 19 | - name: frapp 20 | image: YOUR_REGISTRY_NAME/slot_frapp 21 | ports: 22 | - containerPort: 80 23 | env: 24 | - name: "REACT_APP_BASE_URL" 25 | value: "http://{SUB_BASE_URL}" 26 | -------------------------------------------------------------------------------- /local/BUILD: -------------------------------------------------------------------------------- 1 | package(default_visibility = ["//visibility:public"]) 2 | 3 | load("@io_bazel_rules_docker//container:container.bzl", "container_image") 4 | 5 | 6 | genrule( 7 | name = "init_files", 8 | srcs = [ 9 | ":init.js", 10 | ], 11 | cmd = " && ".join([ 12 | "mkdir docker-entrypoint-initdb.d", 13 | "mv local/init.js docker-entrypoint-initdb.d", 14 | "mv ./docker-entrypoint-initdb.d $@", 15 | ]), 16 | outs = [ 17 | "docker-entrypoint-initdb.d", 18 | ], 19 | ) 20 | 21 | container_image( 22 | name = "db_local_image", 23 | base = "@mongo_base//image", 24 | files = [ 25 | ":init_files" 26 | ], 27 | ) -------------------------------------------------------------------------------- /spec_local/deployment_gateway.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: gateway 5 | namespace: slot 6 | spec: 7 | replicas: 1 8 | selector: 9 | matchLabels: 10 | app: gateway 11 | template: 12 | metadata: 13 | annotations: 14 | linkerd.io/inject: enabled 15 | labels: 16 | app: gateway 17 | spec: 18 | containers: 19 | - name: gateway 20 | image: YOUR_REGISTRY_NAME/slot_gateway 21 | ports: 22 | - containerPort: 3000 23 | env: 24 | - name: "REACT_APP_BASE_URL" 25 | value: "http://{SUB_BASE_URL}" 26 | -------------------------------------------------------------------------------- /frontend/app/src/components/Protected.js: -------------------------------------------------------------------------------- 1 | import useAuthData from '../hooks/useAuthData' 2 | import React, { useState, useEffect } from 'react'; 3 | import {getUrl} from '../protoHelper'; 4 | 5 | export default function Protected(props){ 6 | 7 | const [loadingState, accessToken] = useAuthData(); 8 | 9 | useEffect(() => { 10 | console.log("protected ") 11 | console.log(loadingState) 12 | },[loadingState]) 13 | 14 | console.log(loadingState) 15 | if(loadingState == "SUCCESS" || (loadingState == "IN_PROGRESS" && accessToken != null) ){ 16 | return (props.children) 17 | }else if(loadingState == "FAILED"){ 18 | window.location.assign(getUrl()); 19 | return "FAILED" 20 | } 21 | else{ 22 | return "LOADING..." 23 | } 24 | 25 | } -------------------------------------------------------------------------------- /frontend/next_app/entry.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | echo "Check that we have REACT_APP_BASE_URL,PH_GOOGLE_CLIENT_ID,PH_GOOGLE_CLIENT_SECRET vars" 4 | test -n "$REACT_APP_BASE_URL" 5 | test -n "$PH_GOOGLE_CLIENT_ID" 6 | test -n "$PH_GOOGLE_CLIENT_SECRET" 7 | 8 | find prodBuild/nextBuild \( -type d -name .git -prune \) -o -type f -print0 | xargs -0 sed -i "s#PH_NEXT_PUBLIC_REACT_APP_BASE_URL#$NEXT_PUBLIC_REACT_APP_BASE_URL#g" 9 | find prodBuild/nextBuild \( -type d -name .git -prune \) -o -type f -print0 | xargs -0 sed -i "s#PH_GOOGLE_CLIENT_ID#$GOOGLE_CLIENT_ID#g" 10 | find prodBuild/nextBuild \( -type d -name .git -prune \) -o -type f -print0 | xargs -0 sed -i "s#PH_GOOGLE_CLIENT_SECRET#$GOOGLE_CLIENT_SECRET#g" 11 | 12 | echo "Starting React" 13 | exec "$@" -------------------------------------------------------------------------------- /frontend/next_app/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "next_app", 3 | "version": "1.0.0", 4 | "description": "NextJs frontend app ", 5 | "private": true, 6 | "scripts": { 7 | "dev": "next dev", 8 | "build": "next build", 9 | "start": "next start" 10 | }, 11 | "dependencies": { 12 | "@grpc/grpc-js": "^1.4.5", 13 | "axios": "^0.24.0", 14 | "cookies": "^0.8.0", 15 | "google-protobuf": "3.19.0", 16 | "grpc-tools": "^1.11.2", 17 | "grpc-web": "1.2.1", 18 | "next": "11.1.2", 19 | "next-auth": "^4.0.6", 20 | "react": "^17.0.2", 21 | "react-dom": "^17.0.2", 22 | "tailwindcss": "^2.2.17" 23 | }, 24 | "devDependencies": { 25 | "autoprefixer": "^10.2.6", 26 | "postcss": "^8.3.5" 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /frontend/next_app/components/Login.js: -------------------------------------------------------------------------------- 1 | import {useEffect} from 'react' 2 | import { useSession, signIn } from "next-auth/react" 3 | import { useRouter } from 'next/router' 4 | import {getUrl} from '../util/protoHelper' 5 | 6 | export default function Login(){ 7 | 8 | const router = useRouter() 9 | const {data: session, status } = useSession() 10 | 11 | if (status === "authenticated"){ 12 | return ( 13 | <> 14 | 15 | Signed in as {session.user.email}
16 | 17 | 18 | ) 19 | } 20 | return ( 21 | <> 22 | Not signed in
23 | 24 | 25 | ) 26 | 27 | } -------------------------------------------------------------------------------- /java/com/user/util/FakeSSOValidator.java: -------------------------------------------------------------------------------- 1 | package com.user.util; 2 | 3 | import java.util.Map; 4 | import java.util.HashMap; 5 | import java.util.Optional; 6 | import com.user.UserProto.User; 7 | import com.google.inject.Singleton; 8 | 9 | @Singleton 10 | public class FakeSSOValidator implements SSOValidator { 11 | 12 | private Map tokenIdMap = new HashMap(); 13 | 14 | @Override 15 | public Optional validateGoogleIdToken(String idToken){ 16 | if(tokenIdMap.containsKey(idToken)){ 17 | return Optional.of(tokenIdMap.get(idToken)); 18 | } 19 | return Optional.empty(); 20 | } 21 | 22 | public void clear(){ 23 | tokenIdMap = new HashMap(); 24 | } 25 | 26 | public void setUserToIdToken(String idToken, User user){ 27 | tokenIdMap.put(idToken, user); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /java/com/task/TaskService.java: -------------------------------------------------------------------------------- 1 | package com.task; 2 | 3 | import com.util.ServiceModule; 4 | import com.google.inject.Injector; 5 | import com.google.inject.Guice; 6 | import io.grpc.Server; 7 | import io.grpc.ServerBuilder; 8 | import com.util.SetupUtil; 9 | 10 | public class TaskService { 11 | 12 | public static void main(String[] args) throws Exception { 13 | 14 | Injector injector = Guice.createInjector(new ServiceModule()); 15 | 16 | Server pollServer = 17 | ServerBuilder 18 | .forPort(SetupUtil.DEFAULT_SERVICE_PORT) 19 | .intercept(new AuthenticatedInterceptor()) 20 | .addService(injector.getInstance(TaskServiceImpl.class)) 21 | .build(); 22 | pollServer.start(); 23 | pollServer.awaitTermination(); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /java/com/task/Constants.java: -------------------------------------------------------------------------------- 1 | package com.task; 2 | 3 | import io.grpc.Metadata; 4 | import io.grpc.Context; 5 | import com.user.UserProto.UserId; 6 | 7 | public class Constants { 8 | 9 | // Metadata 10 | public static final Metadata.Key COOKIE_SLOT_METADATA_KEY = 11 | Metadata.Key.of("cookie", Metadata.ASCII_STRING_MARSHALLER); 12 | 13 | public static final Metadata.Key ACCESS_SLOT_METADATA_KEY = 14 | Metadata.Key.of("slot-a-tkn", Metadata.ASCII_STRING_MARSHALLER); 15 | 16 | public static final String CUSTOM_SLOT_HEADER_COOKIE_KEY = "custom_slot_header"; 17 | 18 | // Context 19 | public static final Context.Key CUSTOM_HEADER_CTX_KEY = Context.key("custom_slot"); 20 | public static final Context.Key USER_ID_CTX_KEY = Context.key("slot_user_id"); 21 | } 22 | 23 | -------------------------------------------------------------------------------- /spec_local/deployment_user_management.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: user-management 5 | namespace: slot 6 | spec: 7 | replicas: 1 8 | selector: 9 | matchLabels: 10 | app: user-management 11 | template: 12 | metadata: 13 | annotations: 14 | linkerd.io/inject: enabled 15 | labels: 16 | app: user-management 17 | spec: 18 | containers: 19 | - name: user-management 20 | image: YOUR_REGISTRY_NAME/slot_user_management 21 | ports: 22 | - containerPort: 80 23 | env: 24 | - name: "GOOGLE_CLIENT_ID" 25 | value: "{GOOGLE_CLIENT_ID}" 26 | - name: "MONGODB_URI" 27 | value: "{MONGODB_URI}" 28 | - name: "MONGODB_DB" 29 | value: "{MONGODB_DB}" 30 | -------------------------------------------------------------------------------- /java/com/util/SetupUtil.java: -------------------------------------------------------------------------------- 1 | package com.util; 2 | 3 | import java.security.Provider.Service; 4 | import java.util.Optional; 5 | import com.google.common.collect.ImmutableMap; 6 | 7 | /** 8 | * 9 | * Util to handle configuration and fetching of setup information for microservices. Servers: Call 10 | * to get port number for server instantiation Client: Call to get target address for said server 11 | */ 12 | 13 | public class SetupUtil { 14 | 15 | public static int DEFAULT_SERVICE_PORT = 80; 16 | 17 | // All availabe services 18 | public enum AvailableServices { 19 | TASK, 20 | USER 21 | } 22 | 23 | public static String getTarget(AvailableServices service) { 24 | switch (service) { 25 | case TASK: 26 | return "task"; 27 | case USER: 28 | return "user"; 29 | } 30 | return "someurl"; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /java/test/com/task/BUILD: -------------------------------------------------------------------------------- 1 | load("//:java/test/java_testing_generator.bzl", "java_unit_test") 2 | 3 | package(default_visibility = ["//visibility:public"]) 4 | 5 | java_unit_test( 6 | name = "task_tests", 7 | pkg="com.task", 8 | srcs = glob(["*.java"]), 9 | deps = [ 10 | "@io_grpc_grpc_java//api", 11 | "@io_grpc_grpc_java//core:inprocess", 12 | "@io_grpc_grpc_java//stub", 13 | "@maven2//:io_grpc_grpc_testing", 14 | "@maven2//:com_google_inject_guice", 15 | "@maven2//:org_junit_jupiter_junit_jupiter_api", 16 | "//proto/task:task_service_java_proto", 17 | "//proto/user:user_java_proto", 18 | "//java/com/task:task_service_impl", 19 | "//java/com/task:authenticated_interceptor", 20 | "//java/com/task:constants", 21 | "//java/com/util:fake_service_module", 22 | "//java/com/user/util:jwt_util", 23 | ], 24 | 25 | ) 26 | -------------------------------------------------------------------------------- /nodejs/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "nodejs", 3 | "version": "0.1.0", 4 | "private": true, 5 | "devDependencies": { 6 | "@bazel/bazelisk": "latest", 7 | "@bazel/buildifier": "latest", 8 | "@bazel/ibazel": "latest" 9 | }, 10 | "scripts": { 11 | "build": "bazel build --platforms @build_bazel_rules_nodejs//toolchains/node:linux_amd64 //...", 12 | "run": "bazel run --platforms @build_bazel_rules_nodejs//toolchains/node:linux_amd64 //...", 13 | "test": "bazel test //..." 14 | }, 15 | "dependencies": { 16 | "@grpc/grpc-js": "1.1.7", 17 | "body-parser": "^1.19.1", 18 | "cookie-parser": "^1.4.6", 19 | "express": "^4.17.1", 20 | "fs": "^0.0.1-security", 21 | "google-protobuf": "^3.17.3", 22 | "grpc-tools": "1.9.1", 23 | "grpc-web": "1.2.1", 24 | "ts-protoc-gen": "^0.15.0" 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /spec_local/deployment_next_frapp.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: next-frapp 5 | namespace: slot 6 | spec: 7 | replicas: 1 8 | selector: 9 | matchLabels: 10 | app: next-frapp 11 | template: 12 | metadata: 13 | annotations: 14 | linkerd.io/inject: enabled 15 | labels: 16 | app: next-frapp 17 | spec: 18 | containers: 19 | - name: next-frapp 20 | image: YOUR_REGISTRY_NAME/slot_next_frapp 21 | ports: 22 | - containerPort: 80 23 | env: 24 | - name: "NEXT_PUBLIC_REACT_APP_BASE_URL" 25 | value: "http://{SUB_BASE_URL}" 26 | - name: "NEXTAUTH_URL" 27 | value: "http://{NEXTAUTH_URL}" 28 | - name: "GOOGLE_CLIENT_ID" 29 | value: "{GOOGLE_CLIENT_ID}" 30 | - name: "GOOGLE_CLIENT_SECRET" 31 | value: "{GOOGLE_CLIENT_SECRET}" 32 | -------------------------------------------------------------------------------- /java/com/user/db/UserDBHandler.java: -------------------------------------------------------------------------------- 1 | package com.user.db; 2 | 3 | import java.util.Optional; 4 | import java.util.List; 5 | import com.google.common.util.concurrent.ListenableFuture; 6 | import com.user.UserDBProto.CreateUserRequest; 7 | import com.user.UserDBProto.CreateUserResponse; 8 | import com.user.UserDBProto.DBFetchUserRequest; 9 | import com.user.UserDBProto.DBFetchUserResponse; 10 | import com.user.UserDBProto.UpdateRefreshTokenRequest; 11 | import com.user.UserDBProto.UpdateRefreshTokenResponse; 12 | 13 | /* 14 | DB Util for all user related operations 15 | */ 16 | public interface UserDBHandler { 17 | 18 | //fetch 19 | public ListenableFuture fetchUser(DBFetchUserRequest fetchUserRequest); 20 | 21 | //update 22 | public ListenableFuture updateRefreshToken(UpdateRefreshTokenRequest updateRefreshTokenRequest); 23 | public ListenableFuture createUser(CreateUserRequest createUserRequest); 24 | } 25 | -------------------------------------------------------------------------------- /java/test/com/user/management/BUILD: -------------------------------------------------------------------------------- 1 | load("//:java/test/java_testing_generator.bzl", "java_unit_test") 2 | 3 | package(default_visibility = ["//visibility:public"]) 4 | 5 | java_unit_test( 6 | name = "user_tests", 7 | pkg="com.user.management", 8 | srcs = glob(["*.java"]), 9 | deps = [ 10 | "@io_grpc_grpc_java//api", 11 | "@io_grpc_grpc_java//core:inprocess", 12 | "@io_grpc_grpc_java//stub", 13 | "@maven2//:io_grpc_grpc_testing", 14 | "@maven2//:com_google_inject_guice", 15 | "@maven2//:org_junit_jupiter_junit_jupiter_api", 16 | "//proto/user:user_management_service_java_proto", 17 | "//proto/user:user_java_proto", 18 | "//java/com/user/management:user_management_service_impl", 19 | "//java/com/util:fake_service_module", 20 | "//java/com/user/db:fake_user_db_handler", 21 | "//java/com/user/util:jwt_util", 22 | "//java/com/user/util:fake_sso_validator" 23 | ], 24 | 25 | ) 26 | -------------------------------------------------------------------------------- /proto/user/BUILD: -------------------------------------------------------------------------------- 1 | package(default_visibility = ["//visibility:public"]) 2 | 3 | load("@rules_proto//proto:defs.bzl", "proto_library") 4 | load("@rules_java//java:defs.bzl", "java_proto_library") 5 | load("@rules_proto_grpc//java:defs.bzl", "java_grpc_library") 6 | 7 | 8 | proto_library( 9 | name = "user_proto", 10 | srcs = ["user.proto"], 11 | ) 12 | 13 | java_proto_library( 14 | name = "user_java_proto", 15 | deps = [":user_proto"], 16 | ) 17 | 18 | proto_library( 19 | name = "user_db_proto", 20 | srcs = ["db.proto"], 21 | deps = [":user_proto"] 22 | ) 23 | 24 | java_proto_library( 25 | name = "user_db_java_proto", 26 | deps = [":user_db_proto"], 27 | ) 28 | 29 | 30 | proto_library( 31 | name = "user_management_service_proto", 32 | srcs = ["user_management_service.proto"], 33 | deps = [":user_proto"] 34 | ) 35 | 36 | java_grpc_library( 37 | name = "user_management_service_java_proto", 38 | protos = [ 39 | ":user_management_service_proto", 40 | ":user_proto" 41 | ], 42 | ) -------------------------------------------------------------------------------- /java/com/user/management/UserManagementService.java: -------------------------------------------------------------------------------- 1 | package com.user.management; 2 | 3 | import com.util.ServiceModule; 4 | import com.google.inject.Injector; 5 | import com.google.inject.Guice; 6 | import io.grpc.Server; 7 | import io.grpc.ServerBuilder; 8 | import com.util.SetupUtil; 9 | 10 | public class UserManagementService { 11 | 12 | public static void main(String[] args) throws Exception { 13 | 14 | Injector injector = Guice.createInjector(new ServiceModule()); 15 | 16 | System.out.println("user management service"); 17 | System.out.println(System.getenv("GOOGLE_CLIENT_ID")); 18 | System.out.println(System.getenv("MONGODB_URI")); 19 | System.out.println(System.getenv("MONGODB_DB")); 20 | 21 | Server pollServer = 22 | ServerBuilder 23 | .forPort(SetupUtil.DEFAULT_SERVICE_PORT) 24 | .addService(injector.getInstance(UserManagementServiceImpl.class)) 25 | .build(); 26 | pollServer.start(); 27 | pollServer.awaitTermination(); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /frontend/next_app/public/vercel.svg: -------------------------------------------------------------------------------- 1 | 3 | 4 | -------------------------------------------------------------------------------- /spec_local/ingress.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: networking.k8s.io/v1 2 | kind: Ingress 3 | metadata: 4 | name: ing 5 | namespace: slot 6 | annotations: 7 | nginx.ingress.kubernetes.io/rewrite-target: /$1 8 | spec: 9 | rules: 10 | - http: 11 | paths: 12 | - path: /frapp(?:/|$)?(.*) 13 | pathType: Prefix 14 | backend: 15 | service: 16 | name: frapp 17 | port: 18 | number: 8080 19 | - path: /gateway(?:/|$)?(.*) 20 | pathType: Prefix 21 | backend: 22 | service: 23 | name: gateway 24 | port: 25 | number: 8080 26 | - path: /gapi(?:/|$)?(.*) 27 | pathType: Prefix 28 | backend: 29 | service: 30 | name: envoy 31 | port: 32 | number: 8080 33 | - path: /?(.*) 34 | pathType: Prefix 35 | backend: 36 | service: 37 | name: next-frapp 38 | port: 39 | number: 8080 -------------------------------------------------------------------------------- /frontend/ReadMe.md: -------------------------------------------------------------------------------- 1 | ##Frontend Apps 2 | 3 | React app will be protectd by access token rotation 4 | 5 | For non k8s testing: 6 | 7 | **NOTE** : there may be complication's w/ user auth if running outside of k8s cluster, might need to add signal in backend to not validate 8 | 9 | 1) generate the proto js files, will need to install `protoc` 10 | - use `genProto` dir in react app `util` to store all the files 11 | 12 | Example proto generation: 13 | protoc proto/DIRECTORY_PATH*.proto --js_out=import_style=commonjs,binary:frontend/app/util/genProto/ --grpc-web_out=import_style=commonjs,mode=grpcwebtext:frontend/app/util/genProto/ 14 | 15 | 16 | 2)run local cluster & expose envoy service : spec_local/startup.sh && minikube service envoy -n slot 17 | 18 | 3)run webpack dev locally w/ webpack.config.js : LOCAL_URL=ENVOY_SERVICE_URL npm run dev 19 | 20 | #Run Dev server 21 | - ibazel run //frontend/app:dev_server --REACT_APP_BASE_URL= 22 | 23 | #Run Prod Server 24 | - http local : ibazel run //frontend/app:dev_server --REACT_APP_BASE_URL= 25 | - docker image gen : bazel run //frontend/app:prod_image 26 | 27 | 28 | -------------------------------------------------------------------------------- /proto/user/user_management_service.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package user_management; 4 | 5 | option java_package = "com.user.management"; 6 | option java_outer_classname = "UserManagementServiceProto"; 7 | 8 | import 'proto/user/user.proto'; 9 | 10 | service UserManagementService { 11 | 12 | // validate existing refresh then create new refresh/access token 13 | rpc RegenerateRefreshToken (RegenerateRefreshTokenRequest) returns (RegenerateRefreshTokenResponse); 14 | 15 | rpc SignIn ( SignInRequest) returns (SignInResponse); 16 | 17 | } 18 | 19 | message RegenerateRefreshTokenRequest{ 20 | 21 | oneof credential { 22 | user.UserRefreshToken existing_refresh_token = 1; 23 | } 24 | } 25 | 26 | message RegenerateRefreshTokenResponse{ 27 | user.UserRefreshToken refresh_token = 1; 28 | user.UserAccessToken access_token = 2; 29 | } 30 | 31 | message SignInRequest { 32 | string id_token = 1; 33 | 34 | enum Provider { 35 | UNKNOWN = 0; 36 | GOOGLE = 1; 37 | } 38 | 39 | Provider provider = 2; 40 | } 41 | 42 | message SignInResponse{ 43 | user.UserRefreshToken refresh_token = 1; 44 | user.UserAccessToken access_token = 2; 45 | } -------------------------------------------------------------------------------- /proto/user/db.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package db; 4 | 5 | option java_package = "com.user"; 6 | option java_outer_classname = "UserDBProto"; 7 | 8 | import 'proto/user/user.proto'; 9 | 10 | // fetches all user details by field 11 | message DBFetchUserRequest { 12 | 13 | oneof identifier { 14 | user.UserId user_id = 1; 15 | string email = 3; 16 | } 17 | 18 | enum FetchableFields{ 19 | USER_FIELD_UNKNOWN = 0; 20 | USER_FIELD_USER = 2; 21 | USER_FIELD_REFRESH_TOKEN = 1; 22 | 23 | } 24 | 25 | repeated FetchableFields field = 2; 26 | 27 | } 28 | 29 | message DBFetchUserResponse { 30 | 31 | user.UserRefreshToken refresh_token = 1; 32 | user.User user = 2; 33 | 34 | } 35 | 36 | message UpdateRefreshTokenRequest{ 37 | user.UserId user_id = 1; 38 | user.UserRefreshToken new_refresh_token = 2; 39 | } 40 | 41 | message UpdateRefreshTokenResponse{ 42 | user.UserRefreshToken new_refresh_token = 1; 43 | } 44 | 45 | message CreateUserRequest{ 46 | user.User user = 1; 47 | } 48 | 49 | message CreateUserResponse{ 50 | enum Error{ 51 | CREATION_ERROR_UNKOWN = 0; 52 | } 53 | 54 | Error error = 1; 55 | 56 | user.UserId created_user_id = 2; 57 | } -------------------------------------------------------------------------------- /frontend/app/nginx.conf: -------------------------------------------------------------------------------- 1 | user nginx; 2 | 3 | worker_processes auto; 4 | error_log /var/log/nginx/error.log warn; 5 | pid /var/run/nginx.pid; 6 | # since docker is one process per container, no need to run in background 7 | daemon off; 8 | 9 | events { 10 | worker_connections 1024; 11 | } 12 | http { 13 | include /etc/nginx/mime.types; 14 | default_type application/octet-stream; 15 | log_format main ‘$remote_addr — $remote_user [$time_local] “$request” ‘ 16 | ‘$status $body_bytes_sent “$http_referer” ‘ 17 | ‘“$http_user_agent” “$http_x_forwarded_for”’; 18 | access_log /var/log/nginx/access.log main; 19 | 20 | server { 21 | listen 80; 22 | 23 | location = /status { 24 | access_log off; 25 | default_type text/plain; 26 | add_header Content-Type text/plain; 27 | return 200 "good"; 28 | } 29 | 30 | location / { 31 | gzip off; 32 | root /dist/; 33 | index index.html; 34 | } 35 | 36 | location ~* \.(js|jpg|png|css)$ { 37 | root /dist/; 38 | } 39 | } 40 | 41 | sendfile on; 42 | keepalive_timeout 65; 43 | } 44 | -------------------------------------------------------------------------------- /frontend/app/nginxDev.conf: -------------------------------------------------------------------------------- 1 | user nginx; 2 | 3 | worker_processes auto; 4 | error_log /var/log/nginx/error.log warn; 5 | pid /var/run/nginx.pid; 6 | # since docker is one process per container, no need to run in background 7 | daemon off; 8 | 9 | events { 10 | worker_connections 1024; 11 | } 12 | http { 13 | include /etc/nginx/mime.types; 14 | default_type application/octet-stream; 15 | log_format main ‘$remote_addr — $remote_user [$time_local] “$request” ‘ 16 | ‘$status $body_bytes_sent “$http_referer” ‘ 17 | ‘“$http_user_agent” “$http_x_forwarded_for”’; 18 | access_log /var/log/nginx/access.log main; 19 | 20 | server { 21 | listen 80; 22 | 23 | location = /status { 24 | access_log off; 25 | default_type text/plain; 26 | add_header Content-Type text/plain; 27 | return 200 "good"; 28 | } 29 | 30 | location / { 31 | gzip off; 32 | root /devDist/; 33 | index index.html; 34 | } 35 | 36 | location ~* \.(js|jpg|png|css)$ { 37 | root /devDist/; 38 | } 39 | } 40 | 41 | sendfile on; 42 | keepalive_timeout 65; 43 | } 44 | -------------------------------------------------------------------------------- /java/com/user/management/BUILD: -------------------------------------------------------------------------------- 1 | load("@rules_java//java:defs.bzl", "java_library") 2 | load("@io_bazel_rules_docker//java:image.bzl", "java_image") 3 | 4 | package(default_visibility = ["//visibility:public"]) 5 | 6 | 7 | java_library( 8 | name = "user_management_service_impl", 9 | srcs = ["UserManagementServiceImpl.java"], 10 | deps = [ 11 | "@maven2//:com_google_inject_guice", 12 | "@io_grpc_grpc_java//api", 13 | "@com_google_api_grpc_proto_google_common_protos//:com_google_api_grpc_proto_google_common_protos", 14 | "//proto/user:user_management_service_java_proto", 15 | "//java/com/user/db:user_db_handler", 16 | "//proto/user:user_db_java_proto", 17 | "//java/com/user/util:jwt_util", 18 | "//java/com/user/util:sso_validator" 19 | ], 20 | ) 21 | 22 | 23 | java_image( 24 | name = "user_management_java_image", 25 | srcs = ["UserManagementService.java"], 26 | deps = [ 27 | "@maven2//:com_google_inject_guice", 28 | "@io_grpc_grpc_java//api", 29 | ":user_management_service_impl", 30 | "//java/com/util:service_module", 31 | "//java/com/util:setup-util", 32 | ], 33 | main_class = "com.user.management.UserManagementService", 34 | ) -------------------------------------------------------------------------------- /nodejs/BUILD: -------------------------------------------------------------------------------- 1 | package(default_visibility = ["//visibility:public"]) 2 | 3 | load("@rules_proto_grpc//js:defs.bzl", "js_grpc_node_library","js_proto_library") 4 | load("@io_bazel_rules_docker//nodejs:image.bzl", "nodejs_image") 5 | 6 | js_grpc_node_library( 7 | name = "task_service_proto", 8 | protos = [ 9 | "//proto/task:task_service_proto", 10 | "//proto/user:user_proto", 11 | ], 12 | deps_repo = "@nodejs_modules" 13 | ) 14 | 15 | js_grpc_node_library( 16 | name = "user_management_service_proto", 17 | protos = [ 18 | "//proto/user:user_management_service_proto", 19 | "//proto/user:user_proto" 20 | ], 21 | deps_repo = "@nodejs_modules" 22 | ) 23 | 24 | js_proto_library( 25 | name = "user_js_proto", 26 | protos = ["//proto/user:user_proto"], 27 | deps_repo = "@nodejs_modules" 28 | ) 29 | 30 | 31 | nodejs_image( 32 | name = "nodejs_gateway_image", 33 | entry_point = "gateway.js", 34 | data = [ 35 | "@nodejs_modules//express", 36 | "@nodejs_modules//cookie-parser", 37 | "@nodejs_modules//@grpc/grpc-js", 38 | "@nodejs_modules//body-parser", 39 | ":task_service_proto", 40 | ":user_management_service_proto", 41 | ":user_js_proto", 42 | ], 43 | ) 44 | -------------------------------------------------------------------------------- /java/com/util/BUILD: -------------------------------------------------------------------------------- 1 | load("@rules_java//java:defs.bzl", "java_library") 2 | 3 | package(default_visibility = ["//visibility:public"]) 4 | 5 | # The java_binary rule, like many others, has multiple outputs. We'll 6 | # see that in action shortly. 7 | 8 | java_library( 9 | name = "setup-util", 10 | srcs = ["SetupUtil.java"], 11 | deps = [ 12 | "@maven//:com_google_guava_guava", 13 | ], 14 | ) 15 | 16 | java_library( 17 | name = "service_module", 18 | srcs = ["ServiceModule.java"], 19 | deps = [ 20 | "@maven2//:com_google_inject_guice", 21 | "@maven2//:com_google_guava_guava", 22 | "//java/com/user/db:user_db_handler", 23 | "//java/com/user/db:main_user_db_handler", 24 | "//java/com/user/util:sso_validator", 25 | "//java/com/user/util:main_sso_validator", 26 | ], 27 | ) 28 | 29 | java_library( 30 | name = "fake_service_module", 31 | srcs = ["FakeServiceModule.java"], 32 | deps = [ 33 | "@maven2//:com_google_inject_guice", 34 | "@maven2//:com_google_guava_guava", 35 | "//java/com/user/db:user_db_handler", 36 | "//java/com/user/db:fake_user_db_handler", 37 | "//java/com/user/util:sso_validator", 38 | "//java/com/user/util:fake_sso_validator", 39 | ], 40 | ) 41 | -------------------------------------------------------------------------------- /spec_local/startup.sh: -------------------------------------------------------------------------------- 1 | kubectl config view >> spec_local/config; 2 | export CLUSTER_INGRESS_IP=""; 3 | export DOMAIN="slotlocal.com"; 4 | export MONGODB_URI="mongodb"; 5 | export MONGODB_DB="localSlot"; 6 | export GOOGLE_CLIENT_ID="ENTER_CLIENT_ID"; 7 | export GOOGLE_CLIENT_SECRET="ENTER_CLIENT_SECRET"; 8 | while [ -z $CLUSTER_INGRESS_IP ]; do echo "Waiting for minikube end point..."; CLUSTER_INGRESS_IP=$(minikube ip ); [ -z "$CLUSTER_INGRESS_IP" ] && sleep 10; done; echo "End point ready-" && echo $CLUSTER_INGRESS_IP; 9 | # bazel run //spec_local:controllers.apply; 10 | #linkerd check; 11 | bazel run //spec_local:ns_slot.apply; 12 | bazel run //spec_local:k8s_1.apply --define=CLUSTER_INGRESS_IP=$CLUSTER_INGRESS_IP --define=MONGODB_URI=$MONGODB_URI --define=MONGODB_DB=$MONGODB_DB --define=GOOGLE_CLIENT_ID=$GOOGLE_CLIENT_ID --define=GOOGLE_CLIENT_SECRET=$GOOGLE_CLIENT_SECRET --define=DOMAIN=$DOMAIN; 13 | bazel run //spec_local:k8s_2.apply --define=CLUSTER_INGRESS_IP=$CLUSTER_INGRESS_IP --define=MONGODB_URI=$MONGODB_URI --define=MONGODB_DB=$MONGODB_DB --define=GOOGLE_CLIENT_ID=$GOOGLE_CLIENT_ID --define=GOOGLE_CLIENT_SECRET=$GOOGLE_CLIENT_SECRET --define=DOMAIN=$DOMAIN; 14 | bazel run //spec_local:nodejs_deployment.apply --platforms=@build_bazel_rules_nodejs//toolchains/node:linux_amd64 --define=DOMAIN=$DOMAIN; -------------------------------------------------------------------------------- /java/com/user/util/BUILD: -------------------------------------------------------------------------------- 1 | package(default_visibility = ["//visibility:public"]) 2 | 3 | 4 | java_library( 5 | name = "jwt_util", 6 | srcs = ["JWTUtil.java"], 7 | deps = [ 8 | "@jjwt//:io_jsonwebtoken_jjwt", 9 | "@jjwt//:javax_xml_bind_jaxb_api", 10 | "//proto/user:user_java_proto", 11 | ] 12 | ) 13 | 14 | java_library( 15 | name = "sso_validator", 16 | srcs = ["SSOValidator.java"], 17 | deps = [ 18 | "@maven2//:com_google_api_client_google_api_client", 19 | "//proto/user:user_java_proto", 20 | ] 21 | ) 22 | 23 | java_library( 24 | name = "main_sso_validator", 25 | srcs = ["MainSSOValidator.java"], 26 | deps = [ 27 | ":sso_validator", 28 | "@maven2//:com_google_api_client_google_api_client", 29 | "@maven2//:com_google_http_client_google_http_client", 30 | "@maven2//:com_google_http_client_google_http_client_gson", 31 | "//proto/user:user_java_proto", 32 | ] 33 | ) 34 | 35 | java_library( 36 | name = "fake_sso_validator", 37 | srcs = ["FakeSSOValidator.java"], 38 | deps = [ 39 | ":sso_validator", 40 | "@maven2//:com_google_api_client_google_api_client", 41 | "@maven2//:com_google_inject_guice", 42 | "//proto/user:user_java_proto", 43 | ] 44 | ) -------------------------------------------------------------------------------- /java/com/user/db/BUILD: -------------------------------------------------------------------------------- 1 | load("@rules_java//java:defs.bzl","java_library") 2 | 3 | package(default_visibility = ["//visibility:public"]) 4 | 5 | java_library( 6 | name = "user_db_handler", 7 | srcs = ["UserDBHandler.java"], 8 | deps = [ 9 | "@maven2//:com_google_guava_guava", 10 | "//proto/user:user_java_proto", 11 | "//proto/user:user_db_java_proto", 12 | ], 13 | ) 14 | 15 | java_library( 16 | name = "fake_user_db_handler", 17 | srcs = ["FakeUserDBHandler.java"], 18 | deps = [ 19 | "@maven2//:com_google_guava_guava", 20 | "@maven2//:com_google_inject_guice", 21 | ":user_db_handler", 22 | "//proto/user:user_java_proto", 23 | "//proto/user:user_db_java_proto", 24 | ], 25 | ) 26 | 27 | java_library( 28 | name = "main_user_db_handler", 29 | srcs = ["MainUserDBHandler.java"], 30 | deps = [ 31 | "@maven2//:com_google_guava_guava", 32 | "@maven2//:com_google_inject_guice", 33 | "@maven2//:org_mongodb_mongodb_driver_sync", 34 | "@maven2//:org_mongodb_bson", 35 | "@maven2//:org_mongodb_mongo_java_driver", 36 | "@maven2//:org_slf4j_slf4j_simple", 37 | ":user_db_handler", 38 | "//proto/user:user_java_proto", 39 | "//proto/user:user_db_java_proto", 40 | "//java/com/user/util:jwt_util" 41 | ], 42 | ) -------------------------------------------------------------------------------- /frontend/app/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "app", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "src/index.js", 6 | "scripts": { 7 | "dev": "webpack serve --mode=development --open --hot" 8 | }, 9 | "author": "", 10 | "license": "ISC", 11 | "dependencies": { 12 | "@grpc/grpc-js": "^1.4.5", 13 | "@material-ui/core": "^4.11.4", 14 | "axios": "^0.24.0", 15 | "buffer": "^6.0.3", 16 | "copy-webpack-plugin": "^9.0.0", 17 | "crypto-browserify": "^3.12.0", 18 | "fs": "^0.0.1-security", 19 | "google-protobuf": "^3.17.3", 20 | "grpc-tools": "^1.11.2", 21 | "grpc-web": "^1.2.1", 22 | "http_server": "^1.0.12", 23 | "jsonwebtoken": "^8.5.1", 24 | "process": "^0.11.10", 25 | "react": "^17.0.2", 26 | "react-dom": "^17.0.2", 27 | "react-router-dom": "^6.2.1", 28 | "stream-browserify": "^3.0.0", 29 | "ts-protoc-gen": "^0.15.0", 30 | "util": "^0.12.4", 31 | "webpack-node-externals": "^3.0.0" 32 | }, 33 | "devDependencies": { 34 | "@babel/core": "^7.14.2", 35 | "@babel/preset-env": "^7.14.2", 36 | "@babel/preset-react": "^7.13.13", 37 | "babel-loader": "^8.2.2", 38 | "copy-webpack-plugin": "^9.0.0", 39 | "css-loader": "^5.2.4", 40 | "fs": "^0.0.1-security", 41 | "html-webpack-plugin": "^5.3.1", 42 | "style-loader": "^2.0.0", 43 | "webpack": "^5.37.0", 44 | "webpack-cli": "^4.7.0", 45 | "webpack-dev-server": "^3.11.2" 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /frontend/app/webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | var webpack = require('webpack'); 3 | const HtmlWebPackPlugin = require('html-webpack-plugin'); 4 | 5 | module.exports = { 6 | output: { 7 | path: path.resolve(__dirname, 'dist'), 8 | filename: 'bundle.js', 9 | }, 10 | resolve: { 11 | modules: [path.join(__dirname, 'src'), 'node_modules'], 12 | alias: { 13 | react: path.join(__dirname, 'node_modules', 'react'), 14 | }, 15 | fallback: { 16 | "stream": require.resolve("stream-browserify"), 17 | "crypto": require.resolve("crypto-browserify"), 18 | "buffer": require.resolve("buffer/"), 19 | "util": require.resolve("util/") 20 | } 21 | }, 22 | module: { 23 | rules: [ 24 | { 25 | test: /\.(js|jsx)$/, 26 | exclude: /node_modules/, 27 | use: { 28 | loader: 'babel-loader', 29 | }, 30 | }, 31 | { 32 | test: /\.css$/, 33 | use: [ 34 | { 35 | loader: 'style-loader', 36 | }, 37 | { 38 | loader: 'css-loader', 39 | }, 40 | ], 41 | }, 42 | ], 43 | }, 44 | plugins: [ 45 | new HtmlWebPackPlugin({ 46 | template: './src/index.html', 47 | favicon: './assets/frappe.png' 48 | }), 49 | new webpack.ProvidePlugin({ 50 | process: 'process/browser', 51 | }), 52 | new webpack.EnvironmentPlugin({ 53 | "process.env" : JSON.stringify(process.env), 54 | }), 55 | ], 56 | }; -------------------------------------------------------------------------------- /java/com/task/TaskServiceImpl.java: -------------------------------------------------------------------------------- 1 | package com.task; 2 | 3 | import static java.util.stream.Collectors.toList; 4 | 5 | import java.util.Collections; 6 | import java.lang.Integer; 7 | import java.util.List; 8 | import java.util.Optional; 9 | import java.util.stream.Stream; 10 | import java.util.concurrent.Callable; 11 | import java.util.concurrent.Executor; 12 | import com.google.inject.Inject; 13 | import com.google.rpc.Status; 14 | import com.google.common.base.Function; 15 | import com.google.common.util.concurrent.AsyncFunction; 16 | import com.google.common.util.concurrent.Futures; 17 | import com.google.common.util.concurrent.ListenableFuture; 18 | import com.google.common.util.concurrent.MoreExecutors; 19 | import io.grpc.protobuf.StatusProto; 20 | import com.google.rpc.Code; 21 | import io.grpc.stub.StreamObserver; 22 | import com.task.TaskServiceProto.ActionRequest; 23 | import com.task.TaskServiceProto.ActionResponse; 24 | 25 | public class TaskServiceImpl extends TaskServiceGrpc.TaskServiceImplBase { 26 | 27 | @Override 28 | public void someAction(ActionRequest req, StreamObserver responseObserver) { 29 | 30 | //get userid from context : Constants.USER_ID_CTX_KEY.get() 31 | 32 | responseObserver.onNext( 33 | ActionResponse.newBuilder() 34 | .addResultData("result data 1") 35 | .addResultData("result data 2") 36 | .addResultData("result data 3") 37 | .build()); 38 | 39 | responseObserver.onCompleted(); 40 | 41 | } 42 | 43 | } -------------------------------------------------------------------------------- /frontend/app/webpack.dev.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const HtmlWebPackPlugin = require('html-webpack-plugin'); 3 | var webpack = require('webpack'); 4 | 5 | module.exports = { 6 | mode: 'production', 7 | output: { 8 | path: path.resolve(__dirname, 'devDist'), 9 | filename: '[name].bundle.js', 10 | }, 11 | resolve: { 12 | modules: [ 13 | path.join(process.cwd(), 'external/frapp_modules/node_modules'), 14 | ], 15 | fallback: { 16 | "stream": require.resolve("stream-browserify"), 17 | "crypto": require.resolve("crypto-browserify"), 18 | "buffer": require.resolve("buffer/"), 19 | "util": require.resolve("util/") 20 | } 21 | }, 22 | entry : { 23 | index: [path.join(process.cwd(), "frontend/app/src/index.js")], 24 | }, 25 | module: { 26 | rules: [ 27 | { 28 | test: /\.(js|jsx)$/, 29 | exclude: /node_modules/, 30 | use: { 31 | loader: 'babel-loader', 32 | }, 33 | }, 34 | { 35 | test: /\.css$/, 36 | use: [ 37 | { 38 | loader: 'style-loader', 39 | }, 40 | { 41 | loader: 'css-loader', 42 | }, 43 | ], 44 | }, 45 | ], 46 | }, 47 | plugins: [ 48 | new HtmlWebPackPlugin({ 49 | template: './frontend/app/src/index.html', 50 | }), 51 | new webpack.ProvidePlugin({ 52 | process: 'process/browser', 53 | }), 54 | new webpack.EnvironmentPlugin({ 55 | "process.env" : JSON.stringify(process.env), 56 | }), 57 | ], 58 | }; -------------------------------------------------------------------------------- /java/com/user/util/MainSSOValidator.java: -------------------------------------------------------------------------------- 1 | package com.user.util; 2 | 3 | import java.util.Collections; 4 | import java.util.Optional; 5 | import com.user.UserProto.User; 6 | import com.user.UserProto.UserId; 7 | import com.google.api.client.googleapis.auth.oauth2.GoogleIdToken; 8 | import com.google.api.client.googleapis.auth.oauth2.GoogleIdToken.Payload; 9 | import com.google.api.client.googleapis.auth.oauth2.GoogleIdTokenVerifier; 10 | import com.google.api.client.http.javanet.NetHttpTransport; 11 | import com.google.api.client.json.gson.GsonFactory; 12 | 13 | public class MainSSOValidator implements SSOValidator { 14 | 15 | @Override 16 | public Optional validateGoogleIdToken(String idToken){ 17 | 18 | try{ 19 | GoogleIdTokenVerifier verifier = 20 | new GoogleIdTokenVerifier.Builder(new NetHttpTransport(), new GsonFactory()) 21 | .setAudience(Collections.singletonList(System.getenv("GOOGLE_CLIENT_ID"))) 22 | .build(); 23 | GoogleIdToken googleIdToken = verifier.verify(idToken); 24 | 25 | if (idToken != null) { 26 | Payload payload = googleIdToken.getPayload(); 27 | 28 | System.out.println(payload); 29 | 30 | return Optional.of(User.newBuilder() 31 | .setGoogleUserId((String) payload.getSubject()) 32 | .setName((String) payload.get("name")) 33 | .setEmail(payload.getEmail()) 34 | .setProfileUrl((String) payload.get("picture")) 35 | .setLocale((String) payload.get("locale")) 36 | .build()); 37 | } 38 | } 39 | catch(Exception e){ 40 | } 41 | return Optional.empty(); 42 | } 43 | } -------------------------------------------------------------------------------- /frontend/app/webpack.prod.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const HtmlWebPackPlugin = require('html-webpack-plugin'); 3 | var webpack = require('webpack'); 4 | 5 | module.exports = { 6 | mode: 'production', 7 | output: { 8 | path: path.resolve(__dirname, 'dist'), 9 | filename: '[name].bundle.js', 10 | publicPath: '/frapp' 11 | }, 12 | resolve: { 13 | modules: [ 14 | path.join(process.cwd(), 'external/frapp_modules/node_modules'), 15 | ], 16 | fallback: { 17 | "stream": require.resolve("stream-browserify"), 18 | "crypto": require.resolve("crypto-browserify"), 19 | "buffer": require.resolve("buffer/"), 20 | "util": require.resolve("util/") 21 | } 22 | }, 23 | entry : { 24 | index: [path.join(process.cwd(), "frontend/app/src/index.js")], 25 | }, 26 | module: { 27 | rules: [ 28 | { 29 | test: /\.(js|jsx)$/, 30 | exclude: /node_modules/, 31 | use: { 32 | loader: 'babel-loader', 33 | }, 34 | }, 35 | { 36 | test: /\.css$/, 37 | use: [ 38 | { 39 | loader: 'style-loader', 40 | }, 41 | { 42 | loader: 'css-loader', 43 | }, 44 | ], 45 | }, 46 | ], 47 | }, 48 | plugins: [ 49 | new HtmlWebPackPlugin({ 50 | template: './frontend/app/src/index.html', 51 | favicon: './frontend/app/assets/frappe.png' 52 | }), 53 | new webpack.ProvidePlugin({ 54 | process: 'process/browser', 55 | }), 56 | new webpack.DefinePlugin({ 57 | "process.env" : JSON.stringify(process.env), 58 | }), 59 | ], 60 | }; -------------------------------------------------------------------------------- /frontend/next_app/README.md: -------------------------------------------------------------------------------- 1 | # Next.js + Tailwind CSS Example 2 | 3 | This example shows how to use [Tailwind CSS](https://tailwindcss.com/) [(v2.2)](https://blog.tailwindcss.com/tailwindcss-2-2) with Next.js. It follows the steps outlined in the official [Tailwind docs](https://tailwindcss.com/docs/guides/nextjs). 4 | 5 | It uses the new [`Just-in-Time Mode`](https://tailwindcss.com/docs/just-in-time-mode) for Tailwind CSS. 6 | 7 | ## Preview 8 | 9 | Preview the example live on [StackBlitz](http://stackblitz.com/): 10 | 11 | [![Open in StackBlitz](https://developer.stackblitz.com/img/open_in_stackblitz.svg)](https://stackblitz.com/github/vercel/next.js/tree/canary/examples/with-tailwindcss) 12 | 13 | ## Deploy your own 14 | 15 | Deploy the example using [Vercel](https://vercel.com?utm_source=github&utm_medium=readme&utm_campaign=next-example): 16 | 17 | [![Deploy with Vercel](https://vercel.com/button)](https://vercel.com/new/git/external?repository-url=https://github.com/vercel/next.js/tree/canary/examples/with-tailwindcss&project-name=with-tailwindcss&repository-name=with-tailwindcss) 18 | 19 | ## How to use 20 | 21 | Execute [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app) with [npm](https://docs.npmjs.com/cli/init) or [Yarn](https://yarnpkg.com/lang/en/docs/cli/create/) to bootstrap the example: 22 | 23 | ```bash 24 | npx create-next-app --example with-tailwindcss with-tailwindcss-app 25 | # or 26 | yarn create next-app --example with-tailwindcss with-tailwindcss-app 27 | ``` 28 | 29 | Deploy it to the cloud with [Vercel](https://vercel.com/new?utm_source=github&utm_medium=readme&utm_campaign=next-example) ([Documentation](https://nextjs.org/docs/deployment)). 30 | -------------------------------------------------------------------------------- /frontend/app/src/HelloWorld.js: -------------------------------------------------------------------------------- 1 | import React, {useState, useEffect} from 'react'; 2 | import Button from '@material-ui/core/Button'; 3 | import {getUrl, getTaskServiceProto, getTaskServiceClient} from './protoHelper'; 4 | import useAuthData from './hooks/useAuthData' 5 | import {useNavigate} from 'react-router-dom' 6 | 7 | const taskServiceProto = getTaskServiceProto() 8 | const client = getTaskServiceClient(); 9 | 10 | 11 | const HelloWorld = () => { 12 | 13 | const [dataList, setDataList] = useState([]); 14 | const [loadingState, accessToken] = useAuthData(); 15 | const navigate = useNavigate(); 16 | 17 | useEffect(() => { 18 | 19 | }); 20 | 21 | function getData(){ 22 | var request = new taskServiceProto.ActionRequest(); 23 | 24 | client.someAction(request, {"slot-a-tkn": accessToken},(err, data) => { 25 | if(err){ 26 | console.log(err) 27 | return 28 | } 29 | console.log("testgrpc response") 30 | setDataList(data.getResultDataList()) 31 | }) 32 | } 33 | 34 | function getDataList(){ 35 | return dataList.map(data =>
{data}
) 36 | } 37 | 38 | return ( 39 |
40 |

React App - Private and within user session

41 | 46 | 47 | 50 | 51 |
52 | {getDataList()} 53 |
54 |
55 | ); 56 | }; 57 | 58 | export default HelloWorld; -------------------------------------------------------------------------------- /frontend/next_app/BUILD: -------------------------------------------------------------------------------- 1 | package(default_visibility = ["//visibility:public"]) 2 | 3 | load("@io_bazel_rules_docker//nodejs:image.bzl", "nodejs_image") 4 | load("@build_bazel_rules_nodejs//:index.bzl", "nodejs_binary") 5 | load("@io_bazel_rules_docker//container:container.bzl", "container_image") 6 | load("@rules_proto_grpc//js:defs.bzl", "js_grpc_web_library") 7 | 8 | NPM_PACKAGES = [ 9 | "@frapp_next_modules//:node_modules", 10 | ] 11 | 12 | filegroup( 13 | name = "srcs", 14 | srcs = glob(["pages/**"]) 15 | + glob(["components/**"]) 16 | + glob(["util/**"]) 17 | + glob(["public/**"]) 18 | ) 19 | 20 | 21 | nodejs_binary( 22 | name = "next", 23 | data = NPM_PACKAGES, 24 | entry_point = "@frapp_next_modules//:node_modules/next/dist/bin/next", 25 | ) 26 | 27 | genrule( 28 | name = "prod_build", 29 | tools = [":next"], 30 | srcs = NPM_PACKAGES + [ 31 | ":srcs", 32 | "next.config.js", 33 | "postcss.config.js", 34 | "tailwind.config.js", 35 | ], 36 | cmd = " && ".join([ 37 | "cp -a frontend/next_app/ ./", 38 | "$(location next) build", 39 | "mkdir prodBuild", 40 | "mv external/frapp_next_modules/node_modules nextBuild public/ next.config.js tailwind.config.js prodBuild", 41 | "mv prodBuild $@", 42 | ]), 43 | outs = [ 44 | "prodBuild", 45 | ], 46 | ) 47 | 48 | 49 | container_image( 50 | name = "prod_image", 51 | base = "@node_base//image", 52 | files = [ 53 | ":prod_build", 54 | "entry.sh", 55 | ], 56 | cmd = "".join([ 57 | "ls;", 58 | "chmod +x ./entry.sh;", 59 | "./entry.sh;", 60 | "cd prodBuild/;", 61 | "ls;", 62 | "node_modules/next/dist/bin/next start;" 63 | ]), 64 | ) 65 | -------------------------------------------------------------------------------- /frontend/app/src/hooks/useAuthData.js: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect } from 'react'; 2 | import {gatewayUrl} from '../util/url' 3 | import axios from 'axios'; 4 | 5 | var jwt = require("jsonwebtoken"); 6 | 7 | export default function useAuthData(test = null){ 8 | 9 | const [accessToken, setAccessToken] = useState(null); 10 | 11 | // IN_PROGRESS, SUCCESS, FAILED 12 | const [loadingState, setLoadingState] = useState(null); 13 | 14 | 15 | console.log() 16 | // after initial render set loading state to IP 17 | useEffect(() => { 18 | console.log("useAuth initial") 19 | setLoadingState("IN_PROGRESS"); 20 | },[]) 21 | 22 | useEffect(() => { 23 | if(loadingState === 'IN_PROGRESS'){ 24 | console.log("useAuth fetch") 25 | console.log(gatewayUrl()) 26 | // make call to fetch new access token 27 | axios.post(gatewayUrl()+"/gateway/refresh") 28 | .then(res => { 29 | console.log(res.data) 30 | if(res.data.access_token != null){ 31 | setAccessToken(res.data.access_token); 32 | }else{ 33 | setLoadingState("FAILED") 34 | } 35 | }) 36 | .catch(function (error){ 37 | setLoadingState("FAILED") 38 | }) 39 | } 40 | }, [loadingState]) 41 | 42 | useEffect(() =>{ 43 | console.log("useAuth ac") 44 | console.log(accessToken) 45 | if(accessToken != null){ 46 | console.log("useAuth set access") 47 | setLoadingState("SUCCESS"); 48 | console.log((jwt.decode(accessToken)['iat'] - new Date().getTime()/1000 - 200) * 10) 49 | // parse access token iat, refetch 200 millis before expiration 50 | setTimeout( 51 | (jwt.decode(accessToken)['iat'] - new Date().getTime()/1000 - 200) * 100 52 | ,() => setLoadingState("IN_PROGRESS")) 53 | } 54 | }, [accessToken]) 55 | 56 | return [loadingState, accessToken]; 57 | } -------------------------------------------------------------------------------- /java/com/task/BUILD: -------------------------------------------------------------------------------- 1 | load("@rules_java//java:defs.bzl", "java_library") 2 | load("@io_bazel_rules_docker//java:image.bzl", "java_image") 3 | 4 | package(default_visibility = ["//visibility:public"]) 5 | 6 | 7 | java_library( 8 | name = "constants", 9 | srcs = ["Constants.java"], 10 | deps = [ 11 | "@io_grpc_grpc_java//context", 12 | "@io_grpc_grpc_java//api", 13 | "@com_google_api_grpc_proto_google_common_protos//:com_google_api_grpc_proto_google_common_protos", 14 | "//proto/user:user_java_proto", 15 | ], 16 | ) 17 | 18 | java_library( 19 | name = "authenticated_interceptor", 20 | srcs = ["AuthenticatedInterceptor.java"], 21 | deps = [ 22 | ":constants", 23 | "@io_grpc_grpc_java//api", 24 | "@io_grpc_grpc_java//context", 25 | "@com_google_api_grpc_proto_google_common_protos//:com_google_api_grpc_proto_google_common_protos", 26 | "@com_google_protobuf_javalite//:protobuf_javalite", 27 | "//java/com/user/util:jwt_util", 28 | "//proto/user:user_java_proto", 29 | ], 30 | ) 31 | 32 | java_library( 33 | name = "task_service_impl", 34 | srcs = ["TaskServiceImpl.java"], 35 | deps = [ 36 | "@maven2//:com_google_inject_guice", 37 | "@io_grpc_grpc_java//api", 38 | "@com_google_api_grpc_proto_google_common_protos//:com_google_api_grpc_proto_google_common_protos", 39 | ":constants", 40 | "//proto/task:task_service_java_proto", 41 | ], 42 | ) 43 | 44 | 45 | java_image( 46 | name = "task_java_image", 47 | srcs = ["TaskService.java"], 48 | deps = [ 49 | "@maven2//:com_google_inject_guice", 50 | "@io_grpc_grpc_java//api", 51 | ":task_service_impl", 52 | ":authenticated_interceptor", 53 | "//java/com/util:service_module", 54 | "//java/com/util:setup-util", 55 | ], 56 | main_class = "com.task.TaskService", 57 | ) -------------------------------------------------------------------------------- /frontend/next_app/pages/api/auth/[...nextauth].js: -------------------------------------------------------------------------------- 1 | import NextAuth from "next-auth" 2 | import GoogleProvider from "next-auth/providers/google" 3 | import axios from 'axios'; 4 | import Cookies from 'cookies'; 5 | import {getUrl, omitAuthCalls} from '../../../util/protoHelper' 6 | 7 | 8 | const Auth = (req, res) => { 9 | const cookies = new Cookies(req, res) 10 | 11 | return ( 12 | NextAuth(req, res, { 13 | site: process.env.NEXTAUTH_URL, 14 | providers: [ 15 | GoogleProvider({ 16 | clientId: (process.env.GOOGLE_CLIENT_ID ? process.env.GOOGLE_CLIENT_ID : "PH_GOOGLE_CLIENT_ID" ) , 17 | clientSecret:(process.env.GOOGLE_CLIENT_SECRET ? process.env.GOOGLE_CLIENT_SECRET : "PH_GOOGLE_CLIENT_SECRET" ) 18 | }), 19 | // ...add more providers here 20 | ], 21 | callbacks: { 22 | async signIn({ user, account, profile, email, credentials }) { 23 | console.log('sign in start') 24 | console.log(getUrl()) 25 | if(!omitAuthCalls()){ 26 | return axios.post(getUrl()+"/gateway/signIn", {"id_token": account.id_token}) 27 | .then(res => { 28 | cookies.set("slot_refresh", res.data.refresh_token, {httpOnly: true}) 29 | return true}) 30 | .catch(err => {console.log(err); return false}) 31 | }else{ 32 | return true 33 | } 34 | }, 35 | async session({ session, user, token }) { 36 | console.log("session ") 37 | console.log(session) 38 | session.user.picture = token.picture 39 | return session 40 | }, 41 | async jwt({ token, user, account, profile, isNewUser }) { 42 | console.log("jwt ") 43 | console.log(token) 44 | return token 45 | } 46 | }, 47 | secret : "kV1i6y9v+bWLAcZeqKapTPQGjh6VjgGP8pa8RBvilpQ=", 48 | })) 49 | } 50 | 51 | export default Auth -------------------------------------------------------------------------------- /java/com/user/util/JWTUtil.java: -------------------------------------------------------------------------------- 1 | package com.user.util; 2 | 3 | import java.util.Optional; 4 | import java.util.Date; 5 | import io.jsonwebtoken.JwtParser; 6 | import io.jsonwebtoken.Jws; 7 | import io.jsonwebtoken.Claims; 8 | import io.jsonwebtoken.Jwts; 9 | import io.jsonwebtoken.SignatureAlgorithm; 10 | import com.user.UserProto.UserId; 11 | import com.user.UserProto.UserRefreshToken; 12 | import com.user.UserProto.UserAccessToken; 13 | 14 | public class JWTUtil{ 15 | 16 | public final static String REFRESH_SIGNING_KEY = "refresh_slot_secret_value"; 17 | public final static String ACCESS_SIGNING_KEY = "access_slot_secret_value"; 18 | 19 | public static Optional validateRefreshToken(UserRefreshToken jwtToken){ 20 | return parseJWTForUserId(jwtToken.getData(), REFRESH_SIGNING_KEY); 21 | } 22 | 23 | public static Optional validateAccessToken(UserAccessToken accessToken){ 24 | return parseJWTForUserId(accessToken.getData(), ACCESS_SIGNING_KEY); 25 | } 26 | 27 | public static UserRefreshToken generateNewRefreshToken(UserId userId){ 28 | return UserRefreshToken.newBuilder().setData( 29 | Jwts.builder() 30 | .setSubject(String.valueOf(userId.getId())) 31 | .setIssuedAt(new Date()) 32 | .signWith(SignatureAlgorithm.HS256, REFRESH_SIGNING_KEY) 33 | .compact() 34 | ).build(); 35 | } 36 | 37 | public static UserAccessToken generateNewAccessToken(UserId userId){ 38 | return UserAccessToken.newBuilder().setData( 39 | Jwts.builder() 40 | .setSubject(String.valueOf(userId.getId())) 41 | .setIssuedAt(new Date()) 42 | .signWith(SignatureAlgorithm.HS256, ACCESS_SIGNING_KEY) 43 | .compact() 44 | ).build(); 45 | } 46 | 47 | private static Optional parseJWTForUserId(String token, String secret){ 48 | JwtParser parser = Jwts.parser().setSigningKey(secret); 49 | Optional userId = Optional.empty(); 50 | 51 | try { 52 | Jws claims = parser.parseClaimsJws(token.trim()); 53 | userId = Optional.of( 54 | UserId.newBuilder().setId( 55 | Integer.parseInt(claims.getBody().getSubject()) 56 | ).build()); 57 | } catch (Exception e) {} 58 | 59 | return userId; 60 | 61 | } 62 | } -------------------------------------------------------------------------------- /frontend/envoy/envoy.yaml: -------------------------------------------------------------------------------- 1 | admin: 2 | access_log_path: /tmp/admin_access.log 3 | address: 4 | socket_address: { address: 0.0.0.0, port_value: 9901 } 5 | 6 | static_resources: 7 | listeners: 8 | - name: listener_0 9 | address: 10 | socket_address: { address: 0.0.0.0, port_value: 80 } 11 | filter_chains: 12 | - filters: 13 | - name: envoy.filters.network.http_connection_manager 14 | typed_config: 15 | "@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager 16 | codec_type: auto 17 | stat_prefix: ingress_http 18 | route_config: 19 | name: local_route 20 | virtual_hosts: 21 | - name: local_service 22 | domains: ["*"] 23 | request_headers_to_add: [] 24 | routes: 25 | - match: { prefix: "/" } 26 | route: 27 | cluster: task_service 28 | timeout: 0s 29 | max_stream_duration: 30 | grpc_timeout_header_max: 0s 31 | cors: 32 | allow_origin_string_match: 33 | - prefix: "*" 34 | allow_methods: GET, PUT, DELETE, POST, OPTIONS 35 | allow_headers: keep-alive,user-agent,cache-control,content-type,content-transfer-encoding,custom-header-1,slot-a-tkn,x-accept-content-transfer-encoding,x-accept-response-streaming,x-user-agent,x-grpc-web,grpc-timeout,Cookie 36 | max_age: "1728000" 37 | allow_credentials: true 38 | expose_headers: slot-a-tkn,custom-header-1,grpc-status,grpc-message 39 | http_filters: 40 | - name: envoy.filters.http.grpc_web 41 | - name: envoy.filters.http.cors 42 | - name: envoy.filters.http.router 43 | clusters: 44 | - name: task_service 45 | connect_timeout: 0.25s 46 | type: logical_dns 47 | http2_protocol_options: {} 48 | lb_policy: round_robin 49 | load_assignment: 50 | cluster_name: cluster_0 51 | endpoints: 52 | - lb_endpoints: 53 | - endpoint: 54 | address: 55 | socket_address: 56 | address: task 57 | port_value: 80 -------------------------------------------------------------------------------- /java/com/task/AuthenticatedInterceptor.java: -------------------------------------------------------------------------------- 1 | package com.task; 2 | 3 | import io.grpc.Status; 4 | import io.grpc.ForwardingServerCall.SimpleForwardingServerCall; 5 | import io.grpc.Metadata; 6 | import io.grpc.Context; 7 | import io.grpc.Contexts; 8 | import io.grpc.ServerCall; 9 | import io.grpc.ServerCallHandler; 10 | import io.grpc.ServerInterceptor; 11 | import io.grpc.StatusRuntimeException; 12 | import java.util.Optional; 13 | import java.util.Arrays; 14 | import com.user.UserProto.UserId; 15 | import com.user.UserProto.UserAccessToken; 16 | import com.user.util.JWTUtil; 17 | import com.google.protobuf.InvalidProtocolBufferException; 18 | 19 | /** 20 | * A interceptor to authenticate protected paths. 21 | */ 22 | public class AuthenticatedInterceptor implements ServerInterceptor { 23 | 24 | @Override 25 | public ServerCall.Listener interceptCall( 26 | ServerCall call, 27 | final Metadata requestHeaders, 28 | ServerCallHandler next) { 29 | 30 | Context ctx = Context.current(); 31 | 32 | // parse cookie for custom value 33 | String cookieValue = requestHeaders.get(Constants.COOKIE_SLOT_METADATA_KEY); 34 | if(cookieValue == null){ 35 | throw new StatusRuntimeException(Status.INVALID_ARGUMENT.withDescription("Invalid cookie")); 36 | } 37 | Optional customHeaderValue = 38 | Arrays.asList(cookieValue.split(";")) 39 | .stream() 40 | .filter(cookie -> cookie.split("=")[0].trim().equals(Constants.CUSTOM_SLOT_HEADER_COOKIE_KEY)).findAny(); 41 | 42 | if(customHeaderValue.isPresent()){ 43 | ctx = ctx.withValue(Constants.CUSTOM_HEADER_CTX_KEY, customHeaderValue.get().split("=")[1].trim()); 44 | } 45 | 46 | System.out.println("intercept"); 47 | System.out.println(requestHeaders.toString()); 48 | 49 | //parse metadata for auth header 50 | String accessTokenData = requestHeaders.get(Constants.ACCESS_SLOT_METADATA_KEY); 51 | 52 | if(accessTokenData == null){ 53 | throw new StatusRuntimeException(Status.INVALID_ARGUMENT.withDescription("Missing access token")); 54 | } 55 | 56 | Optional userId = JWTUtil.validateAccessToken(UserAccessToken.newBuilder().setData(accessTokenData).build()); 57 | if(!userId.isPresent()){ 58 | throw new StatusRuntimeException(Status.INVALID_ARGUMENT.withDescription("Invalid access token")); 59 | } 60 | 61 | ctx = ctx.withValue(Constants.USER_ID_CTX_KEY, userId.get()); 62 | return Contexts.interceptCall(ctx, call, requestHeaders, next); 63 | } 64 | 65 | } -------------------------------------------------------------------------------- /frontend/app/BUILD: -------------------------------------------------------------------------------- 1 | package(default_visibility = ["//visibility:public"]) 2 | 3 | load("@frapp_modules//webpack:index.bzl", "webpack") 4 | load("@frapp_modules//http_server:index.bzl", "http_server") 5 | load("@io_bazel_rules_docker//nodejs:image.bzl", "nodejs_image") 6 | load("@build_bazel_rules_nodejs//:index.bzl", "nodejs_binary") 7 | load("@io_bazel_rules_docker//container:container.bzl", "container_image") 8 | load("@rules_proto_grpc//js:defs.bzl", "js_grpc_web_library") 9 | 10 | NPM_PACKAGES = [ 11 | "@frapp_modules//:node_modules", 12 | ] 13 | 14 | js_grpc_web_library( 15 | name = "task_grpc_js", 16 | protos = [ 17 | "//proto/task:task_service_proto", 18 | "//proto/user:user_proto" 19 | ], 20 | deps_repo = "@frapp_modules", 21 | ) 22 | 23 | filegroup( 24 | name = "srcs", 25 | srcs = glob(["src/**"], exclude = [ "src/genProto/**" ] ) + glob(["assets/**"]) 26 | ) 27 | 28 | 29 | nodejs_binary( 30 | name = "webpack_cli", 31 | data = NPM_PACKAGES, 32 | entry_point = "@frapp_modules//:node_modules/webpack-cli/bin/cli.js", 33 | ) 34 | 35 | genrule( 36 | name = "prod_build", 37 | tools = [":webpack_cli"], 38 | srcs = NPM_PACKAGES + [ 39 | ":srcs", 40 | "src/index.html", 41 | "webpack.prod.js", 42 | ".babelrc", 43 | ":task_grpc_js" 44 | ], 45 | cmd = " && ".join([ 46 | "cp -a bazel-out/darwin-fastbuild/bin/frontend/app/task_grpc_js_pb/ frontend/app/src/", 47 | #"ls -R frontend | grep ':$$' | sed -e 's/:$$//' -e 's/[^-][^\/]*\//--/g' -e 's/^/ /' -e 's/-/|/'", 48 | "$(execpath webpack_cli) --mode=production --config=$(location webpack.prod.js)", 49 | "mv frontend/app/dist $@", 50 | ]), 51 | outs = [ 52 | "dist", 53 | ], 54 | ) 55 | 56 | genrule( 57 | name = "dev_build", 58 | tools = [":webpack_cli"], 59 | srcs = NPM_PACKAGES + [ 60 | ":srcs", 61 | "src/index.html", 62 | "webpack.dev.js", 63 | ".babelrc", 64 | ":task_grpc_js", 65 | ], 66 | cmd = " && ".join([ 67 | "cp -a bazel-out/darwin-fastbuild/bin/frontend/app/task_grpc_js_pb/ frontend/app/src/", 68 | "$(execpath webpack_cli) --mode=development --config=$(location webpack.dev.js)", 69 | "mv frontend/app/devDist $@", 70 | ]), 71 | outs = [ 72 | "devDist", 73 | ], 74 | ) 75 | 76 | http_server( 77 | name = "prod_server", 78 | data = [ 79 | ":prod_build" 80 | ], 81 | # args = ["frontend/app/dist/"], 82 | ) 83 | 84 | http_server( 85 | name = "dev_server", 86 | data = [ 87 | ":dev_build" 88 | ], 89 | args = ["frontend/app/devDist"], 90 | ) 91 | 92 | 93 | container_image( 94 | name = "dev_image", 95 | base = "@nginx_base//image", 96 | files = [ 97 | ":dev_build", 98 | "nginxDev.conf", 99 | "entry.sh", 100 | ], 101 | cmd = "".join([ 102 | "chmod +x ./entry.sh;", 103 | "./entry.sh;", 104 | "nginx -c /nginxDev.conf;" 105 | ]), 106 | ) 107 | 108 | container_image( 109 | name = "prod_image", 110 | base = "@nginx_base//image", 111 | files = [ 112 | ":prod_build", 113 | "nginx.conf", 114 | "entry.sh", 115 | ], 116 | cmd = "".join([ 117 | "chmod +x ./entry.sh;", 118 | "./entry.sh;", 119 | "nginx -c /nginx.conf;" 120 | ]), 121 | ) 122 | -------------------------------------------------------------------------------- /nodejs/gateway.js: -------------------------------------------------------------------------------- 1 | const grpc = require('@grpc/grpc-js'); 2 | const express = require('express') 3 | const cookieParser = require('cookie-parser'); 4 | var bodyParser = require('body-parser') 5 | 6 | const taskServiceProto = require('./task_service_proto_pb/proto/task/task_service_grpc_pb.js') 7 | const taskProto = require('./task_service_proto_pb/proto/task/task_service_pb.js') 8 | 9 | const userProto = require('./user_js_proto_pb/proto/user/user_pb.js') 10 | const userServiceProto = require('./user_management_service_proto_pb/proto/user/user_management_service_pb.js') 11 | const userServiceClientProto = require('./user_management_service_proto_pb/proto/user/user_management_service_grpc_pb.js') 12 | 13 | 14 | // create application/json parser 15 | var jsonParser = bodyParser.json() 16 | 17 | const app = express() 18 | const port = 3000 19 | const REFRESH_COOKIE_KEY = "slot_refresh" 20 | const SITE_COOKIES = [ 21 | REFRESH_COOKIE_KEY, 22 | "next-auth.session-token", 23 | "next-auth.callback-url", 24 | "next-auth.csrf-token", 25 | ] 26 | 27 | 28 | var client = new taskServiceProto.TaskServiceClient("task:80", grpc.credentials.createInsecure()); 29 | var userClient = new userServiceClientProto.UserManagementServiceClient("user-management:80", grpc.credentials.createInsecure()); 30 | 31 | app.use(cookieParser()); 32 | 33 | app.get('/', (req, res) => { 34 | res.send('this is the GATEWAY!') 35 | }) 36 | 37 | var actionRequest = () => new taskProto.ActionRequest(); 38 | 39 | var refreshRequest = (existingRefreshToken) => 40 | new userServiceProto.RegenerateRefreshTokenRequest() 41 | .setExistingRefreshToken(new userProto.UserRefreshToken().setData(existingRefreshToken)); 42 | 43 | var signInRequest = (idToken) => 44 | new userServiceProto.SignInRequest() 45 | .setIdToken(idToken); 46 | 47 | 48 | app.get('/testgrpc', (req, res) => { 49 | var request = actionRequest() 50 | client.someAction(request, (err, data) => { 51 | if(err){ 52 | res.json({err: err}) 53 | return 54 | } 55 | res.cookie('custom_slot_header', 'fromTestGrpc',{httpOnly:true}) 56 | res.json({data : data.toObject()}) 57 | }) 58 | }) 59 | 60 | app.post('/refresh', (req, res) => { 61 | 62 | console.log("refresh") 63 | console.log(req.cookies) 64 | if(req.cookies[REFRESH_COOKIE_KEY] == null){ 65 | res.json({err:"Invalid refresh"}) 66 | console.log('invalid refresh') 67 | return 68 | } 69 | 70 | userClient.regenerateRefreshToken(refreshRequest(req.cookies[REFRESH_COOKIE_KEY]), (err, data) => { 71 | if(err){ 72 | res.json({err: err}) 73 | return 74 | } 75 | res.cookie(REFRESH_COOKIE_KEY, data.getRefreshToken().getData(),{httpOnly:true}); 76 | res.json({access_token: data.getAccessToken().getData()}) 77 | }) 78 | }) 79 | 80 | app.post('/signIn',jsonParser, (req, res) => { 81 | 82 | console.log("signin") 83 | 84 | if(req.body.id_token == null){ 85 | res.json({err:"Invalid Id Token"}) 86 | return 87 | } 88 | 89 | userClient.signIn(signInRequest(req.body.id_token), (err, data) => { 90 | if(err){ 91 | res.json({err: err}) 92 | return 93 | } 94 | res.json({ 95 | access_token: data.getAccessToken().getData(), 96 | refresh_token:data.getRefreshToken().getData()}) 97 | }) 98 | }) 99 | 100 | app.get('/signOut', (req, res) => { 101 | SITE_COOKIES.forEach(cook => res.clearCookie(cook)) 102 | res.redirect(200, process.env.REACT_APP_BASE_URL); 103 | }) 104 | 105 | app.listen(port, () => { 106 | console.log("gateway server running") 107 | }) 108 | 109 | 110 | function getRand(min, max) { 111 | return Math.trunc(Math.random() * (max - min) + min); 112 | } 113 | 114 | -------------------------------------------------------------------------------- /java/com/user/db/FakeUserDBHandler.java: -------------------------------------------------------------------------------- 1 | package com.user.db; 2 | 3 | import java.util.Optional; 4 | import java.util.Map; 5 | import java.util.HashMap; 6 | import java.util.List; 7 | import com.google.common.util.concurrent.ListenableFuture; 8 | import com.google.common.util.concurrent.Futures; 9 | import com.user.UserDBProto.CreateUserRequest; 10 | import com.user.UserDBProto.CreateUserResponse; 11 | import com.user.UserDBProto.DBFetchUserRequest; 12 | import com.user.UserDBProto.DBFetchUserRequest.FetchableFields; 13 | import com.user.UserDBProto.DBFetchUserResponse; 14 | import com.user.UserProto.UserRefreshToken; 15 | import com.user.UserProto.UserId; 16 | import com.user.UserProto.User; 17 | import com.user.UserDBProto.UpdateRefreshTokenRequest; 18 | import com.user.UserDBProto.UpdateRefreshTokenResponse; 19 | import com.google.inject.Singleton; 20 | 21 | @Singleton 22 | public class FakeUserDBHandler implements UserDBHandler { 23 | 24 | private int DEFAULT_CREATED_USER_ID = 999; 25 | 26 | private Map refreshTokens = new HashMap(); 27 | private Map emailToUser = new HashMap(); 28 | private Optional createdUser = Optional.empty(); 29 | private int createdUserId = DEFAULT_CREATED_USER_ID; 30 | 31 | @Override 32 | public ListenableFuture fetchUser(DBFetchUserRequest fetchUserRequest){ 33 | 34 | DBFetchUserResponse.Builder response = DBFetchUserResponse.newBuilder(); 35 | 36 | if(refreshTokens.containsKey(fetchUserRequest.getUserId()) && fetchUserRequest.getFieldList().contains(FetchableFields.USER_FIELD_REFRESH_TOKEN)){ 37 | response.setRefreshToken(refreshTokens.get(fetchUserRequest.getUserId())); 38 | }else if(emailToUser.containsKey(fetchUserRequest.getEmail()) && fetchUserRequest.getFieldList().contains(FetchableFields.USER_FIELD_USER)){ 39 | response.setUser(emailToUser.get(fetchUserRequest.getEmail())); 40 | } 41 | 42 | return Futures.immediateFuture(response.build()); 43 | } 44 | 45 | @Override 46 | public ListenableFuture updateRefreshToken(UpdateRefreshTokenRequest updateRefreshTokenRequest){ 47 | System.out.println("fake updateRefreshToken"); 48 | System.out.println(updateRefreshTokenRequest); 49 | refreshTokens.put(updateRefreshTokenRequest.getUserId(), updateRefreshTokenRequest.getNewRefreshToken()); 50 | 51 | return Futures.immediateFuture( 52 | UpdateRefreshTokenResponse.newBuilder() 53 | .setNewRefreshToken(updateRefreshTokenRequest.getNewRefreshToken()) 54 | .build()); 55 | 56 | } 57 | 58 | @Override 59 | public ListenableFuture createUser(CreateUserRequest createUserRequest){ 60 | UserId newUserId = UserId.newBuilder() 61 | .setId(createdUserId).build(); 62 | createdUser = 63 | Optional.of(createUserRequest.getUser().toBuilder() 64 | .setUserId(newUserId).build()); 65 | return Futures.immediateFuture(CreateUserResponse.newBuilder().setCreatedUserId(newUserId).build()); 66 | } 67 | 68 | 69 | public void clear(){ 70 | refreshTokens = new HashMap(); 71 | emailToUser = new HashMap(); 72 | createdUser = Optional.empty(); 73 | createdUserId = DEFAULT_CREATED_USER_ID; 74 | } 75 | 76 | public void setEmailToUser(String email, User user){ 77 | emailToUser.put(email, user); 78 | } 79 | 80 | public void setRefreshTokenForUserId(UserId userId, UserRefreshToken refreshToken){ 81 | refreshTokens.put(userId, refreshToken); 82 | } 83 | 84 | public UserRefreshToken getRefreshTokenForUserId(UserId userId){ 85 | return refreshTokens.get(userId); 86 | } 87 | 88 | public Optional getCreatedUser(){ 89 | return createdUser; 90 | } 91 | 92 | public void setCreatedUserId(int createdUserId){ 93 | this.createdUserId = createdUserId; 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /nodejs/.bazelrc: -------------------------------------------------------------------------------- 1 | # Common Bazel settings for JavaScript/NodeJS workspaces 2 | # This rc file is automatically discovered when Bazel is run in this workspace, 3 | # see https://docs.bazel.build/versions/master/guide.html#bazelrc 4 | # 5 | # The full list of Bazel options: https://docs.bazel.build/versions/master/command-line-reference.html 6 | 7 | # Cache action outputs on disk so they persist across output_base and bazel shutdown (eg. changing branches) 8 | build --disk_cache=~/.cache/bazel-disk-cache 9 | 10 | # Bazel will create symlinks from the workspace directory to output artifacts. 11 | # Build results will be placed in a directory called "dist/bin" 12 | # Other directories will be created like "dist/testlogs" 13 | # Be aware that this will still create a bazel-out symlink in 14 | # your project directory, which you must exclude from version control and your 15 | # editor's search path. 16 | build --symlink_prefix=dist/ 17 | # To disable the symlinks altogether (including bazel-out) you can use 18 | # build --symlink_prefix=/ 19 | # however this makes it harder to find outputs. 20 | 21 | # Specifies desired output mode for running tests. 22 | # Valid values are 23 | # 'summary' to output only test status summary 24 | # 'errors' to also print test logs for failed tests 25 | # 'all' to print logs for all tests 26 | # 'streamed' to output logs for all tests in real time 27 | # (this will force tests to be executed locally one at a time regardless of --test_strategy value). 28 | test --test_output=errors 29 | 30 | # Support for debugging NodeJS tests 31 | # Add the Bazel option `--config=debug` to enable this 32 | # --test_output=streamed 33 | # Stream stdout/stderr output from each test in real-time. 34 | # See https://docs.bazel.build/versions/master/user-manual.html#flag--test_output for more details. 35 | # --test_strategy=exclusive 36 | # Run one test at a time. 37 | # --test_timeout=9999 38 | # Prevent long running tests from timing out 39 | # See https://docs.bazel.build/versions/master/user-manual.html#flag--test_timeout for more details. 40 | # --nocache_test_results 41 | # Always run tests 42 | # --node_options=--inspect-brk 43 | # Pass the --inspect-brk option to all tests which enables the node inspector agent. 44 | # See https://nodejs.org/de/docs/guides/debugging-getting-started/#command-line-options for more details. 45 | # --define=VERBOSE_LOGS=1 46 | # Rules will output verbose logs if the VERBOSE_LOGS environment variable is set. `VERBOSE_LOGS` will be passed to 47 | # `nodejs_binary` and `nodejs_test` via the default value of the `default_env_vars` attribute of those rules. 48 | # --compilation_mode=dbg 49 | # Rules may change their build outputs if the compilation mode is set to dbg. For example, 50 | # mininfiers such as terser may make their output more human readable when this is set. Rules will pass `COMPILATION_MODE` 51 | # to `nodejs_binary` executables via the actions.run env attribute. 52 | # See https://docs.bazel.build/versions/master/user-manual.html#flag--compilation_mode for more details. 53 | test:debug --test_output=streamed --test_strategy=exclusive --test_timeout=9999 --nocache_test_results --define=VERBOSE_LOGS=1 54 | # Use bazel run with `--config=debug` to turn on the NodeJS inspector agent. 55 | # The node process will break before user code starts and wait for the debugger to connect. 56 | run:debug --define=VERBOSE_LOGS=1 -- --node_options=--inspect-brk 57 | # The following option will change the build output of certain rules such as terser and may not be desirable in all cases 58 | build:debug --compilation_mode=dbg 59 | 60 | # Turn off legacy external runfiles 61 | # This prevents accidentally depending on this feature, which Bazel will remove. 62 | build --nolegacy_external_runfiles 63 | 64 | # Turn on --incompatible_strict_action_env which was on by default 65 | # in Bazel 0.21.0 but turned off again in 0.22.0. Follow 66 | # https://github.com/bazelbuild/bazel/issues/7026 for more details. 67 | # This flag is needed to so that the bazel cache is not invalidated 68 | # when running bazel via `yarn bazel`. 69 | # See https://github.com/angular/angular/issues/27514. 70 | build --incompatible_strict_action_env 71 | run --incompatible_strict_action_env 72 | 73 | # When running `bazel coverage` --instrument_test_targets needs to be set in order to 74 | # collect coverage information from test targets 75 | coverage --instrument_test_targets 76 | 77 | # Load any settings specific to the current user. 78 | # .bazelrc.user should appear in .gitignore so that settings are not shared with team members 79 | # This needs to be last statement in this 80 | # config, as the user configuration should be able to overwrite flags from this file. 81 | # See https://docs.bazel.build/versions/master/best-practices.html#bazelrc 82 | # (Note that we use .bazelrc.user so the file appears next to .bazelrc in directory listing, 83 | # rather than user.bazelrc as suggested in the Bazel docs) 84 | try-import %workspace%/.bazelrc.user 85 | 86 | -------------------------------------------------------------------------------- /spec_local/BUILD: -------------------------------------------------------------------------------- 1 | load("@io_bazel_rules_k8s//k8s:object.bzl", "k8s_object") 2 | load("@io_bazel_rules_k8s//k8s:objects.bzl", "k8s_objects") 3 | 4 | k8s_object( 5 | name = "deployment_gateway", 6 | kind = "deployment", 7 | kubeconfig = ":config", 8 | cluster = "minikube", 9 | template = ":deployment_gateway.yaml", 10 | substitutions = { 11 | "{SUB_BASE_URL}": "$(DOMAIN)", 12 | }, 13 | images = { 14 | "YOUR_REGISTRY_NAME/slot_gateway": "//nodejs:nodejs_gateway_image" 15 | } 16 | ) 17 | 18 | k8s_object( 19 | name = "deployment_task", 20 | kind = "deployment", 21 | kubeconfig = ":config", 22 | cluster = "minikube", 23 | template = ":deployment_task.yaml", 24 | images = { 25 | "YOUR_REGISTRY_NAME/slot_task": "//java/com/task:task_java_image" 26 | }, 27 | ) 28 | 29 | k8s_object( 30 | name = "deployment_user_management", 31 | kind = "deployment", 32 | kubeconfig = ":config", 33 | cluster = "minikube", 34 | template = ":deployment_user_management.yaml", 35 | substitutions = { 36 | "{GOOGLE_CLIENT_ID}": "$(GOOGLE_CLIENT_ID)", 37 | "{MONGODB_URI}": "$(MONGODB_URI)", 38 | "{MONGODB_DB}": "$(MONGODB_DB)", 39 | }, 40 | images = { 41 | "YOUR_REGISTRY_NAME/slot_user_management": "//java/com/user/management:user_management_java_image" 42 | }, 43 | ) 44 | 45 | k8s_object( 46 | name = "deployment_frapp", 47 | kind = "deployment", 48 | kubeconfig = ":config", 49 | cluster = "minikube", 50 | template = ":deployment_frapp.yaml", 51 | substitutions = { 52 | "{SUB_BASE_URL}": "$(DOMAIN)", 53 | }, 54 | images = { 55 | "YOUR_REGISTRY_NAME/slot_frapp": "//frontend/app:prod_image" 56 | }, 57 | ) 58 | 59 | k8s_object( 60 | name = "deployment_next_frapp", 61 | kind = "deployment", 62 | kubeconfig = ":config", 63 | cluster = "minikube", 64 | template = ":deployment_next_frapp.yaml", 65 | substitutions = { 66 | "{SUB_BASE_URL}": "$(CLUSTER_INGRESS_IP)", 67 | "{NEXTAUTH_URL}" : "$(DOMAIN)", 68 | "{GOOGLE_CLIENT_ID}": "$(GOOGLE_CLIENT_ID)", 69 | "{GOOGLE_CLIENT_SECRET}": "$(GOOGLE_CLIENT_SECRET)", 70 | }, 71 | images = { 72 | "YOUR_REGISTRY_NAME/slot_next_frapp": "//frontend/next_app:prod_image" 73 | }, 74 | ) 75 | 76 | k8s_object( 77 | name = "deployment_envoy", 78 | kind = "deployment", 79 | kubeconfig = ":config", 80 | cluster = "minikube", 81 | template = ":deployment_envoy.yaml", 82 | images = { 83 | "YOUR_REGISTRY_NAME/slot_envoy": "//frontend/envoy:envoy_image" 84 | }, 85 | ) 86 | 87 | k8s_object( 88 | name = "deployment_mongodb", 89 | kind = "deployment", 90 | kubeconfig = ":config", 91 | cluster = "minikube", 92 | template = ":deployment_mongodb.yaml", 93 | images = { 94 | "YOUR_REGISTRY_NAME/slot_mongodb": "//local:db_local_image" 95 | }, 96 | ) 97 | 98 | k8s_object( 99 | name = "service_gateway", 100 | template = ":service_gateway.yaml", 101 | kubeconfig = ":config", 102 | cluster = "minikube", 103 | kind = "service" 104 | ) 105 | 106 | k8s_object( 107 | name = "service_task", 108 | template = ":service_task.yaml", 109 | kubeconfig = ":config", 110 | cluster = "minikube", 111 | kind = "service" 112 | ) 113 | 114 | k8s_object( 115 | name = "service_user_management", 116 | template = ":service_user_management.yaml", 117 | kubeconfig = ":config", 118 | cluster = "minikube", 119 | kind = "service" 120 | ) 121 | 122 | 123 | k8s_object( 124 | name = "service_frapp", 125 | template = ":service_frapp.yaml", 126 | kubeconfig = ":config", 127 | cluster = "minikube", 128 | kind = "service" 129 | ) 130 | 131 | k8s_object( 132 | name = "service_next_frapp", 133 | template = ":service_next_frapp.yaml", 134 | kubeconfig = ":config", 135 | cluster = "minikube", 136 | kind = "service" 137 | ) 138 | 139 | k8s_object( 140 | name = "service_envoy", 141 | template = ":service_envoy.yaml", 142 | kubeconfig = ":config", 143 | cluster = "minikube", 144 | kind = "service" 145 | ) 146 | 147 | k8s_object( 148 | name = "service_mongodb", 149 | template = ":service_mongodb.yaml", 150 | kubeconfig = ":config", 151 | cluster = "minikube", 152 | kind = "service" 153 | ) 154 | 155 | k8s_object( 156 | name = "ingress", 157 | template = ":ingress.yaml", 158 | kubeconfig = ":config", 159 | cluster = "minikube", 160 | kind = "ingress" 161 | ) 162 | 163 | k8s_object( 164 | name = "ns_slot", 165 | template = ":ns_slot.yaml", 166 | kubeconfig = ":config", 167 | cluster = "minikube", 168 | kind = "namespace" 169 | ) 170 | 171 | k8s_objects( 172 | name = "non_nodejs_deployment", 173 | objects = [ 174 | ":deployment_task", 175 | ":deployment_envoy", 176 | "deployment_mongodb", 177 | ":deployment_frapp", 178 | ":deployment_next_frapp", 179 | ] 180 | ) 181 | 182 | k8s_objects( 183 | name = "nodejs_deployment", 184 | objects = [ 185 | ":deployment_gateway", 186 | ] 187 | ) 188 | 189 | k8s_objects( 190 | name = "services", 191 | objects = [ 192 | ":service_gateway", 193 | ":service_task", 194 | ":service_user_management", 195 | ":service_frapp", 196 | ":service_next_frapp", 197 | ":service_envoy", 198 | ":service_mongodb" 199 | ] 200 | ) 201 | 202 | k8s_objects( 203 | name = "k8s_1", 204 | objects = [ 205 | ":ns_slot", 206 | ":non_nodejs_deployment", 207 | ":services", 208 | ":ingress" 209 | ] 210 | ) 211 | 212 | k8s_objects( 213 | name = "k8s_2", 214 | objects = [ 215 | ":deployment_user_management", 216 | ] 217 | ) -------------------------------------------------------------------------------- /java/test/com/task/TaskServiceTest.java: -------------------------------------------------------------------------------- 1 | package com.task; 2 | 3 | import static org.junit.Assert.assertEquals; 4 | import static org.junit.jupiter.api.Assertions.assertThrows; 5 | 6 | import java.util.ArrayList; 7 | import org.junit.Test; 8 | import org.junit.Before; 9 | import org.junit.After; 10 | import org.junit.Rule; 11 | import org.junit.runner.RunWith; 12 | import org.junit.runners.JUnit4; 13 | import io.grpc.ManagedChannel; 14 | import io.grpc.testing.GrpcCleanupRule; 15 | import io.grpc.StatusRuntimeException; 16 | import io.grpc.inprocess.InProcessServerBuilder; 17 | import io.grpc.inprocess.InProcessChannelBuilder; 18 | import com.google.inject.Inject; 19 | import com.google.inject.Injector; 20 | import com.google.inject.Guice; 21 | import com.google.common.collect.Iterables; 22 | import com.task.TaskServiceGrpc; 23 | import com.user.util.JWTUtil; 24 | import com.task.TaskServiceProto.ActionRequest; 25 | import com.task.TaskServiceProto.ActionResponse; 26 | import com.task.TaskServiceImpl; 27 | import com.task.Constants; 28 | import com.task.AuthenticatedInterceptor; 29 | import io.grpc.Metadata; 30 | import io.grpc.stub.MetadataUtils; 31 | import com.user.UserProto.UserId; 32 | import com.util.FakeServiceModule; 33 | 34 | @RunWith(JUnit4.class) 35 | public class TaskServiceTest { 36 | 37 | private final UserId USER_ID = UserId.newBuilder().setId(11111).build(); 38 | private final String COOKIE_VALUE = "TEST1233"; 39 | 40 | protected Injector injector = Guice.createInjector(new FakeServiceModule()); 41 | 42 | @Rule 43 | public GrpcCleanupRule grpcCleanup = new GrpcCleanupRule(); 44 | 45 | private String serverName = InProcessServerBuilder.generateName(); 46 | private InProcessServerBuilder serverBuilder = InProcessServerBuilder 47 | .forName(serverName).directExecutor(); 48 | private InProcessChannelBuilder channelBuilder = InProcessChannelBuilder 49 | .forName(serverName).directExecutor(); 50 | 51 | private Metadata metadata; 52 | 53 | @Before 54 | public void setup() { 55 | injector.injectMembers(this); 56 | 57 | //setup metadata 58 | String cookieValue = Constants.CUSTOM_SLOT_HEADER_COOKIE_KEY + "="+ COOKIE_VALUE; 59 | metadata = new Metadata(); 60 | metadata.put(Constants.COOKIE_SLOT_METADATA_KEY, cookieValue); 61 | metadata.put(Constants.ACCESS_SLOT_METADATA_KEY, JWTUtil.generateNewAccessToken(USER_ID).getData()); 62 | } 63 | 64 | @After 65 | public void cleanUp() { 66 | } 67 | 68 | @Test 69 | public void shouldReturnOneRecurringGeneratedEntry() throws Exception { 70 | 71 | TaskServiceGrpc.TaskServiceBlockingStub blockingStub = createBlockingStub(); 72 | ActionResponse response = 73 | blockingStub.someAction(ActionRequest.getDefaultInstance()); 74 | 75 | assertEquals(response.getResultData(0), "result data 1"); 76 | } 77 | 78 | 79 | // Authenticate Tests 80 | @Test 81 | public void shouldSetCustomAndUserIdHeader() throws Exception { 82 | 83 | String cookieValue = Constants.CUSTOM_SLOT_HEADER_COOKIE_KEY + "=TEST1233"; 84 | metadata = new Metadata(); 85 | metadata.put(Constants.COOKIE_SLOT_METADATA_KEY, cookieValue); 86 | metadata.put(Constants.ACCESS_SLOT_METADATA_KEY, JWTUtil.generateNewAccessToken(USER_ID).getData()); 87 | 88 | TaskServiceGrpc.TaskServiceBlockingStub blockingStub = createBlockingStub(); 89 | 90 | ActionResponse response = 91 | blockingStub.someAction(ActionRequest.getDefaultInstance()); 92 | 93 | assertEquals(response.getResultData(0), "result data 1"); 94 | } 95 | 96 | @Test 97 | public void shouldThrow_missingAccesstoken() throws Exception { 98 | 99 | String cookieValue = Constants.CUSTOM_SLOT_HEADER_COOKIE_KEY + "=TEST1233"; 100 | metadata = new Metadata(); 101 | metadata.put(Constants.COOKIE_SLOT_METADATA_KEY, cookieValue); 102 | 103 | TaskServiceGrpc.TaskServiceBlockingStub blockingStub = createBlockingStub(); 104 | StatusRuntimeException exception = assertThrows(StatusRuntimeException.class, 105 | () -> blockingStub.someAction(ActionRequest.getDefaultInstance())); 106 | 107 | assertEquals(exception.getStatus().getDescription(), "Missing access token"); 108 | } 109 | 110 | @Test 111 | public void shouldThrow_invalidAccesstoken() throws Exception { 112 | 113 | String cookieValue = Constants.CUSTOM_SLOT_HEADER_COOKIE_KEY + "=TEST1233"; 114 | metadata = new Metadata(); 115 | metadata.put(Constants.COOKIE_SLOT_METADATA_KEY, cookieValue); 116 | String accessToken = "fdsf"; 117 | metadata.put(Constants.ACCESS_SLOT_METADATA_KEY, accessToken); 118 | 119 | TaskServiceGrpc.TaskServiceBlockingStub blockingStub = createBlockingStub(); 120 | StatusRuntimeException exception = assertThrows(StatusRuntimeException.class, 121 | () -> blockingStub.someAction(ActionRequest.getDefaultInstance())); 122 | 123 | assertEquals(exception.getStatus().getDescription(), "Invalid access token"); 124 | } 125 | 126 | private TaskServiceGrpc.TaskServiceBlockingStub createBlockingStub() throws Exception{ 127 | // Add the service to the in-process server. 128 | grpcCleanup.register( 129 | serverBuilder.addService(injector.getInstance(TaskServiceImpl.class)).intercept(new AuthenticatedInterceptor()).build().start()); 130 | ManagedChannel channel = grpcCleanup.register( 131 | channelBuilder.maxInboundMessageSize(1024).build()); 132 | 133 | return MetadataUtils.attachHeaders(TaskServiceGrpc.newBlockingStub(channel),metadata); 134 | 135 | } 136 | 137 | 138 | } -------------------------------------------------------------------------------- /java/com/user/db/MainUserDBHandler.java: -------------------------------------------------------------------------------- 1 | package com.user.db; 2 | 3 | import static com.mongodb.client.model.Filters.eq; 4 | import static com.mongodb.client.model.Sorts.ascending; 5 | 6 | import java.util.Optional; 7 | import java.util.List; 8 | import java.util.Map; 9 | import java.util.Arrays; 10 | import java.util.HashMap; 11 | import com.google.common.util.concurrent.ListenableFuture; 12 | import com.google.common.util.concurrent.Futures; 13 | import com.user.UserDBProto.CreateUserRequest; 14 | import com.user.UserDBProto.CreateUserResponse; 15 | import com.user.UserDBProto.DBFetchUserRequest; 16 | import com.user.UserDBProto.DBFetchUserRequest.FetchableFields; 17 | import com.user.UserDBProto.DBFetchUserResponse; 18 | import com.user.UserProto.UserRefreshToken; 19 | import com.user.UserProto.UserId; 20 | import com.user.UserProto.User; 21 | import com.user.UserDBProto.UpdateRefreshTokenRequest; 22 | import com.user.UserDBProto.UpdateRefreshTokenResponse; 23 | import com.mongodb.client.MongoClient; 24 | import com.mongodb.client.MongoClients; 25 | import com.mongodb.client.MongoCursor; 26 | import com.mongodb.client.MongoCollection; 27 | import com.mongodb.client.MongoDatabase; 28 | import com.mongodb.client.model.UpdateOptions; 29 | import com.mongodb.client.model.Updates; 30 | import com.mongodb.client.result.UpdateResult; 31 | import com.mongodb.MongoException; 32 | import org.bson.Document; 33 | import org.bson.conversions.Bson; 34 | 35 | public class MainUserDBHandler implements UserDBHandler { 36 | 37 | private final String COLLECTION_REFRESH = "refresh"; 38 | private final String COLLECTION_USERS = "users"; 39 | MongoClient mongoClient; 40 | 41 | public MainUserDBHandler(){ 42 | mongoClient = MongoClients.create("mongodb://"+System.getenv("MONGODB_URI")+":80"); 43 | 44 | } 45 | 46 | @Override 47 | public ListenableFuture fetchUser(DBFetchUserRequest fetchUserRequest){ 48 | 49 | System.out.println("fetchUser"); 50 | System.out.println(fetchUserRequest); 51 | //TODO: add is-set checks for fetchable fields 52 | boolean isRefreshRequest = fetchUserRequest.getFieldList().contains(FetchableFields.USER_FIELD_REFRESH_TOKEN); 53 | 54 | DBFetchUserResponse.Builder builder = DBFetchUserResponse.newBuilder(); 55 | MongoCollection collection = getCollection(isRefreshRequest ? COLLECTION_REFRESH : COLLECTION_USERS); 56 | 57 | Document result; 58 | if(fetchUserRequest.hasUserId()){ 59 | result = collection.find(eq("user_id", fetchUserRequest.getUserId().getId())) 60 | .first(); 61 | }else{ 62 | result = collection.find(eq("email", fetchUserRequest.getEmail())) 63 | .first(); 64 | } 65 | 66 | System.out.println(result); 67 | 68 | if(result != null){ 69 | if(isRefreshRequest){ 70 | builder.setRefreshToken(UserRefreshToken.newBuilder().setData(result.get("token", String.class)).build()); 71 | }else{ 72 | builder.setUser(constructUser(result)); 73 | } 74 | } 75 | 76 | return Futures.immediateFuture(builder.build()); 77 | } 78 | 79 | @Override 80 | public ListenableFuture updateRefreshToken(UpdateRefreshTokenRequest updateRefreshTokenRequest){ 81 | 82 | Document query = new Document().append("user_id", updateRefreshTokenRequest.getUserId().getId()); 83 | Bson updates = Updates.combine( 84 | Updates.set("token", updateRefreshTokenRequest.getNewRefreshToken().getData())); 85 | UpdateOptions options = new UpdateOptions().upsert(true); 86 | 87 | try { 88 | getCollection(COLLECTION_REFRESH).updateOne(query, updates, options); 89 | return Futures.immediateFuture( 90 | UpdateRefreshTokenResponse.newBuilder() 91 | .setNewRefreshToken(updateRefreshTokenRequest.getNewRefreshToken()) 92 | .build()); 93 | 94 | } catch (MongoException me) { 95 | return Futures.immediateFuture(UpdateRefreshTokenResponse.getDefaultInstance()); 96 | } 97 | } 98 | 99 | @Override 100 | public ListenableFuture createUser(CreateUserRequest createUserRequest){ 101 | 102 | try { 103 | 104 | MongoCollection collection = getCollection(COLLECTION_USERS); 105 | MongoCursor result = collection.find().sort(ascending("user_id")).limit(1).iterator(); 106 | System.out.println("createUser"); 107 | 108 | int createdUserId = (result.hasNext() ? result.next().get("user_id",Double.class).intValue() + 1 : 101); 109 | 110 | System.out.println(createdUserId); 111 | 112 | getCollection(COLLECTION_USERS).insertOne( 113 | new Document() 114 | .append("user_id", createdUserId) 115 | .append("name", createUserRequest.getUser().getName()) 116 | .append("email", createUserRequest.getUser().getEmail()) 117 | .append("google_user_id", createUserRequest.getUser().getGoogleUserId())); 118 | return Futures.immediateFuture(CreateUserResponse.newBuilder().setCreatedUserId(UserId.newBuilder().setId(createdUserId).build()).build()); 119 | } catch (Exception e) { 120 | System.out.println(e); 121 | return Futures.immediateFuture(CreateUserResponse.newBuilder().setError(CreateUserResponse.Error.CREATION_ERROR_UNKOWN).build()); 122 | } 123 | 124 | } 125 | 126 | private User constructUser(Document result){ 127 | return User.newBuilder() 128 | .setUserId( 129 | UserId.newBuilder() 130 | .setId(result.get("user_id",Integer.class)) 131 | .build()) 132 | .setName(result.get("name", String.class)) 133 | .setEmail(result.get("email", String.class)) 134 | .build(); 135 | } 136 | 137 | private MongoCollection getCollection(String collection){ 138 | MongoDatabase database = mongoClient.getDatabase(System.getenv("MONGODB_DB")); 139 | return database.getCollection(collection); 140 | } 141 | 142 | 143 | } 144 | -------------------------------------------------------------------------------- /WORKSPACE: -------------------------------------------------------------------------------- 1 | workspace(name = "slot" , 2 | # Map the @npm bazel workspace to the node_modules directory. 3 | # This lets Bazel use the same node_modules as other local tooling. 4 | managed_directories = { 5 | "@nodejs_modules": ["nodejs/node_modules"], 6 | "@frapp_modules": ["frontend/app/node_modules"], 7 | "@frapp_next_modules": ["frontend/next_app/node_modules"], 8 | }) 9 | 10 | # functions to get external libs 11 | load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive") 12 | 13 | load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive") 14 | http_archive( 15 | name = "rules_java", 16 | url = "https://github.com/bazelbuild/rules_java/releases/download/4.0.0/rules_java-4.0.0.tar.gz", 17 | sha256 = "34b41ec683e67253043ab1a3d1e8b7c61e4e8edefbcad485381328c934d072fe", 18 | ) 19 | 20 | load("@rules_java//java:repositories.bzl", "rules_java_dependencies", "rules_java_toolchains") 21 | rules_java_dependencies() 22 | rules_java_toolchains() 23 | 24 | 25 | # proto - grpc service ( should also get proto_rules dep ) 26 | http_archive( 27 | name = "rules_proto_grpc", 28 | sha256 = "8383116d4c505e93fd58369841814acc3f25bdb906887a2023980d8f49a0b95b", 29 | strip_prefix = "rules_proto_grpc-4.1.0", 30 | urls = ["https://github.com/rules-proto-grpc/rules_proto_grpc/archive/4.1.0.tar.gz"], 31 | ) 32 | 33 | load("@rules_proto_grpc//:repositories.bzl", "rules_proto_grpc_toolchains", "rules_proto_grpc_repos") 34 | rules_proto_grpc_toolchains() 35 | rules_proto_grpc_repos() 36 | 37 | load("@rules_proto_grpc//java:repositories.bzl", rules_proto_grpc_java_repos="java_repos") 38 | rules_proto_grpc_java_repos() 39 | 40 | load("@io_grpc_grpc_java//:repositories.bzl", "IO_GRPC_GRPC_JAVA_ARTIFACTS", "IO_GRPC_GRPC_JAVA_OVERRIDE_TARGETS", "grpc_java_repositories") 41 | 42 | grpc_java_repositories() 43 | 44 | # maven_install 45 | RULES_JVM_EXTERNAL_TAG = "4.0" 46 | RULES_JVM_EXTERNAL_SHA = "31701ad93dbfe544d597dbe62c9a1fdd76d81d8a9150c2bf1ecf928ecdf97169" 47 | 48 | http_archive( 49 | name = "rules_jvm_external", 50 | strip_prefix = "rules_jvm_external-%s" % RULES_JVM_EXTERNAL_TAG, 51 | sha256 = RULES_JVM_EXTERNAL_SHA, 52 | url = "https://github.com/bazelbuild/rules_jvm_external/archive/%s.zip" % RULES_JVM_EXTERNAL_TAG, 53 | ) 54 | 55 | load("@rules_jvm_external//:defs.bzl", "maven_install") 56 | 57 | 58 | maven_install( 59 | artifacts = IO_GRPC_GRPC_JAVA_ARTIFACTS, 60 | generate_compat_repositories = True, 61 | override_targets = IO_GRPC_GRPC_JAVA_OVERRIDE_TARGETS, 62 | repositories = [ 63 | "https://repo.maven.apache.org/maven2/", 64 | ], 65 | ) 66 | 67 | load("@maven//:compat.bzl", "compat_repositories") 68 | 69 | compat_repositories() 70 | 71 | #guice 72 | #grpc testing 73 | grpc_version = "1.27.0" 74 | guice_version = "4.1.0" 75 | maven_install( 76 | name = "maven2", 77 | artifacts = [ 78 | "io.grpc:grpc-testing:%s" % grpc_version, 79 | "com.google.guava:guava:30.0-jre", 80 | 'com.google.inject:guice:' + guice_version, 81 | 'org.junit.jupiter:junit-jupiter-api:5.8.1', 82 | "com.google.api-client:google-api-client:1.32.2", 83 | "org.mongodb:mongodb-driver-sync:4.4.0", 84 | "org.mongodb:bson:4.4.0", 85 | "org.mongodb:mongo-java-driver:3.12.7", 86 | "org.slf4j:slf4j-simple:1.7.32", 87 | ], 88 | repositories = [ 89 | "https://jcenter.bintray.com/", 90 | "https://repo1.maven.org/maven2", 91 | "https://maven.google.com", 92 | ], 93 | ) 94 | 95 | ## rules_docker 96 | http_archive( 97 | name = "io_bazel_rules_docker", 98 | sha256 = "59536e6ae64359b716ba9c46c39183403b01eabfbd57578e84398b4829ca499a", 99 | strip_prefix = "rules_docker-0.22.0", 100 | urls = ["https://github.com/bazelbuild/rules_docker/releases/download/v0.22.0/rules_docker-v0.22.0.tar.gz"], 101 | ) 102 | load( 103 | "@io_bazel_rules_docker//repositories:repositories.bzl", 104 | container_repositories = "repositories", 105 | ) 106 | container_repositories() 107 | load("@io_bazel_rules_docker//repositories:deps.bzl", container_deps = "deps") 108 | container_deps() 109 | 110 | # Load java_image rules to create java docker images to run grpc services 111 | load( 112 | "@io_bazel_rules_docker//java:image.bzl", 113 | _java_image_repos = "repositories", 114 | ) 115 | _java_image_repos() 116 | 117 | # Load the macro that allows you to customize the docker toolchain configuration. 118 | load("@io_bazel_rules_docker//toolchains/docker:toolchain.bzl", 119 | docker_toolchain_configure="toolchain_configure" 120 | ) 121 | 122 | docker_toolchain_configure( 123 | name = "docker_config", 124 | # abosolute path to credentials directory 125 | client_config="${ROOT}/credentials", 126 | docker_flags = [ 127 | "--tls", 128 | "--log-level=error", 129 | ], 130 | ) 131 | 132 | #java jsonwebtoken 133 | 134 | maven_install( 135 | name = "jjwt", 136 | artifacts = [ 137 | "io.jsonwebtoken:jjwt:0.9.0", 138 | "javax.xml.bind:jaxb-api:2.3.1" 139 | ], 140 | repositories = [ 141 | "https://jcenter.bintray.com/", 142 | "https://repo1.maven.org/maven2", 143 | "https://maven.google.com", 144 | ], 145 | ) 146 | 147 | #nodejs & js rules - already installed from proto grpc 148 | load("@rules_proto_grpc//js:repositories.bzl", rules_proto_grpc_js_repos="js_repos") 149 | rules_proto_grpc_js_repos() 150 | 151 | #installing nodejs rules 152 | http_archive( 153 | name = "build_bazel_rules_nodejs", 154 | sha256 = "f0f76a06fd6c10e8fb9a6cb9389fa7d5816dbecd9b1685063f89fb20dc6822f3", 155 | urls = ["https://github.com/bazelbuild/rules_nodejs/releases/download/4.5.1/rules_nodejs-4.5.1.tar.gz"], 156 | ) 157 | 158 | # The npm_install rule runs yarn anytime the package.json or package-lock.json file changes. 159 | # It also extracts any Bazel rules distributed in an npm package. 160 | load("@build_bazel_rules_nodejs//:index.bzl","yarn_install") 161 | yarn_install( 162 | # Name this npm so that Bazel Label references look like @npm//package 163 | name = "nodejs_modules", 164 | package_json = "//nodejs:package.json", 165 | yarn_lock = "//nodejs:yarn.lock", 166 | ) 167 | 168 | yarn_install( 169 | # Name this npm so that Bazel Label references look like @npm//package 170 | name = "npm", 171 | package_json = "//npm:package.json", 172 | yarn_lock = "//npm:yarn.lock", 173 | ) 174 | 175 | yarn_install( 176 | name = "frapp_modules", 177 | package_json = "//frontend/app:package.json", 178 | yarn_lock = "//frontend/app:yarn.lock" 179 | ) 180 | 181 | yarn_install( 182 | name = "frapp_next_modules", 183 | package_json = "//frontend/next_app:package.json", 184 | yarn_lock = "//frontend/next_app:yarn.lock" 185 | ) 186 | 187 | # Load nodejs_image rules to create java docker images to run grpc services 188 | load( 189 | "@io_bazel_rules_docker//nodejs:image.bzl", 190 | _nodejs_image_repos = "repositories", 191 | ) 192 | _nodejs_image_repos() 193 | 194 | 195 | # nginx docker base image 196 | load("@io_bazel_rules_docker//container:pull.bzl", "container_pull") 197 | 198 | container_pull( 199 | name = "nginx_base", 200 | registry = "index.docker.io", 201 | repository = "library/nginx", 202 | tag = "1.21.0-alpine", 203 | ) 204 | 205 | #envoy base image 206 | container_pull( 207 | name = "envoy_base", 208 | registry = "index.docker.io", 209 | repository = "envoyproxy/envoy", 210 | tag = "v1.18.3" 211 | ) 212 | 213 | #node base image 214 | container_pull( 215 | name = "node_base", 216 | registry = "index.docker.io", 217 | repository = "library/node", 218 | tag = "alpine3.14" 219 | ) 220 | 221 | #mongodb base image 222 | container_pull( 223 | name = "mongo_base", 224 | registry = "index.docker.io", 225 | repository = "library/mongo" 226 | ) 227 | 228 | # k8s - push images directly to cluster 229 | http_archive( 230 | name = "io_bazel_rules_k8s", 231 | strip_prefix = "rules_k8s-0.6", 232 | urls = ["https://github.com/bazelbuild/rules_k8s/archive/v0.6.tar.gz"], 233 | sha256 = "51f0977294699cd547e139ceff2396c32588575588678d2054da167691a227ef", 234 | ) 235 | 236 | load("@io_bazel_rules_k8s//k8s:k8s.bzl", "k8s_repositories") 237 | 238 | k8s_repositories() 239 | 240 | load("@io_bazel_rules_k8s//k8s:k8s_go_deps.bzl", k8s_go_deps = "deps") 241 | 242 | k8s_go_deps() 243 | -------------------------------------------------------------------------------- /java/test/com/user/management/UserManagementServiceTest.java: -------------------------------------------------------------------------------- 1 | package com.user.management; 2 | 3 | import static org.junit.Assert.assertEquals; 4 | import static org.junit.Assert.assertNotSame; 5 | import static org.junit.jupiter.api.Assertions.assertThrows; 6 | 7 | import java.util.concurrent.TimeUnit; 8 | import org.junit.Test; 9 | import org.junit.Before; 10 | import org.junit.After; 11 | import org.junit.Rule; 12 | import org.junit.runner.RunWith; 13 | import org.junit.runners.JUnit4; 14 | import io.grpc.StatusRuntimeException; 15 | import io.grpc.ManagedChannel; 16 | import io.grpc.testing.GrpcCleanupRule; 17 | import io.grpc.inprocess.InProcessServerBuilder; 18 | import io.grpc.inprocess.InProcessChannelBuilder; 19 | import com.google.inject.Inject; 20 | import com.google.inject.Injector; 21 | import com.google.inject.Guice; 22 | import com.google.common.collect.Iterables; 23 | import com.user.management.UserManagementServiceGrpc; 24 | import com.user.UserProto.UserId; 25 | import com.user.UserProto.UserAccessToken; 26 | import com.user.UserProto.User; 27 | import com.user.UserProto.UserRefreshToken; 28 | import com.user.management.UserManagementServiceProto.SignInRequest; 29 | import com.user.management.UserManagementServiceProto.SignInResponse; 30 | import com.user.management.UserManagementServiceProto.RegenerateRefreshTokenRequest; 31 | import com.user.management.UserManagementServiceProto.RegenerateRefreshTokenResponse; 32 | import com.user.util.FakeSSOValidator; 33 | import com.user.db.FakeUserDBHandler; 34 | import com.util.FakeServiceModule; 35 | import com.user.util.JWTUtil; 36 | import com.user.management.UserManagementServiceImpl; 37 | 38 | /** 39 | * User Management Service Test 40 | */ 41 | @RunWith(JUnit4.class) 42 | public class UserManagementServiceTest { 43 | 44 | private final String SAMPLE_SSO_ID_TOKEN = "sample-id-token"; 45 | private final UserId USER_ID = UserId.newBuilder().setId(11111).build(); 46 | private final String SAMPLE_USER_EMAIL = "smaple@user.com"; 47 | private final User SAMPLE_USER = 48 | User.newBuilder() 49 | .setUserId(USER_ID) 50 | .setName("sample user") 51 | .setEmail(SAMPLE_USER_EMAIL) 52 | .build(); 53 | private final User SAMPLE_USER_FROM_SSO = 54 | User.newBuilder() 55 | .setName("sample user sso") 56 | .setEmail(SAMPLE_USER_EMAIL) 57 | .build(); 58 | 59 | private Injector injector = Guice.createInjector(new FakeServiceModule()); 60 | private String serverName = InProcessServerBuilder.generateName(); 61 | private InProcessServerBuilder serverBuilder = InProcessServerBuilder 62 | .forName(serverName).directExecutor(); 63 | private InProcessChannelBuilder channelBuilder = InProcessChannelBuilder 64 | .forName(serverName).directExecutor(); 65 | 66 | @Inject private FakeUserDBHandler fakeUserDBHandler; 67 | @Inject private FakeSSOValidator fakeSSOValidator; 68 | 69 | @Rule 70 | public GrpcCleanupRule grpcCleanup = new GrpcCleanupRule(); 71 | 72 | @Before 73 | public void setup() { 74 | injector.injectMembers(this); 75 | } 76 | 77 | @After 78 | public void cleanUp() { 79 | fakeUserDBHandler.clear(); 80 | } 81 | 82 | @Test 83 | public void shouldValidateExistingRefresh_generateAndReturnNewRefreshToken() throws Exception { 84 | 85 | UserRefreshToken existingRefreshToken = JWTUtil.generateNewRefreshToken(USER_ID); 86 | 87 | fakeUserDBHandler.setRefreshTokenForUserId(USER_ID, existingRefreshToken); 88 | 89 | UserManagementServiceGrpc.UserManagementServiceBlockingStub blockingStub = createBlockingStub(); 90 | RegenerateRefreshTokenResponse response = 91 | blockingStub.regenerateRefreshToken( 92 | RegenerateRefreshTokenRequest.newBuilder() 93 | .setExistingRefreshToken(existingRefreshToken) 94 | .build()); 95 | // new on file refresh token should be same as returned 96 | assertEquals(fakeUserDBHandler.getRefreshTokenForUserId(USER_ID), response.getRefreshToken()); 97 | assertNotSame(response.getRefreshToken(), existingRefreshToken); 98 | validateAccessTokenToId(response.getAccessToken(), USER_ID.getId()); 99 | } 100 | 101 | @Test 102 | public void shouldThrow_invalidRefreshToken_unparsable() throws Exception { 103 | 104 | UserRefreshToken existingRefreshToken = JWTUtil.generateNewRefreshToken(USER_ID); 105 | 106 | fakeUserDBHandler.setRefreshTokenForUserId(USER_ID, existingRefreshToken); 107 | 108 | UserManagementServiceGrpc.UserManagementServiceBlockingStub blockingStub = createBlockingStub(); 109 | 110 | StatusRuntimeException exception = assertThrows(StatusRuntimeException.class, 111 | () -> blockingStub.regenerateRefreshToken( 112 | RegenerateRefreshTokenRequest.newBuilder() 113 | .setExistingRefreshToken(UserRefreshToken.newBuilder().setData("test").build()) 114 | .build())); 115 | assertEquals(exception.getStatus().getDescription(), "Invalid refresh token"); 116 | 117 | } 118 | 119 | @Test 120 | public void shouldThrow_invalidRefreshToken_notOnFile() throws Exception { 121 | 122 | UserRefreshToken existingRefreshToken = JWTUtil.generateNewRefreshToken(USER_ID); 123 | //wait a second s.t. issuedAt date is different per token 124 | TimeUnit.SECONDS.sleep(1); 125 | UserRefreshToken anotherRefreshToken = JWTUtil.generateNewRefreshToken(USER_ID); 126 | 127 | fakeUserDBHandler.setRefreshTokenForUserId(USER_ID, existingRefreshToken); 128 | 129 | UserManagementServiceGrpc.UserManagementServiceBlockingStub blockingStub = createBlockingStub(); 130 | 131 | StatusRuntimeException exception = assertThrows(StatusRuntimeException.class, 132 | () -> blockingStub.regenerateRefreshToken( 133 | RegenerateRefreshTokenRequest.newBuilder() 134 | .setExistingRefreshToken(anotherRefreshToken) 135 | .build())); 136 | assertEquals(exception.getStatus().getDescription(), "Invalid refresh token"); 137 | 138 | } 139 | 140 | // Sign in 141 | @Test 142 | public void shouldLogin_validGoogleIdToken_existingUser() throws Exception { 143 | fakeUserDBHandler.setEmailToUser(SAMPLE_USER_EMAIL, SAMPLE_USER); 144 | fakeSSOValidator.setUserToIdToken(SAMPLE_SSO_ID_TOKEN, SAMPLE_USER_FROM_SSO); 145 | 146 | UserManagementServiceGrpc.UserManagementServiceBlockingStub blockingStub = createBlockingStub(); 147 | SignInResponse response = 148 | blockingStub.signIn( 149 | SignInRequest.newBuilder() 150 | .setIdToken(SAMPLE_SSO_ID_TOKEN) 151 | .build()); 152 | // new on file refresh token should be same as returned 153 | assertEquals(fakeUserDBHandler.getRefreshTokenForUserId(USER_ID), response.getRefreshToken()); 154 | validateAccessTokenToId(response.getAccessToken(), USER_ID.getId()); 155 | } 156 | 157 | @Test 158 | public void shouldLogin_validGoogleIdToken_newuser() throws Exception { 159 | fakeSSOValidator.setUserToIdToken(SAMPLE_SSO_ID_TOKEN, SAMPLE_USER_FROM_SSO); 160 | fakeUserDBHandler.setCreatedUserId(1099); 161 | 162 | UserManagementServiceGrpc.UserManagementServiceBlockingStub blockingStub = createBlockingStub(); 163 | SignInResponse response = 164 | blockingStub.signIn( 165 | SignInRequest.newBuilder() 166 | .setIdToken(SAMPLE_SSO_ID_TOKEN) 167 | .build()); 168 | // new on file refresh token should be same as returned 169 | assertEquals(fakeUserDBHandler.getRefreshTokenForUserId(UserId.newBuilder().setId(1099).build()), response.getRefreshToken()); 170 | validateAccessTokenToId(response.getAccessToken(), 1099); 171 | assertEquals(fakeUserDBHandler.getCreatedUser().get(), SAMPLE_USER_FROM_SSO.toBuilder().setUserId(UserId.newBuilder().setId(1099).build()).build()); 172 | } 173 | 174 | private void validateAccessTokenToId(UserAccessToken accessToken, int userId){ 175 | assertEquals(JWTUtil.validateAccessToken(accessToken).get().getId(), userId); 176 | } 177 | 178 | private UserManagementServiceGrpc.UserManagementServiceBlockingStub createBlockingStub() throws Exception{ 179 | // Add the service to the in-process server. 180 | grpcCleanup.register( 181 | serverBuilder.addService(injector.getInstance(UserManagementServiceImpl.class)).build().start()); 182 | ManagedChannel channel = grpcCleanup.register( 183 | channelBuilder.maxInboundMessageSize(1024).build()); 184 | 185 | return UserManagementServiceGrpc.newBlockingStub(channel); 186 | 187 | } 188 | } 189 | -------------------------------------------------------------------------------- /java/com/user/management/UserManagementServiceImpl.java: -------------------------------------------------------------------------------- 1 | package com.user.management; 2 | 3 | import java.util.Optional; 4 | import java.util.concurrent.Executor; 5 | import com.google.common.util.concurrent.AsyncFunction; 6 | import com.google.common.util.concurrent.Futures; 7 | import com.google.common.util.concurrent.ListenableFuture; 8 | import com.google.common.util.concurrent.MoreExecutors; 9 | import com.google.inject.Inject; 10 | import io.grpc.stub.StreamObserver; 11 | import io.grpc.protobuf.StatusProto; 12 | import com.google.rpc.Status; 13 | import com.google.rpc.Code; 14 | import com.user.UserDBProto.DBFetchUserRequest; 15 | import com.user.UserDBProto.DBFetchUserResponse; 16 | import com.user.db.UserDBHandler; 17 | import com.user.UserProto.UserId; 18 | import com.user.UserProto.User; 19 | import com.user.UserProto.UserRefreshToken; 20 | import com.user.util.JWTUtil; 21 | import com.user.util.SSOValidator; 22 | import com.user.UserDBProto.UpdateRefreshTokenRequest; 23 | import com.user.UserDBProto.UpdateRefreshTokenResponse; 24 | import com.user.UserDBProto.CreateUserRequest; 25 | import com.user.UserDBProto.CreateUserResponse; 26 | import com.user.management.UserManagementServiceProto.SignInRequest; 27 | import com.user.management.UserManagementServiceProto.SignInResponse; 28 | import com.user.management.UserManagementServiceProto.RegenerateRefreshTokenRequest; 29 | import com.user.management.UserManagementServiceProto.RegenerateRefreshTokenResponse; 30 | 31 | 32 | public class UserManagementServiceImpl extends UserManagementServiceGrpc.UserManagementServiceImplBase { 33 | 34 | private UserDBHandler userDBHandler; 35 | private SSOValidator ssoValidator; 36 | 37 | @Inject 38 | public UserManagementServiceImpl( 39 | UserDBHandler userDBHandler, 40 | SSOValidator ssoValidator){ 41 | this.userDBHandler = userDBHandler; 42 | this.ssoValidator = ssoValidator; 43 | } 44 | 45 | @Override 46 | public void regenerateRefreshToken(RegenerateRefreshTokenRequest req, StreamObserver responseObserver) { 47 | 48 | Executor executor = MoreExecutors.newDirectExecutorService(); 49 | 50 | if(!req.hasExistingRefreshToken()){ 51 | invalidJwt(responseObserver); 52 | }else { 53 | UserRefreshToken existingRefreshToken = req.getExistingRefreshToken(); 54 | Optional updateRefreshToken = 55 | internalRegenerateRefreshToken( 56 | existingRefreshToken, 57 | responseObserver, 58 | executor); 59 | if(updateRefreshToken.isPresent()){ 60 | responseObserver.onNext( 61 | RegenerateRefreshTokenResponse.newBuilder() 62 | .setRefreshToken( 63 | updateRefreshToken.get()) 64 | .setAccessToken( 65 | JWTUtil.generateNewAccessToken( 66 | JWTUtil.validateRefreshToken(existingRefreshToken).get() 67 | )) 68 | .build()); 69 | responseObserver.onCompleted(); 70 | } 71 | } 72 | } 73 | 74 | @Override 75 | public void signIn(SignInRequest req, StreamObserver responseObserver) { 76 | 77 | System.out.println("sign in "); 78 | System.out.println(req); 79 | Executor executor = MoreExecutors.newDirectExecutorService(); 80 | 81 | /* 82 | 1) validate id token passed 83 | 2) fetch user for email 84 | - if new, create new user 85 | 3) gen refresh/access tokens 86 | */ 87 | 88 | Optional userFromIdToken = ssoValidator.validateGoogleIdToken(req.getIdToken()); 89 | System.out.println(userFromIdToken); 90 | if(userFromIdToken.isPresent()){ 91 | 92 | try{ 93 | ListenableFuture dbFetchUserFuture = 94 | userDBHandler.fetchUser( 95 | DBFetchUserRequest.newBuilder() 96 | .setEmail(userFromIdToken.get().getEmail()) 97 | .addField(DBFetchUserRequest.FetchableFields.USER_FIELD_USER) 98 | .build()); 99 | 100 | PotentialUserCreationResult potentialUserCreationResult = createNewUserIfNeededWithRefreshToken( 101 | dbFetchUserFuture, 102 | userFromIdToken.get(), 103 | executor).get(); 104 | 105 | System.out.println("PotentialUserCreationResult"); 106 | System.out.println(potentialUserCreationResult.getUserId()); 107 | System.out.println(potentialUserCreationResult.getUserRefreshTokenFuture().get()); 108 | 109 | responseObserver.onNext( 110 | SignInResponse.newBuilder() 111 | .setRefreshToken( 112 | potentialUserCreationResult.getUserRefreshTokenFuture().get()) 113 | .setAccessToken( 114 | JWTUtil.generateNewAccessToken( 115 | potentialUserCreationResult.getUserId() 116 | )) 117 | .build()); 118 | responseObserver.onCompleted(); 119 | 120 | }catch(Exception e){ 121 | Status status = Status.newBuilder() 122 | .setCode(Code.UNKNOWN.getNumber()) 123 | .setMessage("something bad happened - pending") 124 | .build(); 125 | responseObserver.onError(StatusProto.toStatusRuntimeException(status)); 126 | 127 | } 128 | 129 | }else{ 130 | // TODO: invalid id token throw 131 | } 132 | 133 | } 134 | 135 | private Optional internalRegenerateRefreshToken( 136 | UserRefreshToken existingRefreshToken, 137 | StreamObserver streamObserver, 138 | Executor executor ){ 139 | 140 | /* 141 | 1) validate refresh token 142 | 2) retrieve user id from token 143 | 3) fetch stored token and compare tokens 144 | 4) generate new token, store in db and return 145 | */ 146 | Optional userId = JWTUtil.validateRefreshToken(existingRefreshToken); 147 | 148 | if(!userId.isPresent()){ 149 | invalidJwt(streamObserver); 150 | }else{ 151 | try{ 152 | Optional> updateRefreshTokenFuture = 153 | verifyRefreshTokenAndUpdate( 154 | existingRefreshToken, 155 | userId.get(), 156 | executor); 157 | 158 | if(!updateRefreshTokenFuture.isPresent()){ 159 | invalidJwt(streamObserver); 160 | }else{ 161 | return Optional.of(updateRefreshTokenFuture.get().get().getNewRefreshToken()); 162 | } 163 | } catch(Exception e){ 164 | Status status = Status.newBuilder() 165 | .setCode(Code.UNKNOWN.getNumber()) 166 | .setMessage("something bad happened - pending") 167 | .build(); 168 | streamObserver.onError(StatusProto.toStatusRuntimeException(status)); 169 | } 170 | } 171 | 172 | return Optional.empty(); 173 | } 174 | 175 | private Optional> verifyRefreshTokenAndUpdate( 176 | UserRefreshToken existingRefreshToken, 177 | UserId userId, 178 | Executor executor) throws Exception{ 179 | 180 | ListenableFuture dbFetchUserFuture = userDBHandler.fetchUser( 181 | DBFetchUserRequest.newBuilder() 182 | .setUserId(userId) 183 | .addField(DBFetchUserRequest.FetchableFields.USER_FIELD_REFRESH_TOKEN) 184 | .build()); 185 | 186 | return validateAndSetNewRefreshToken( 187 | dbFetchUserFuture, 188 | existingRefreshToken, 189 | userId, 190 | executor).get(); 191 | 192 | } 193 | 194 | private ListenableFuture>> validateAndSetNewRefreshToken( 195 | ListenableFuture dbFetchUserFuture, 196 | UserRefreshToken existingRefreshToken, 197 | UserId userId, 198 | Executor executor){ 199 | AsyncFunction>> setNewTokenFunction = 200 | new AsyncFunction>>() { 201 | public ListenableFuture>> apply(DBFetchUserResponse fetchUserResponse) { 202 | Optional> response = Optional.empty(); 203 | if(fetchUserResponse.getRefreshToken().equals(existingRefreshToken)){ 204 | response = Optional.of(updateRefreshTokenFuture(userId)); 205 | } 206 | return Futures.immediateFuture(response); 207 | } 208 | }; 209 | return Futures.transformAsync(dbFetchUserFuture, setNewTokenFunction, executor); 210 | } 211 | 212 | final class PotentialUserCreationResult{ 213 | ListenableFuture userRefreshTokenFuture; 214 | UserId userId; 215 | 216 | public PotentialUserCreationResult(ListenableFuture userRefreshTokenFuture,UserId userId ){ 217 | this.userRefreshTokenFuture = userRefreshTokenFuture; 218 | this.userId = userId; 219 | } 220 | 221 | public ListenableFuture getUserRefreshTokenFuture(){ 222 | return userRefreshTokenFuture; 223 | } 224 | 225 | public UserId getUserId(){ 226 | return userId; 227 | } 228 | 229 | } 230 | 231 | private ListenableFuture createNewUserIfNeededWithRefreshToken( 232 | ListenableFuture dbFetchUserFuture, 233 | User userFromIdToken, 234 | Executor executor){ 235 | 236 | System.out.println("createNewUserIfNeededWithRefreshToken"); 237 | 238 | AsyncFunction handleUpdateRefreshTokenFunction = 239 | new AsyncFunction() { 240 | public ListenableFuture apply(UpdateRefreshTokenResponse updateRefreshTokenResponse) { 241 | return Futures.immediateFuture(updateRefreshTokenResponse.getNewRefreshToken()); 242 | } 243 | }; 244 | 245 | AsyncFunction handleCreateUserFunction = 246 | new AsyncFunction() { 247 | public ListenableFuture apply(CreateUserResponse createUserResponse) { 248 | System.out.println("handleCreateUserFunction"); 249 | System.out.println(createUserResponse); 250 | // TODO: handle errors in creation 251 | return Futures.immediateFuture(new PotentialUserCreationResult( 252 | Futures.transformAsync( 253 | updateRefreshTokenFuture(createUserResponse.getCreatedUserId()), handleUpdateRefreshTokenFunction, executor), 254 | createUserResponse.getCreatedUserId())); 255 | } 256 | }; 257 | 258 | AsyncFunction handleFetchUserResponseFunction = 259 | new AsyncFunction() { 260 | public ListenableFuture apply(DBFetchUserResponse fetchUserResponse) { 261 | System.out.println("fetchUserResponse"); 262 | System.out.println(fetchUserResponse); 263 | if(fetchUserResponse.hasUser()){ 264 | // update refresh token for existing user 265 | return Futures.immediateFuture( 266 | new PotentialUserCreationResult( 267 | Futures.transformAsync( 268 | updateRefreshTokenFuture(fetchUserResponse.getUser().getUserId()), handleUpdateRefreshTokenFunction, executor), 269 | fetchUserResponse.getUser().getUserId())); 270 | }else{ 271 | return Futures.transformAsync( 272 | createUser(userFromIdToken), handleCreateUserFunction, executor); 273 | } 274 | } 275 | }; 276 | return Futures.transformAsync(dbFetchUserFuture, handleFetchUserResponseFunction, executor); 277 | } 278 | 279 | private ListenableFuture updateRefreshTokenFuture(UserId userId){ 280 | return userDBHandler.updateRefreshToken( 281 | UpdateRefreshTokenRequest.newBuilder() 282 | .setUserId(userId) 283 | .setNewRefreshToken(JWTUtil.generateNewRefreshToken(userId)) 284 | .build()); 285 | 286 | } 287 | 288 | private ListenableFuture createUser(User user){ 289 | return userDBHandler.createUser( 290 | CreateUserRequest.newBuilder() 291 | .setUser(user) 292 | .build()); 293 | 294 | } 295 | 296 | private void invalidJwt(StreamObserver responseObserver){ 297 | Status status = Status.newBuilder() 298 | .setCode(Code.INVALID_ARGUMENT.getNumber()) 299 | .setMessage("Invalid refresh token") 300 | .build(); 301 | 302 | responseObserver.onError(StatusProto.toStatusRuntimeException(status)); 303 | } 304 | 305 | 306 | 307 | } -------------------------------------------------------------------------------- /npm/yarn.lock: -------------------------------------------------------------------------------- 1 | # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. 2 | # yarn lockfile v1 3 | 4 | 5 | "@grpc/grpc-js@^1.3.4": 6 | version "1.3.4" 7 | resolved "https://registry.yarnpkg.com/@grpc/grpc-js/-/grpc-js-1.3.4.tgz#5c4f5df717cd10cc5ebbc7523504008d1ff7b322" 8 | integrity sha512-AxtZcm0mArQhY9z8T3TynCYVEaSKxNCa9mVhVwBCUnsuUEe8Zn94bPYYKVQSLt+hJJ1y0ukr3mUvtWfcATL/IQ== 9 | dependencies: 10 | "@types/node" ">=12.12.47" 11 | 12 | "@mapbox/node-pre-gyp@^1.0.5": 13 | version "1.0.5" 14 | resolved "https://registry.yarnpkg.com/@mapbox/node-pre-gyp/-/node-pre-gyp-1.0.5.tgz#2a0b32fcb416fb3f2250fd24cb2a81421a4f5950" 15 | integrity sha512-4srsKPXWlIxp5Vbqz5uLfBN+du2fJChBoYn/f2h991WLdk7jUvcSk/McVLSv/X+xQIPI8eGD5GjrnygdyHnhPA== 16 | dependencies: 17 | detect-libc "^1.0.3" 18 | https-proxy-agent "^5.0.0" 19 | make-dir "^3.1.0" 20 | node-fetch "^2.6.1" 21 | nopt "^5.0.0" 22 | npmlog "^4.1.2" 23 | rimraf "^3.0.2" 24 | semver "^7.3.4" 25 | tar "^6.1.0" 26 | 27 | "@types/node@>=12.12.47": 28 | version "16.0.0" 29 | resolved "https://registry.yarnpkg.com/@types/node/-/node-16.0.0.tgz#067a6c49dc7a5c2412a505628e26902ae967bf6f" 30 | integrity sha512-TmCW5HoZ2o2/z2EYi109jLqIaPIi9y/lc2LmDCWzuCi35bcaQ+OtUh6nwBiFK7SOu25FAU5+YKdqFZUwtqGSdg== 31 | 32 | abbrev@1: 33 | version "1.1.1" 34 | resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-1.1.1.tgz#f8f2c887ad10bf67f634f005b6987fed3179aac8" 35 | integrity sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q== 36 | 37 | agent-base@6: 38 | version "6.0.2" 39 | resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-6.0.2.tgz#49fff58577cfee3f37176feab4c22e00f86d7f77" 40 | integrity sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ== 41 | dependencies: 42 | debug "4" 43 | 44 | ansi-regex@^2.0.0: 45 | version "2.1.1" 46 | resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-2.1.1.tgz#c3b33ab5ee360d86e0e628f0468ae7ef27d654df" 47 | integrity sha1-w7M6te42DYbg5ijwRorn7yfWVN8= 48 | 49 | ansi-regex@^3.0.0: 50 | version "3.0.0" 51 | resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-3.0.0.tgz#ed0317c322064f79466c02966bddb605ab37d998" 52 | integrity sha1-7QMXwyIGT3lGbAKWa922Bas32Zg= 53 | 54 | aproba@^1.0.3: 55 | version "1.2.0" 56 | resolved "https://registry.yarnpkg.com/aproba/-/aproba-1.2.0.tgz#6802e6264efd18c790a1b0d517f0f2627bf2c94a" 57 | integrity sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw== 58 | 59 | are-we-there-yet@~1.1.2: 60 | version "1.1.5" 61 | resolved "https://registry.yarnpkg.com/are-we-there-yet/-/are-we-there-yet-1.1.5.tgz#4b35c2944f062a8bfcda66410760350fe9ddfc21" 62 | integrity sha512-5hYdAkZlcG8tOLujVDTgCT+uPX0VnpAH28gWsLfzpXYm7wP6mp5Q/gYyR7YQ0cKVJcXJnl3j2kpBan13PtQf6w== 63 | dependencies: 64 | delegates "^1.0.0" 65 | readable-stream "^2.0.6" 66 | 67 | balanced-match@^1.0.0: 68 | version "1.0.2" 69 | resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee" 70 | integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw== 71 | 72 | brace-expansion@^1.1.7: 73 | version "1.1.11" 74 | resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" 75 | integrity sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA== 76 | dependencies: 77 | balanced-match "^1.0.0" 78 | concat-map "0.0.1" 79 | 80 | chownr@^2.0.0: 81 | version "2.0.0" 82 | resolved "https://registry.yarnpkg.com/chownr/-/chownr-2.0.0.tgz#15bfbe53d2eab4cf70f18a8cd68ebe5b3cb1dece" 83 | integrity sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ== 84 | 85 | code-point-at@^1.0.0: 86 | version "1.1.0" 87 | resolved "https://registry.yarnpkg.com/code-point-at/-/code-point-at-1.1.0.tgz#0d070b4d043a5bea33a2f1a40e2edb3d9a4ccf77" 88 | integrity sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c= 89 | 90 | concat-map@0.0.1: 91 | version "0.0.1" 92 | resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" 93 | integrity sha1-2Klr13/Wjfd5OnMDajug1UBdR3s= 94 | 95 | console-control-strings@^1.0.0, console-control-strings@~1.1.0: 96 | version "1.1.0" 97 | resolved "https://registry.yarnpkg.com/console-control-strings/-/console-control-strings-1.1.0.tgz#3d7cf4464db6446ea644bf4b39507f9851008e8e" 98 | integrity sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4= 99 | 100 | core-util-is@~1.0.0: 101 | version "1.0.2" 102 | resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7" 103 | integrity sha1-tf1UIgqivFq1eqtxQMlAdUUDwac= 104 | 105 | debug@4: 106 | version "4.3.1" 107 | resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.1.tgz#f0d229c505e0c6d8c49ac553d1b13dc183f6b2ee" 108 | integrity sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ== 109 | dependencies: 110 | ms "2.1.2" 111 | 112 | delegates@^1.0.0: 113 | version "1.0.0" 114 | resolved "https://registry.yarnpkg.com/delegates/-/delegates-1.0.0.tgz#84c6e159b81904fdca59a0ef44cd870d31250f9a" 115 | integrity sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o= 116 | 117 | detect-libc@^1.0.3: 118 | version "1.0.3" 119 | resolved "https://registry.yarnpkg.com/detect-libc/-/detect-libc-1.0.3.tgz#fa137c4bd698edf55cd5cd02ac559f91a4c4ba9b" 120 | integrity sha1-+hN8S9aY7fVc1c0CrFWfkaTEups= 121 | 122 | fs-minipass@^2.0.0: 123 | version "2.1.0" 124 | resolved "https://registry.yarnpkg.com/fs-minipass/-/fs-minipass-2.1.0.tgz#7f5036fdbf12c63c169190cbe4199c852271f9fb" 125 | integrity sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg== 126 | dependencies: 127 | minipass "^3.0.0" 128 | 129 | fs.realpath@^1.0.0: 130 | version "1.0.0" 131 | resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" 132 | integrity sha1-FQStJSMVjKpA20onh8sBQRmU6k8= 133 | 134 | gauge@~2.7.3: 135 | version "2.7.4" 136 | resolved "https://registry.yarnpkg.com/gauge/-/gauge-2.7.4.tgz#2c03405c7538c39d7eb37b317022e325fb018bf7" 137 | integrity sha1-LANAXHU4w51+s3sxcCLjJfsBi/c= 138 | dependencies: 139 | aproba "^1.0.3" 140 | console-control-strings "^1.0.0" 141 | has-unicode "^2.0.0" 142 | object-assign "^4.1.0" 143 | signal-exit "^3.0.0" 144 | string-width "^1.0.1" 145 | strip-ansi "^3.0.1" 146 | wide-align "^1.1.0" 147 | 148 | glob@^7.1.3: 149 | version "7.1.7" 150 | resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.7.tgz#3b193e9233f01d42d0b3f78294bbeeb418f94a90" 151 | integrity sha512-OvD9ENzPLbegENnYP5UUfJIirTg4+XwMWGaQfQTY0JenxNvvIKP3U3/tAQSPIu/lHxXYSZmpXlUHeqAIdKzBLQ== 152 | dependencies: 153 | fs.realpath "^1.0.0" 154 | inflight "^1.0.4" 155 | inherits "2" 156 | minimatch "^3.0.4" 157 | once "^1.3.0" 158 | path-is-absolute "^1.0.0" 159 | 160 | google-protobuf@^3.15.5, google-protobuf@^3.17.3: 161 | version "3.17.3" 162 | resolved "https://registry.yarnpkg.com/google-protobuf/-/google-protobuf-3.17.3.tgz#f87595073545a77946c8f0b67c302c5f7646d700" 163 | integrity sha512-OVPzcSWIAJ+d5yiHyeaLrdufQtrvaBrF4JQg+z8ynTkbO3uFcujqXszTumqg1cGsAsjkWnI+M5B1xZ19yR4Wyg== 164 | 165 | grpc-tools@^1.11.2: 166 | version "1.11.2" 167 | resolved "https://registry.yarnpkg.com/grpc-tools/-/grpc-tools-1.11.2.tgz#22d802d40012510ccc6591d11f9c94109ac07aab" 168 | integrity sha512-4+EgpnnkJraamY++oyBCw5Hp9huRYfgakjNVKbiE3PgO9Tv5ydVlRo7ZyGJ0C0SEiA7HhbVc1sNNtIyK7FiEtg== 169 | dependencies: 170 | "@mapbox/node-pre-gyp" "^1.0.5" 171 | 172 | has-unicode@^2.0.0: 173 | version "2.0.1" 174 | resolved "https://registry.yarnpkg.com/has-unicode/-/has-unicode-2.0.1.tgz#e0e6fe6a28cf51138855e086d1691e771de2a8b9" 175 | integrity sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk= 176 | 177 | https-proxy-agent@^5.0.0: 178 | version "5.0.0" 179 | resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-5.0.0.tgz#e2a90542abb68a762e0a0850f6c9edadfd8506b2" 180 | integrity sha512-EkYm5BcKUGiduxzSt3Eppko+PiNWNEpa4ySk9vTC6wDsQJW9rHSa+UhGNJoRYp7bz6Ht1eaRIa6QaJqO5rCFbA== 181 | dependencies: 182 | agent-base "6" 183 | debug "4" 184 | 185 | inflight@^1.0.4: 186 | version "1.0.6" 187 | resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" 188 | integrity sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk= 189 | dependencies: 190 | once "^1.3.0" 191 | wrappy "1" 192 | 193 | inherits@2, inherits@~2.0.3: 194 | version "2.0.4" 195 | resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" 196 | integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== 197 | 198 | is-fullwidth-code-point@^1.0.0: 199 | version "1.0.0" 200 | resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz#ef9e31386f031a7f0d643af82fde50c457ef00cb" 201 | integrity sha1-754xOG8DGn8NZDr4L95QxFfvAMs= 202 | dependencies: 203 | number-is-nan "^1.0.0" 204 | 205 | is-fullwidth-code-point@^2.0.0: 206 | version "2.0.0" 207 | resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz#a3b30a5c4f199183167aaab93beefae3ddfb654f" 208 | integrity sha1-o7MKXE8ZkYMWeqq5O+764937ZU8= 209 | 210 | isarray@~1.0.0: 211 | version "1.0.0" 212 | resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11" 213 | integrity sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE= 214 | 215 | lru-cache@^6.0.0: 216 | version "6.0.0" 217 | resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-6.0.0.tgz#6d6fe6570ebd96aaf90fcad1dafa3b2566db3a94" 218 | integrity sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA== 219 | dependencies: 220 | yallist "^4.0.0" 221 | 222 | make-dir@^3.1.0: 223 | version "3.1.0" 224 | resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-3.1.0.tgz#415e967046b3a7f1d185277d84aa58203726a13f" 225 | integrity sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw== 226 | dependencies: 227 | semver "^6.0.0" 228 | 229 | minimatch@^3.0.4: 230 | version "3.0.4" 231 | resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083" 232 | integrity sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA== 233 | dependencies: 234 | brace-expansion "^1.1.7" 235 | 236 | minipass@^3.0.0: 237 | version "3.1.3" 238 | resolved "https://registry.yarnpkg.com/minipass/-/minipass-3.1.3.tgz#7d42ff1f39635482e15f9cdb53184deebd5815fd" 239 | integrity sha512-Mgd2GdMVzY+x3IJ+oHnVM+KG3lA5c8tnabyJKmHSaG2kAGpudxuOf8ToDkhumF7UzME7DecbQE9uOZhNm7PuJg== 240 | dependencies: 241 | yallist "^4.0.0" 242 | 243 | minizlib@^2.1.1: 244 | version "2.1.2" 245 | resolved "https://registry.yarnpkg.com/minizlib/-/minizlib-2.1.2.tgz#e90d3466ba209b932451508a11ce3d3632145931" 246 | integrity sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg== 247 | dependencies: 248 | minipass "^3.0.0" 249 | yallist "^4.0.0" 250 | 251 | mkdirp@^1.0.3: 252 | version "1.0.4" 253 | resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-1.0.4.tgz#3eb5ed62622756d79a5f0e2a221dfebad75c2f7e" 254 | integrity sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw== 255 | 256 | ms@2.1.2: 257 | version "2.1.2" 258 | resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" 259 | integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== 260 | 261 | node-fetch@^2.6.1: 262 | version "2.6.1" 263 | resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.1.tgz#045bd323631f76ed2e2b55573394416b639a0052" 264 | integrity sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw== 265 | 266 | nopt@^5.0.0: 267 | version "5.0.0" 268 | resolved "https://registry.yarnpkg.com/nopt/-/nopt-5.0.0.tgz#530942bb58a512fccafe53fe210f13a25355dc88" 269 | integrity sha512-Tbj67rffqceeLpcRXrT7vKAN8CwfPeIBgM7E6iBkmKLV7bEMwpGgYLGv0jACUsECaa/vuxP0IjEont6umdMgtQ== 270 | dependencies: 271 | abbrev "1" 272 | 273 | npmlog@^4.1.2: 274 | version "4.1.2" 275 | resolved "https://registry.yarnpkg.com/npmlog/-/npmlog-4.1.2.tgz#08a7f2a8bf734604779a9efa4ad5cc717abb954b" 276 | integrity sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg== 277 | dependencies: 278 | are-we-there-yet "~1.1.2" 279 | console-control-strings "~1.1.0" 280 | gauge "~2.7.3" 281 | set-blocking "~2.0.0" 282 | 283 | number-is-nan@^1.0.0: 284 | version "1.0.1" 285 | resolved "https://registry.yarnpkg.com/number-is-nan/-/number-is-nan-1.0.1.tgz#097b602b53422a522c1afb8790318336941a011d" 286 | integrity sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0= 287 | 288 | object-assign@^4.1.0: 289 | version "4.1.1" 290 | resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" 291 | integrity sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM= 292 | 293 | once@^1.3.0: 294 | version "1.4.0" 295 | resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" 296 | integrity sha1-WDsap3WWHUsROsF9nFC6753Xa9E= 297 | dependencies: 298 | wrappy "1" 299 | 300 | path-is-absolute@^1.0.0: 301 | version "1.0.1" 302 | resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" 303 | integrity sha1-F0uSaHNVNP+8es5r9TpanhtcX18= 304 | 305 | process-nextick-args@~2.0.0: 306 | version "2.0.1" 307 | resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.1.tgz#7820d9b16120cc55ca9ae7792680ae7dba6d7fe2" 308 | integrity sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag== 309 | 310 | readable-stream@^2.0.6: 311 | version "2.3.7" 312 | resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.7.tgz#1eca1cf711aef814c04f62252a36a62f6cb23b57" 313 | integrity sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw== 314 | dependencies: 315 | core-util-is "~1.0.0" 316 | inherits "~2.0.3" 317 | isarray "~1.0.0" 318 | process-nextick-args "~2.0.0" 319 | safe-buffer "~5.1.1" 320 | string_decoder "~1.1.1" 321 | util-deprecate "~1.0.1" 322 | 323 | rimraf@^3.0.2: 324 | version "3.0.2" 325 | resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-3.0.2.tgz#f1a5402ba6220ad52cc1282bac1ae3aa49fd061a" 326 | integrity sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA== 327 | dependencies: 328 | glob "^7.1.3" 329 | 330 | safe-buffer@~5.1.0, safe-buffer@~5.1.1: 331 | version "5.1.2" 332 | resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d" 333 | integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g== 334 | 335 | semver@^6.0.0: 336 | version "6.3.0" 337 | resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d" 338 | integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw== 339 | 340 | semver@^7.3.4: 341 | version "7.3.5" 342 | resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.5.tgz#0b621c879348d8998e4b0e4be94b3f12e6018ef7" 343 | integrity sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ== 344 | dependencies: 345 | lru-cache "^6.0.0" 346 | 347 | set-blocking@~2.0.0: 348 | version "2.0.0" 349 | resolved "https://registry.yarnpkg.com/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7" 350 | integrity sha1-BF+XgtARrppoA93TgrJDkrPYkPc= 351 | 352 | signal-exit@^3.0.0: 353 | version "3.0.3" 354 | resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.3.tgz#a1410c2edd8f077b08b4e253c8eacfcaf057461c" 355 | integrity sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA== 356 | 357 | string-width@^1.0.1: 358 | version "1.0.2" 359 | resolved "https://registry.yarnpkg.com/string-width/-/string-width-1.0.2.tgz#118bdf5b8cdc51a2a7e70d211e07e2b0b9b107d3" 360 | integrity sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M= 361 | dependencies: 362 | code-point-at "^1.0.0" 363 | is-fullwidth-code-point "^1.0.0" 364 | strip-ansi "^3.0.0" 365 | 366 | "string-width@^1.0.2 || 2": 367 | version "2.1.1" 368 | resolved "https://registry.yarnpkg.com/string-width/-/string-width-2.1.1.tgz#ab93f27a8dc13d28cac815c462143a6d9012ae9e" 369 | integrity sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw== 370 | dependencies: 371 | is-fullwidth-code-point "^2.0.0" 372 | strip-ansi "^4.0.0" 373 | 374 | string_decoder@~1.1.1: 375 | version "1.1.1" 376 | resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.1.1.tgz#9cf1611ba62685d7030ae9e4ba34149c3af03fc8" 377 | integrity sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg== 378 | dependencies: 379 | safe-buffer "~5.1.0" 380 | 381 | strip-ansi@^3.0.0, strip-ansi@^3.0.1: 382 | version "3.0.1" 383 | resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-3.0.1.tgz#6a385fb8853d952d5ff05d0e8aaf94278dc63dcf" 384 | integrity sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8= 385 | dependencies: 386 | ansi-regex "^2.0.0" 387 | 388 | strip-ansi@^4.0.0: 389 | version "4.0.0" 390 | resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-4.0.0.tgz#a8479022eb1ac368a871389b635262c505ee368f" 391 | integrity sha1-qEeQIusaw2iocTibY1JixQXuNo8= 392 | dependencies: 393 | ansi-regex "^3.0.0" 394 | 395 | tar@^6.1.0: 396 | version "6.1.0" 397 | resolved "https://registry.yarnpkg.com/tar/-/tar-6.1.0.tgz#d1724e9bcc04b977b18d5c573b333a2207229a83" 398 | integrity sha512-DUCttfhsnLCjwoDoFcI+B2iJgYa93vBnDUATYEeRx6sntCTdN01VnqsIuTlALXla/LWooNg0yEGeB+Y8WdFxGA== 399 | dependencies: 400 | chownr "^2.0.0" 401 | fs-minipass "^2.0.0" 402 | minipass "^3.0.0" 403 | minizlib "^2.1.1" 404 | mkdirp "^1.0.3" 405 | yallist "^4.0.0" 406 | 407 | ts-protoc-gen@^0.15.0: 408 | version "0.15.0" 409 | resolved "https://registry.yarnpkg.com/ts-protoc-gen/-/ts-protoc-gen-0.15.0.tgz#2fec5930b46def7dcc9fa73c060d770b7b076b7b" 410 | integrity sha512-TycnzEyrdVDlATJ3bWFTtra3SCiEP0W0vySXReAuEygXCUr1j2uaVyL0DhzjwuUdQoW5oXPwk6oZWeA0955V+g== 411 | dependencies: 412 | google-protobuf "^3.15.5" 413 | 414 | util-deprecate@~1.0.1: 415 | version "1.0.2" 416 | resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" 417 | integrity sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8= 418 | 419 | wide-align@^1.1.0: 420 | version "1.1.3" 421 | resolved "https://registry.yarnpkg.com/wide-align/-/wide-align-1.1.3.tgz#ae074e6bdc0c14a431e804e624549c633b000457" 422 | integrity sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA== 423 | dependencies: 424 | string-width "^1.0.2 || 2" 425 | 426 | wrappy@1: 427 | version "1.0.2" 428 | resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" 429 | integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8= 430 | 431 | yallist@^4.0.0: 432 | version "4.0.0" 433 | resolved "https://registry.yarnpkg.com/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72" 434 | integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A== 435 | --------------------------------------------------------------------------------