diff --git a/.github/workflows/android-build.yml b/.github/workflows/android-build.yml index 391e8cd5..9d890ef8 100644 --- a/.github/workflows/android-build.yml +++ b/.github/workflows/android-build.yml @@ -15,6 +15,7 @@ on: jobs: build-android: + permissions: write-all runs-on: ubuntu-latest steps: - name: 'Checkout' @@ -65,7 +66,7 @@ jobs: # Create artifacts (only for Release) # Create app-universal-release APK artifact asset for Release - name: 'Upload universal .apk Release Artifact (for Release)' - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v3.1.2 if: inputs.semver != '' # If this workflow is called from release.yml with: name: robosats-${{ inputs.semver }}-universal.apk @@ -73,7 +74,7 @@ jobs: # Create app-arm64-v8a-release APK artifact asset for Release - name: 'Upload arm64-v8a .apk Release Artifact (for Release)' - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v3.1.2 if: inputs.semver != '' # If this workflow is called from release.yml with: name: robosats-${{ inputs.semver }}-arm64-v8a.apk @@ -81,7 +82,7 @@ jobs: # Create app-armeabi-v7a-release APK artifact asset for Release - name: 'Upload armeabi-v7a .apk Release Artifact (for Release)' - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v3.1.2 if: inputs.semver != '' # If this workflow is called from release.yml with: name: robosats-${{ inputs.semver }}-armeabi-v7a.apk @@ -89,7 +90,7 @@ jobs: # Create app-x86_64-release APK artifact asset for Release - name: 'Upload x86_64 .apk Release Artifact (for Release)' - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v3.1.2 if: inputs.semver != '' # If this workflow is called from release.yml with: name: robosats-${{ inputs.semver }}-x86_64.apk @@ -97,7 +98,7 @@ jobs: # Create app-x86-release APK artifact asset for Release - name: 'Upload x86 .apk Release Artifact (for Release)' - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v3.1.2 if: inputs.semver != '' # If this workflow is called from release.yml with: name: robosats-${{ inputs.semver }}-x86.apk @@ -106,14 +107,10 @@ jobs: - name: 'Create Pre-release' id: create_release if: inputs.semver == '' # only if this workflow is not called from a push to tag (a Release) - uses: actions/create-release@v1 - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + uses: ncipollo/release-action@v1.12.0 with: - tag_name: android-${{ steps.commit.outputs.short }} - release_name: robosats-android-${{ steps.commit.outputs.short }} - changelog: mobile/CHANGELOG.md - draft: false + tag: android-${{ steps.commit.outputs.short }} + name: robosats-android-${{ steps.commit.outputs.short }} prerelease: true # Upload universal APK to pre-release diff --git a/.github/workflows/frontend-build.yml b/.github/workflows/frontend-build.yml index 1cd5e650..f5a42f6a 100644 --- a/.github/workflows/frontend-build.yml +++ b/.github/workflows/frontend-build.yml @@ -49,17 +49,17 @@ jobs: cd frontend npm run build - name: 'Archive Web Basic Build Results' - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v3.1.2 with: name: web-main-js path: frontend/static/frontend/main.js - name: 'Archive Web PRO Build Results' - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v3.1.2 with: name: web-pro-js path: frontend/static/frontend/pro.js - name: 'Archive Mobile Build Results' - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v3.1.2 with: name: mobile-web.bundle path: mobile/html/Web.bundle diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index a9e6134b..d623e234 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -67,21 +67,15 @@ jobs: semver: ${{ needs.check-versions.outputs.semver }} release: + permissions: write-all needs: [check-versions, coordinator-image, client-image, android-build] runs-on: ubuntu-latest steps: - name: Checkout uses: actions/checkout@v3 - - name: "Generate Release Changelog" - id: changelog - uses: heinrichreimer/github-changelog-generator-action@v2.3 - with: - token: ${{ secrets.GITHUB_TOKEN }} - name: Release id: create-release uses: softprops/action-gh-release@v1 - with: - body: ${{ steps.changelog.outputs.changelog }} # Upload app-universal-release APK artifact asset - name: 'Download universal APK Artifact' diff --git a/api/oas_schemas.py b/api/oas_schemas.py index 9b5c6270..5f1b3882 100644 --- a/api/oas_schemas.py +++ b/api/oas_schemas.py @@ -956,7 +956,7 @@ class HistoricalViewSchema: class StealthViewSchema: - put = { + post = { "summary": "Update stealth option", "description": "Update stealth invoice option for the user", "responses": { diff --git a/api/views.py b/api/views.py index 960c8eae..b135cdf7 100644 --- a/api/views.py +++ b/api/views.py @@ -15,7 +15,7 @@ from rest_framework.authentication import ( SessionAuthentication, # DEPRECATE session authentication ) from rest_framework.authentication import TokenAuthentication -from rest_framework.generics import CreateAPIView, ListAPIView, UpdateAPIView +from rest_framework.generics import CreateAPIView, ListAPIView from rest_framework.permissions import IsAuthenticated from rest_framework.response import Response from rest_framework.views import APIView @@ -1094,14 +1094,14 @@ class HistoricalView(ListAPIView): return Response(payload, status.HTTP_200_OK) -class StealthView(UpdateAPIView): +class StealthView(APIView): authentication_classes = [TokenAuthentication, SessionAuthentication] permission_classes = [IsAuthenticated] serializer_class = StealthSerializer - @extend_schema(**StealthViewSchema.put) - def put(self, request): + @extend_schema(**StealthViewSchema.post) + def post(self, request): serializer = self.serializer_class(data=request.data) if not serializer.is_valid(): diff --git a/frontend/src/basic/OrderPage/index.tsx b/frontend/src/basic/OrderPage/index.tsx index 23d515d2..c751c306 100644 --- a/frontend/src/basic/OrderPage/index.tsx +++ b/frontend/src/basic/OrderPage/index.tsx @@ -58,13 +58,15 @@ const OrderPage = (): JSX.Element => { escrow_duration: order.escrow_duration, bond_size: order.bond_size, }; - apiClient.post(baseUrl, '/api/make/', body, robot.tokenSHA256).then((data: any) => { - if (data.bad_request) { - setBadOrder(data.bad_request); - } else if (data.id) { - navigate('/order/' + data.id); - } - }); + apiClient + .post(baseUrl, '/api/make/', body, { tokenSHA256: robot.tokenSHA256 }) + .then((data: any) => { + if (data.bad_request) { + setBadOrder(data.bad_request); + } else if (data.id) { + navigate('/order/' + data.id); + } + }); } }; diff --git a/frontend/src/components/Dialogs/Profile.tsx b/frontend/src/components/Dialogs/Profile.tsx index fd8c8835..e6676529 100644 --- a/frontend/src/components/Dialogs/Profile.tsx +++ b/frontend/src/components/Dialogs/Profile.tsx @@ -30,17 +30,13 @@ import { EnableTelegramDialog } from '.'; import BoltIcon from '@mui/icons-material/Bolt'; import SendIcon from '@mui/icons-material/Send'; import NumbersIcon from '@mui/icons-material/Numbers'; -import ContentCopy from '@mui/icons-material/ContentCopy'; -import PersonAddAltIcon from '@mui/icons-material/PersonAddAlt'; import EmojiEventsIcon from '@mui/icons-material/EmojiEvents'; import { UserNinjaIcon } from '../Icons'; -import { systemClient } from '../../services/System'; import { getHost, getWebln } from '../../utils'; import RobotAvatar from '../RobotAvatar'; import { apiClient } from '../../services/api'; import { Robot } from '../../models'; -import { Page } from '../../basic/NavBar'; interface Props { open: boolean; @@ -96,7 +92,7 @@ const ProfileDialog = ({ open = false, baseUrl, onClose, robot, setRobot }: Prop { invoice: rewardInvoice, }, - robot.tokenSHA256, + { tokenSHA256: robot.tokenSHA256 }, ) .then((data: any) => { setBadInvoice(data.bad_invoice ?? ''); @@ -110,7 +106,7 @@ const ProfileDialog = ({ open = false, baseUrl, onClose, robot, setRobot }: Prop const setStealthInvoice = (wantsStealth: boolean) => { apiClient - .put(baseUrl, '/api/stealth/', { wantsStealth }, robot.tokenSHA256) + .post(baseUrl, '/api/stealth/', { wantsStealth }, { tokenSHA256: robot.tokenSHA256 }) .then((data) => setRobot({ ...robot, stealthInvoices: data?.wantsStealth })); }; diff --git a/frontend/src/components/MakerForm/MakerForm.tsx b/frontend/src/components/MakerForm/MakerForm.tsx index ec0f5b7c..901eedc0 100644 --- a/frontend/src/components/MakerForm/MakerForm.tsx +++ b/frontend/src/components/MakerForm/MakerForm.tsx @@ -249,13 +249,15 @@ const MakerForm = ({ escrow_duration: maker.escrowDuration, bond_size: maker.bondSize, }; - apiClient.post(baseUrl, '/api/make/', body, robot.tokenSHA256).then((data: object) => { - setBadRequest(data.bad_request); - if (data.id) { - onOrderCreated(data.id); - } - setSubmittingRequest(false); - }); + apiClient + .post(baseUrl, '/api/make/', body, { tokenSHA256: robot.tokenSHA256 }) + .then((data: object) => { + setBadRequest(data.bad_request); + if (data.id) { + onOrderCreated(data.id); + } + setSubmittingRequest(false); + }); } setOpenDialogs(false); }; diff --git a/frontend/src/components/OrderDetails/TakeButton.tsx b/frontend/src/components/OrderDetails/TakeButton.tsx index a69b99fe..815cd52c 100644 --- a/frontend/src/components/OrderDetails/TakeButton.tsx +++ b/frontend/src/components/OrderDetails/TakeButton.tsx @@ -285,7 +285,7 @@ const TakeButton = ({ order, setOrder, baseUrl, info }: TakeButtonProps): JSX.El action: 'take', amount: order.currency == 1000 ? takeAmount / 100000000 : takeAmount, }, - robot.tokenSHA256, + { tokenSHA256: robot.tokenSHA256 }, ) .then((data) => { setLoadingTake(false); diff --git a/frontend/src/components/TradeBox/EncryptedChat/EncryptedTurtleChat/index.tsx b/frontend/src/components/TradeBox/EncryptedChat/EncryptedTurtleChat/index.tsx index 6b780aeb..28a79146 100644 --- a/frontend/src/components/TradeBox/EncryptedChat/EncryptedTurtleChat/index.tsx +++ b/frontend/src/components/TradeBox/EncryptedChat/EncryptedTurtleChat/index.tsx @@ -76,7 +76,9 @@ const EncryptedTurtleChat: React.FC = ({ const loadMessages: () => void = () => { apiClient - .get(baseUrl, `/api/chat/?order_id=${orderId}&offset=${lastIndex}`, robot.tokenSHA256) + .get(baseUrl, `/api/chat/?order_id=${orderId}&offset=${lastIndex}`, { + tokenSHA256: robot.tokenSHA256, + }) .then((results: any) => { if (results) { setPeerConnected(results.peer_connected); @@ -175,7 +177,7 @@ const EncryptedTurtleChat: React.FC = ({ order_id: orderId, offset: lastIndex, }, - robot.tokenSHA256, + { tokenSHA256: robot.tokenSHA256 }, ) .then((response) => { if (response != null) { diff --git a/frontend/src/components/TradeBox/index.tsx b/frontend/src/components/TradeBox/index.tsx index a41c828b..c666953f 100644 --- a/frontend/src/components/TradeBox/index.tsx +++ b/frontend/src/components/TradeBox/index.tsx @@ -173,7 +173,7 @@ const TradeBox = ({ statement, rating, }, - robot.tokenSHA256, + { tokenSHA256: robot.tokenSHA256 }, ) .catch(() => { setOpen(closeAll); diff --git a/frontend/src/contexts/AppContext.ts b/frontend/src/contexts/AppContext.ts index b9ae57b5..d6be350f 100644 --- a/frontend/src/contexts/AppContext.ts +++ b/frontend/src/contexts/AppContext.ts @@ -296,7 +296,7 @@ export const useAppStore = () => { const fetchOrder = function () { if (currentOrder != undefined) { apiClient - .get(baseUrl, '/api/order/?order_id=' + currentOrder, robot.tokenSHA256) + .get(baseUrl, '/api/order/?order_id=' + currentOrder, { tokenSHA256: robot.tokenSHA256 }) .then(orderReceived); } }; @@ -325,9 +325,14 @@ export const useAppStore = () => { const encPrivKey = newKeys?.encPrivKey ?? robot.encPrivKey ?? ''; const pubKey = newKeys?.pubKey ?? robot.pubKey ?? ''; - // On first authenticated request, pubkey and privkey must be in header cookies - systemClient.setCookie('public_key', pubKey.split('\n').join('\\')); - systemClient.setCookie('encrypted_private_key', encPrivKey.split('\n').join('\\')); + // On first authenticated requests, pubkey and privkey are needed in header cookies + const auth = { + tokenSHA256, + keys: { + pubKey: pubKey.split('\n').join('\\'), + encPrivKey: encPrivKey.split('\n').join('\\'), + }, + }; if (!isRefresh) { setRobot((robot) => { @@ -340,7 +345,7 @@ export const useAppStore = () => { } apiClient - .get(baseUrl, '/api/robot/', tokenSHA256) + .get(baseUrl, '/api/robot/', auth) .then((data: any) => { const newRobot = { avatarLoaded: isRefresh ? robot.avatarLoaded : false, diff --git a/frontend/src/models/Robot.model.ts b/frontend/src/models/Robot.model.ts index a91a87cb..68602ce2 100644 --- a/frontend/src/models/Robot.model.ts +++ b/frontend/src/models/Robot.model.ts @@ -2,7 +2,7 @@ class Robot { constructor(garageRobot?: Robot) { if (garageRobot != null) { this.token = garageRobot?.token ?? undefined; - this.tokenSHA256 = garageRobot?.tokenSHA256 ?? undefined; + this.tokenSHA256 = garageRobot?.tokenSHA256 ?? ''; this.pubKey = garageRobot?.pubKey ?? undefined; this.encPrivKey = garageRobot?.encPrivKey ?? undefined; } @@ -12,7 +12,7 @@ class Robot { public token?: string; public bitsEntropy?: number; public shannonEntropy?: number; - public tokenSHA256?: string; + public tokenSHA256: string = ''; public pubKey?: string; public encPrivKey?: string; public stealthInvoices: boolean = true; diff --git a/frontend/src/services/api/ApiNativeClient/index.ts b/frontend/src/services/api/ApiNativeClient/index.ts index 52310449..8223a093 100644 --- a/frontend/src/services/api/ApiNativeClient/index.ts +++ b/frontend/src/services/api/ApiNativeClient/index.ts @@ -1,32 +1,29 @@ -import { ApiClient } from '../api'; +import { ApiClient, Auth } from '..'; import { systemClient } from '../../System'; class ApiNativeClient implements ApiClient { private assetsCache: { [path: string]: string } = {}; private assetsPromises: { [path: string]: Promise } = {}; - private readonly getHeaders: (tokenSHA256?: string) => HeadersInit = (tokenSHA256) => { + private readonly getHeaders: (auth?: Auth) => HeadersInit = (auth) => { let headers = { 'Content-Type': 'application/json', }; - if (tokenSHA256) { + if (auth) { headers = { ...headers, ...{ - Authorization: `Token ${tokenSHA256}`, + Authorization: `Token ${auth.tokenSHA256}`, }, }; } - const encrypted_private_key = systemClient.getCookie('encrypted_private_key'); - const public_key = systemClient.getCookie('public_key'); - - if (encrypted_private_key && public_key) { + if (auth?.keys) { headers = { ...headers, ...{ - Cookie: `public_key=${public_key};encrypted_private_key=${encrypted_private_key}`, + Cookie: `public_key=${auth.keys.pubKey};encrypted_private_key=${auth.keys.encPrivKey}`, }, }; } @@ -52,46 +49,46 @@ class ApiNativeClient implements ApiClient { return await new Promise((res, _rej) => res({})); }; - public delete: ( - baseUrl: string, - path: string, - tokenSHA256?: string, - ) => Promise = async (baseUrl, path, tokenSHA256) => { - return await window.NativeRobosats?.postMessage({ - category: 'http', - type: 'delete', - baseUrl, - path, - headers: this.getHeaders(tokenSHA256), - }).then(this.parseResponse); - }; + public delete: (baseUrl: string, path: string, auth?: Auth) => Promise = + async (baseUrl, path, auth) => { + return await window.NativeRobosats?.postMessage({ + category: 'http', + type: 'delete', + baseUrl, + path, + headers: this.getHeaders(auth), + }).then(this.parseResponse); + }; public post: ( baseUrl: string, path: string, body: object, - tokenSHA256?: string, - ) => Promise = async (baseUrl, path, body, tokenSHA256) => { + auth?: Auth, + ) => Promise = async (baseUrl, path, body, auth) => { return await window.NativeRobosats?.postMessage({ category: 'http', type: 'post', baseUrl, path, body, - headers: this.getHeaders(tokenSHA256), + headers: this.getHeaders(auth), }).then(this.parseResponse); }; - public get: (baseUrl: string, path: string, tokenSHA256?: string) => Promise = - async (baseUrl, path, tokenSHA256) => { - return await window.NativeRobosats?.postMessage({ - category: 'http', - type: 'get', - baseUrl, - path, - headers: this.getHeaders(tokenSHA256), - }).then(this.parseResponse); - }; + public get: (baseUrl: string, path: string, auth?: Auth) => Promise = async ( + baseUrl, + path, + auth, + ) => { + return await window.NativeRobosats?.postMessage({ + category: 'http', + type: 'get', + baseUrl, + path, + headers: this.getHeaders(auth), + }).then(this.parseResponse); + }; public fileImageUrl: (baseUrl: string, path: string) => Promise = async ( baseUrl, diff --git a/frontend/src/services/api/ApiWebClient/index.ts b/frontend/src/services/api/ApiWebClient/index.ts index a366204c..a942c87c 100644 --- a/frontend/src/services/api/ApiWebClient/index.ts +++ b/frontend/src/services/api/ApiWebClient/index.ts @@ -1,76 +1,75 @@ -import { ApiClient } from '..'; +import { ApiClient, Auth } from '..'; +import { systemClient } from '../../System'; class ApiWebClient implements ApiClient { - private readonly getHeaders: (tokenSHA256?: string) => HeadersInit = (tokenSHA256) => { + private readonly getHeaders: (auth?: Auth) => HeadersInit = (auth) => { let headers = { 'Content-Type': 'application/json', }; - if (tokenSHA256) { + if (auth) { headers = { ...headers, ...{ - Authorization: `Token ${tokenSHA256}`, + Authorization: `Token ${auth.tokenSHA256}`, }, }; } + // set cookies before sending the request + if (auth?.keys) { + systemClient.setCookie('public_key', auth.keys.pubKey); + systemClient.setCookie('encrypted_private_key', auth.keys.encPrivKey); + } + return headers; }; - public post: ( - baseUrl: string, - path: string, - body: object, - tokenSHA256?: string, - ) => Promise = async (baseUrl, path, body, tokenSHA256) => { - const requestOptions = { - method: 'POST', - headers: this.getHeaders(tokenSHA256), - body: JSON.stringify(body), + public post: (baseUrl: string, path: string, body: object, auth?: Auth) => Promise = + async (baseUrl, path, body, auth) => { + const requestOptions = { + method: 'POST', + headers: this.getHeaders(auth), + body: JSON.stringify(body), + }; + + return await fetch(baseUrl + path, requestOptions).then( + async (response) => await response.json(), + ); }; - return await fetch(baseUrl + path, requestOptions).then( - async (response) => await response.json(), - ); - }; - - public put: ( - baseUrl: string, - path: string, - body: object, - tokenSHA256?: string, - ) => Promise = async (baseUrl, path, body, tokenSHA256) => { - const requestOptions = { - method: 'PUT', - headers: this.getHeaders(tokenSHA256), - body: JSON.stringify(body), + public put: (baseUrl: string, path: string, body: object, auth?: Auth) => Promise = + async (baseUrl, path, body, auth) => { + const requestOptions = { + method: 'PUT', + headers: this.getHeaders(auth), + body: JSON.stringify(body), + }; + return await fetch(baseUrl + path, requestOptions).then( + async (response) => await response.json(), + ); }; - return await fetch(baseUrl + path, requestOptions).then( - async (response) => await response.json(), - ); - }; - public delete: (baseUrl: string, path: string, tokenSHA256?: string) => Promise = async ( + public delete: (baseUrl: string, path: string, auth?: Auth) => Promise = async ( baseUrl, path, - tokenSHA256, + auth, ) => { const requestOptions = { method: 'DELETE', - headers: this.getHeaders(tokenSHA256), + headers: this.getHeaders(auth), }; return await fetch(baseUrl + path, requestOptions).then( async (response) => await response.json(), ); }; - public get: (baseUrl: string, path: string, tokenSHA256?: string) => Promise = async ( + public get: (baseUrl: string, path: string, auth?: Auth) => Promise = async ( baseUrl, path, - tokenSHA256, + auth, ) => { - return await fetch(baseUrl + path, { headers: this.getHeaders(tokenSHA256) }).then( + return await fetch(baseUrl + path, { headers: this.getHeaders(auth) }).then( async (response) => await response.json(), ); }; diff --git a/frontend/src/services/api/index.ts b/frontend/src/services/api/index.ts index 0864f2e7..b9ac94d8 100644 --- a/frontend/src/services/api/index.ts +++ b/frontend/src/services/api/index.ts @@ -1,21 +1,16 @@ import ApiWebClient from './ApiWebClient'; import ApiNativeClient from './ApiNativeClient'; +export interface Auth { + tokenSHA256: string; + keys?: { pubKey: string; encPrivKey: string }; +} + export interface ApiClient { - post: ( - baseUrl: string, - path: string, - body: object, - tokenSHA256?: string, - ) => Promise; - put: ( - baseUrl: string, - path: string, - body: object, - tokenSHA256?: string, - ) => Promise; - get: (baseUrl: string, path: string, tokenSHA256?: string) => Promise; - delete: (baseUrl: string, path: string, tokenSHA256?: string) => Promise; + post: (baseUrl: string, path: string, body: object, auth?: Auth) => Promise; + put: (baseUrl: string, path: string, body: object, auth?: Auth) => Promise; + get: (baseUrl: string, path: string, auth?: Auth) => Promise; + delete: (baseUrl: string, path: string, auth?: Auth) => Promise; fileImageUrl?: (baseUrl: string, path: string) => Promise; }