Initial Commit
This commit is contained in:
commit
22c2de9cc6
12 changed files with 594 additions and 0 deletions
25
.dockerignore
Normal file
25
.dockerignore
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
**/.dockerignore
|
||||
**/.env
|
||||
**/.git
|
||||
**/.gitignore
|
||||
**/.project
|
||||
**/.settings
|
||||
**/.toolstarget
|
||||
**/.vs
|
||||
**/.vscode
|
||||
**/.idea
|
||||
**/*.*proj.user
|
||||
**/*.dbmdl
|
||||
**/*.jfm
|
||||
**/azds.yaml
|
||||
**/bin
|
||||
**/charts
|
||||
**/docker-compose*
|
||||
**/Dockerfile*
|
||||
**/node_modules
|
||||
**/npm-debug.log
|
||||
**/obj
|
||||
**/secrets.dev.yaml
|
||||
**/values.dev.yaml
|
||||
LICENSE
|
||||
README.md
|
||||
31
.github/workflows/build.yml
vendored
Normal file
31
.github/workflows/build.yml
vendored
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
name: Build Docker Image
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
docker:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v2
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v2
|
||||
- name: Login to GitHub Container Registry
|
||||
uses: docker/login-action@v2
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
- name: Build and push
|
||||
uses: docker/build-push-action@v5
|
||||
with:
|
||||
push: true
|
||||
file: ./VRCAuthProxy/Dockerfile
|
||||
platforms: linux/amd64
|
||||
tags: ghcr.io/pridevrcommunity/VRCAuthProxy:latest
|
||||
cache-from: type=gha
|
||||
cache-to: type=gha,mode=max
|
||||
45
.gitignore
vendored
Normal file
45
.gitignore
vendored
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
*.swp
|
||||
*.*~
|
||||
project.lock.json
|
||||
.DS_Store
|
||||
*.pyc
|
||||
nupkg/
|
||||
|
||||
# Visual Studio Code
|
||||
.vscode/
|
||||
|
||||
# Rider
|
||||
.idea/
|
||||
|
||||
# Visual Studio
|
||||
.vs/
|
||||
|
||||
# Fleet
|
||||
.fleet/
|
||||
|
||||
# Code Rush
|
||||
.cr/
|
||||
|
||||
# User-specific files
|
||||
*.suo
|
||||
*.user
|
||||
*.userosscache
|
||||
*.sln.docstates
|
||||
|
||||
# Build results
|
||||
[Dd]ebug/
|
||||
[Dd]ebugPublic/
|
||||
[Rr]elease/
|
||||
[Rr]eleases/
|
||||
x64/
|
||||
x86/
|
||||
build/
|
||||
bld/
|
||||
[Bb]in/
|
||||
[Oo]bj/
|
||||
[Oo]ut/
|
||||
msbuild.log
|
||||
msbuild.err
|
||||
msbuild.wrn
|
||||
|
||||
VRCAuthProxy/appsettings.json
|
||||
56
Models/VrcAuthProxyFlow.drawio
Normal file
56
Models/VrcAuthProxyFlow.drawio
Normal file
|
|
@ -0,0 +1,56 @@
|
|||
<mxfile host="app.diagrams.net" agent="Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/130.0.0.0 Safari/537.36" version="24.8.4">
|
||||
<diagram id="TGkC-fbVlQBUkP2gEpgm" name="Page-1">
|
||||
<mxGraphModel dx="1434" dy="738" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="827" pageHeight="1169" math="0" shadow="0">
|
||||
<root>
|
||||
<mxCell id="0" />
|
||||
<mxCell id="1" parent="0" />
|
||||
<mxCell id="Fao0HeDoRiUuBRh6Pi_Q-1" value="API Accounts" style="swimlane;fontStyle=0;childLayout=stackLayout;horizontal=1;startSize=30;horizontalStack=0;resizeParent=1;resizeParentMax=0;resizeLast=0;collapsible=1;marginBottom=0;whiteSpace=wrap;html=1;" vertex="1" parent="1">
|
||||
<mxGeometry x="420" y="40" width="140" height="120" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="Fao0HeDoRiUuBRh6Pi_Q-2" value="Account 1" style="text;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;spacingLeft=4;spacingRight=4;overflow=hidden;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;rotatable=0;whiteSpace=wrap;html=1;" vertex="1" parent="Fao0HeDoRiUuBRh6Pi_Q-1">
|
||||
<mxGeometry y="30" width="140" height="30" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="Fao0HeDoRiUuBRh6Pi_Q-3" value="Account 2" style="text;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;spacingLeft=4;spacingRight=4;overflow=hidden;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;rotatable=0;whiteSpace=wrap;html=1;" vertex="1" parent="Fao0HeDoRiUuBRh6Pi_Q-1">
|
||||
<mxGeometry y="60" width="140" height="30" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="Fao0HeDoRiUuBRh6Pi_Q-4" value="Account 3" style="text;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;spacingLeft=4;spacingRight=4;overflow=hidden;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;rotatable=0;whiteSpace=wrap;html=1;" vertex="1" parent="Fao0HeDoRiUuBRh6Pi_Q-1">
|
||||
<mxGeometry y="90" width="140" height="30" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="Fao0HeDoRiUuBRh6Pi_Q-10" value="" style="ellipse;html=1;shape=startState;fillColor=#000000;strokeColor=#ff0000;" vertex="1" parent="1">
|
||||
<mxGeometry x="70" y="50" width="30" height="30" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="Fao0HeDoRiUuBRh6Pi_Q-11" value="" style="edgeStyle=orthogonalEdgeStyle;html=1;verticalAlign=bottom;endArrow=open;endSize=8;strokeColor=#ff0000;rounded=0;entryX=-0.007;entryY=0.761;entryDx=0;entryDy=0;entryPerimeter=0;" edge="1" source="Fao0HeDoRiUuBRh6Pi_Q-10" parent="1" target="Fao0HeDoRiUuBRh6Pi_Q-12">
|
||||
<mxGeometry relative="1" as="geometry">
|
||||
<mxPoint x="85" y="140" as="targetPoint" />
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="Fao0HeDoRiUuBRh6Pi_Q-13" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=0;exitY=0;exitDx=65;exitDy=0;exitPerimeter=0;entryX=0;entryY=0;entryDx=0;entryDy=0;" edge="1" parent="1" source="Fao0HeDoRiUuBRh6Pi_Q-12" target="Fao0HeDoRiUuBRh6Pi_Q-1">
|
||||
<mxGeometry relative="1" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="Fao0HeDoRiUuBRh6Pi_Q-12" value="<pre>for cred in credentials:</pre><pre> account = new VRCAPI(cred)</pre><pre> apiAccounts.push(account)</pre><pre>server.listen()</pre>" style="verticalAlign=top;align=left;spacingTop=8;spacingLeft=2;spacingRight=12;shape=cube;size=10;direction=south;fontStyle=4;html=1;whiteSpace=wrap;" vertex="1" parent="1">
|
||||
<mxGeometry x="30" y="180" width="230" height="140" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="Fao0HeDoRiUuBRh6Pi_Q-14" value="VRCApi" style="shape=folder;fontStyle=1;spacingTop=10;tabWidth=40;tabHeight=14;tabPosition=left;html=1;whiteSpace=wrap;" vertex="1" parent="1">
|
||||
<mxGeometry x="180" y="40" width="70" height="50" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="Fao0HeDoRiUuBRh6Pi_Q-15" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=0.5;exitY=1;exitDx=0;exitDy=0;exitPerimeter=0;entryX=-0.014;entryY=0.196;entryDx=0;entryDy=0;entryPerimeter=0;" edge="1" parent="1" source="Fao0HeDoRiUuBRh6Pi_Q-14" target="Fao0HeDoRiUuBRh6Pi_Q-12">
|
||||
<mxGeometry relative="1" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="Fao0HeDoRiUuBRh6Pi_Q-16" value="HTTP Request" style="rounded=1;whiteSpace=wrap;html=1;arcSize=40;fontColor=#000000;fillColor=#ffffc0;strokeColor=#ff0000;" vertex="1" parent="1">
|
||||
<mxGeometry x="370" y="310" width="120" height="40" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="Fao0HeDoRiUuBRh6Pi_Q-17" value="" style="edgeStyle=orthogonalEdgeStyle;html=1;verticalAlign=bottom;endArrow=open;endSize=8;strokeColor=#ff0000;rounded=0;" edge="1" source="Fao0HeDoRiUuBRh6Pi_Q-16" parent="1">
|
||||
<mxGeometry relative="1" as="geometry">
|
||||
<mxPoint x="430" y="410" as="targetPoint" />
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="Fao0HeDoRiUuBRh6Pi_Q-18" value="<pre>// account = firstWorkingAccount()</pre><pre>result = account.request(request.path, request.method, request.body)</pre><pre>return result</pre>" style="verticalAlign=top;align=left;spacingTop=8;spacingLeft=2;spacingRight=12;shape=cube;size=10;direction=south;fontStyle=4;html=1;whiteSpace=wrap;" vertex="1" parent="1">
|
||||
<mxGeometry x="350" y="410" width="470" height="120" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="Fao0HeDoRiUuBRh6Pi_Q-19" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=1;exitY=0;exitDx=0;exitDy=0;entryX=-0.05;entryY=0.496;entryDx=0;entryDy=0;entryPerimeter=0;" edge="1" parent="1" source="Fao0HeDoRiUuBRh6Pi_Q-1" target="Fao0HeDoRiUuBRh6Pi_Q-18">
|
||||
<mxGeometry relative="1" as="geometry" />
|
||||
</mxCell>
|
||||
</root>
|
||||
</mxGraphModel>
|
||||
</diagram>
|
||||
</mxfile>
|
||||
101
Models/VrcAuthProxyModels.drawio
Normal file
101
Models/VrcAuthProxyModels.drawio
Normal file
|
|
@ -0,0 +1,101 @@
|
|||
<mxfile host="app.diagrams.net" agent="Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/130.0.0.0 Safari/537.36" version="24.8.4">
|
||||
<diagram id="C5RBs43oDa-KdzZeNtuy" name="Page-1">
|
||||
<mxGraphModel dx="1434" dy="738" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="827" pageHeight="1169" math="0" shadow="0">
|
||||
<root>
|
||||
<mxCell id="WIyWlLk6GJQsqaUBKTNV-0" />
|
||||
<mxCell id="WIyWlLk6GJQsqaUBKTNV-1" parent="WIyWlLk6GJQsqaUBKTNV-0" />
|
||||
<mxCell id="DkdebAAeG6_I_vclzc5Z-1" value="API Accounts" style="swimlane;fontStyle=0;childLayout=stackLayout;horizontal=1;startSize=30;horizontalStack=0;resizeParent=1;resizeParentMax=0;resizeLast=0;collapsible=1;marginBottom=0;whiteSpace=wrap;html=1;" vertex="1" parent="WIyWlLk6GJQsqaUBKTNV-1">
|
||||
<mxGeometry x="530" y="40" width="140" height="120" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="DkdebAAeG6_I_vclzc5Z-2" value="Account 1" style="text;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;spacingLeft=4;spacingRight=4;overflow=hidden;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;rotatable=0;whiteSpace=wrap;html=1;" vertex="1" parent="DkdebAAeG6_I_vclzc5Z-1">
|
||||
<mxGeometry y="30" width="140" height="30" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="DkdebAAeG6_I_vclzc5Z-3" value="Account 2" style="text;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;spacingLeft=4;spacingRight=4;overflow=hidden;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;rotatable=0;whiteSpace=wrap;html=1;" vertex="1" parent="DkdebAAeG6_I_vclzc5Z-1">
|
||||
<mxGeometry y="60" width="140" height="30" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="DkdebAAeG6_I_vclzc5Z-4" value="Account 3" style="text;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;spacingLeft=4;spacingRight=4;overflow=hidden;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;rotatable=0;whiteSpace=wrap;html=1;" vertex="1" parent="DkdebAAeG6_I_vclzc5Z-1">
|
||||
<mxGeometry y="90" width="140" height="30" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="DkdebAAeG6_I_vclzc5Z-11" value="Credentials" style="swimlane;fontStyle=0;childLayout=stackLayout;horizontal=1;startSize=30;horizontalStack=0;resizeParent=1;resizeParentMax=0;resizeLast=0;collapsible=1;marginBottom=0;whiteSpace=wrap;html=1;" vertex="1" parent="WIyWlLk6GJQsqaUBKTNV-1">
|
||||
<mxGeometry x="320" y="40" width="140" height="120" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="DkdebAAeG6_I_vclzc5Z-12" value="Account 1" style="text;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;spacingLeft=4;spacingRight=4;overflow=hidden;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;rotatable=0;whiteSpace=wrap;html=1;" vertex="1" parent="DkdebAAeG6_I_vclzc5Z-11">
|
||||
<mxGeometry y="30" width="140" height="30" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="DkdebAAeG6_I_vclzc5Z-13" value="Account 2" style="text;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;spacingLeft=4;spacingRight=4;overflow=hidden;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;rotatable=0;whiteSpace=wrap;html=1;" vertex="1" parent="DkdebAAeG6_I_vclzc5Z-11">
|
||||
<mxGeometry y="60" width="140" height="30" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="DkdebAAeG6_I_vclzc5Z-14" value="Account 3" style="text;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;spacingLeft=4;spacingRight=4;overflow=hidden;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;rotatable=0;whiteSpace=wrap;html=1;" vertex="1" parent="DkdebAAeG6_I_vclzc5Z-11">
|
||||
<mxGeometry y="90" width="140" height="30" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="DkdebAAeG6_I_vclzc5Z-30" value="Credential" style="shape=table;startSize=30;container=1;collapsible=1;childLayout=tableLayout;fixedRows=1;rowLines=0;fontStyle=1;align=center;resizeLast=1;html=1;" vertex="1" parent="WIyWlLk6GJQsqaUBKTNV-1">
|
||||
<mxGeometry x="30" y="40" width="180" height="150" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="DkdebAAeG6_I_vclzc5Z-31" value="" style="shape=tableRow;horizontal=0;startSize=0;swimlaneHead=0;swimlaneBody=0;fillColor=none;collapsible=0;dropTarget=0;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;top=0;left=0;right=0;bottom=1;" vertex="1" parent="DkdebAAeG6_I_vclzc5Z-30">
|
||||
<mxGeometry y="30" width="180" height="30" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="DkdebAAeG6_I_vclzc5Z-32" value="PK" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;fontStyle=1;overflow=hidden;whiteSpace=wrap;html=1;" vertex="1" parent="DkdebAAeG6_I_vclzc5Z-31">
|
||||
<mxGeometry width="30" height="30" as="geometry">
|
||||
<mxRectangle width="30" height="30" as="alternateBounds" />
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="DkdebAAeG6_I_vclzc5Z-33" value="Username" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;align=left;spacingLeft=6;fontStyle=5;overflow=hidden;whiteSpace=wrap;html=1;" vertex="1" parent="DkdebAAeG6_I_vclzc5Z-31">
|
||||
<mxGeometry x="30" width="150" height="30" as="geometry">
|
||||
<mxRectangle width="150" height="30" as="alternateBounds" />
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="DkdebAAeG6_I_vclzc5Z-34" value="" style="shape=tableRow;horizontal=0;startSize=0;swimlaneHead=0;swimlaneBody=0;fillColor=none;collapsible=0;dropTarget=0;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;top=0;left=0;right=0;bottom=0;" vertex="1" parent="DkdebAAeG6_I_vclzc5Z-30">
|
||||
<mxGeometry y="60" width="180" height="30" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="DkdebAAeG6_I_vclzc5Z-35" value="" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;editable=1;overflow=hidden;whiteSpace=wrap;html=1;" vertex="1" parent="DkdebAAeG6_I_vclzc5Z-34">
|
||||
<mxGeometry width="30" height="30" as="geometry">
|
||||
<mxRectangle width="30" height="30" as="alternateBounds" />
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="DkdebAAeG6_I_vclzc5Z-36" value="Password" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;align=left;spacingLeft=6;overflow=hidden;whiteSpace=wrap;html=1;" vertex="1" parent="DkdebAAeG6_I_vclzc5Z-34">
|
||||
<mxGeometry x="30" width="150" height="30" as="geometry">
|
||||
<mxRectangle width="150" height="30" as="alternateBounds" />
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="DkdebAAeG6_I_vclzc5Z-37" value="" style="shape=tableRow;horizontal=0;startSize=0;swimlaneHead=0;swimlaneBody=0;fillColor=none;collapsible=0;dropTarget=0;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;top=0;left=0;right=0;bottom=0;" vertex="1" parent="DkdebAAeG6_I_vclzc5Z-30">
|
||||
<mxGeometry y="90" width="180" height="30" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="DkdebAAeG6_I_vclzc5Z-38" value="" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;editable=1;overflow=hidden;whiteSpace=wrap;html=1;" vertex="1" parent="DkdebAAeG6_I_vclzc5Z-37">
|
||||
<mxGeometry width="30" height="30" as="geometry">
|
||||
<mxRectangle width="30" height="30" as="alternateBounds" />
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="DkdebAAeG6_I_vclzc5Z-39" value="TOTP Key" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;align=left;spacingLeft=6;overflow=hidden;whiteSpace=wrap;html=1;" vertex="1" parent="DkdebAAeG6_I_vclzc5Z-37">
|
||||
<mxGeometry x="30" width="150" height="30" as="geometry">
|
||||
<mxRectangle width="150" height="30" as="alternateBounds" />
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="DkdebAAeG6_I_vclzc5Z-40" value="" style="shape=tableRow;horizontal=0;startSize=0;swimlaneHead=0;swimlaneBody=0;fillColor=none;collapsible=0;dropTarget=0;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;top=0;left=0;right=0;bottom=0;" vertex="1" parent="DkdebAAeG6_I_vclzc5Z-30">
|
||||
<mxGeometry y="120" width="180" height="30" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="DkdebAAeG6_I_vclzc5Z-41" value="" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;editable=1;overflow=hidden;whiteSpace=wrap;html=1;" vertex="1" parent="DkdebAAeG6_I_vclzc5Z-40">
|
||||
<mxGeometry width="30" height="30" as="geometry">
|
||||
<mxRectangle width="30" height="30" as="alternateBounds" />
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="DkdebAAeG6_I_vclzc5Z-42" value="" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;align=left;spacingLeft=6;overflow=hidden;whiteSpace=wrap;html=1;" vertex="1" parent="DkdebAAeG6_I_vclzc5Z-40">
|
||||
<mxGeometry x="30" width="150" height="30" as="geometry">
|
||||
<mxRectangle width="150" height="30" as="alternateBounds" />
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="DkdebAAeG6_I_vclzc5Z-53" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=1;exitY=0.5;exitDx=0;exitDy=0;entryX=0;entryY=0.5;entryDx=0;entryDy=0;" edge="1" parent="WIyWlLk6GJQsqaUBKTNV-1" source="DkdebAAeG6_I_vclzc5Z-12" target="DkdebAAeG6_I_vclzc5Z-2">
|
||||
<mxGeometry relative="1" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="DkdebAAeG6_I_vclzc5Z-57" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=1;exitY=0.5;exitDx=0;exitDy=0;entryX=0;entryY=0;entryDx=0;entryDy=0;" edge="1" parent="WIyWlLk6GJQsqaUBKTNV-1" source="DkdebAAeG6_I_vclzc5Z-31" target="DkdebAAeG6_I_vclzc5Z-11">
|
||||
<mxGeometry relative="1" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="DkdebAAeG6_I_vclzc5Z-59" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=1;exitY=0.5;exitDx=0;exitDy=0;entryX=0;entryY=0.5;entryDx=0;entryDy=0;" edge="1" parent="WIyWlLk6GJQsqaUBKTNV-1" source="DkdebAAeG6_I_vclzc5Z-14" target="DkdebAAeG6_I_vclzc5Z-4">
|
||||
<mxGeometry relative="1" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="DkdebAAeG6_I_vclzc5Z-60" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=1;exitY=0.5;exitDx=0;exitDy=0;entryX=0;entryY=0.5;entryDx=0;entryDy=0;" edge="1" parent="WIyWlLk6GJQsqaUBKTNV-1" source="DkdebAAeG6_I_vclzc5Z-13" target="DkdebAAeG6_I_vclzc5Z-3">
|
||||
<mxGeometry relative="1" as="geometry" />
|
||||
</mxCell>
|
||||
</root>
|
||||
</mxGraphModel>
|
||||
</diagram>
|
||||
</mxfile>
|
||||
16
VRCAuthProxy.sln
Normal file
16
VRCAuthProxy.sln
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "VRCAuthProxy", "VRCAuthProxy\VRCAuthProxy.csproj", "{BB549439-B34F-4BCA-B8FC-173114631646}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
Release|Any CPU = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{BB549439-B34F-4BCA-B8FC-173114631646}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{BB549439-B34F-4BCA-B8FC-173114631646}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{BB549439-B34F-4BCA-B8FC-173114631646}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{BB549439-B34F-4BCA-B8FC-173114631646}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
EndGlobal
|
||||
12
VRCAuthProxy/ApiClientWithCookies.cs
Normal file
12
VRCAuthProxy/ApiClientWithCookies.cs
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
using System.Net;
|
||||
using VRChat.API.Client;
|
||||
|
||||
namespace VRCAuthProxy;
|
||||
|
||||
public class ApiClientWithCookies : ApiClient
|
||||
{
|
||||
public CookieContainer GetCookieContainer()
|
||||
{
|
||||
return CookieContainer;
|
||||
}
|
||||
}
|
||||
62
VRCAuthProxy/Config.cs
Normal file
62
VRCAuthProxy/Config.cs
Normal file
|
|
@ -0,0 +1,62 @@
|
|||
using System.Text.Json;
|
||||
|
||||
namespace VRCAuthProxy;
|
||||
|
||||
public class ConfigAccount
|
||||
{
|
||||
public string username { get; set; }
|
||||
public string password { get; set; }
|
||||
public string? totpSecret { get; set; }
|
||||
}
|
||||
|
||||
public class iConfig
|
||||
{
|
||||
public List<ConfigAccount> accounts { get; set; }
|
||||
}
|
||||
|
||||
// Load config from appsettings.json
|
||||
public class Config
|
||||
{
|
||||
private static Config? _instance;
|
||||
public List<ConfigAccount> Accounts { get; set; }
|
||||
|
||||
public static Config Instance
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_instance == null) _instance = Load();
|
||||
return _instance;
|
||||
}
|
||||
}
|
||||
|
||||
public static Config Load()
|
||||
{
|
||||
var config = new Config();
|
||||
var configPath = Path.Combine(AppContext.BaseDirectory, "appsettings.json");
|
||||
if (File.Exists(configPath))
|
||||
{
|
||||
var json = File.ReadAllText(configPath);
|
||||
// load iConfig
|
||||
var iConfig = JsonSerializer.Deserialize<iConfig>(json);
|
||||
if (iConfig == null) throw new Exception("Failed to load config");
|
||||
config.Accounts = iConfig.accounts;
|
||||
}
|
||||
else
|
||||
{
|
||||
Console.WriteLine("No config found at " + configPath);
|
||||
}
|
||||
|
||||
return config;
|
||||
}
|
||||
|
||||
public void Save()
|
||||
{
|
||||
var configPath = Path.Combine(AppContext.BaseDirectory, "appsettings.json");
|
||||
var iConfig = new iConfig
|
||||
{
|
||||
accounts = Accounts
|
||||
};
|
||||
var json = JsonSerializer.Serialize(iConfig, new JsonSerializerOptions { WriteIndented = true });
|
||||
File.WriteAllText(configPath, json);
|
||||
}
|
||||
}
|
||||
21
VRCAuthProxy/Dockerfile
Normal file
21
VRCAuthProxy/Dockerfile
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
FROM mcr.microsoft.com/dotnet/runtime:8.0 AS base
|
||||
USER $APP_UID
|
||||
WORKDIR /app
|
||||
|
||||
FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build
|
||||
ARG BUILD_CONFIGURATION=Release
|
||||
WORKDIR /src
|
||||
COPY ["VRCAuthProxy/VRCAuthProxy.csproj", "VRCAuthProxy/"]
|
||||
RUN dotnet restore "VRCAuthProxy/VRCAuthProxy.csproj"
|
||||
COPY . .
|
||||
WORKDIR "/src/VRCAuthProxy"
|
||||
RUN dotnet build "VRCAuthProxy.csproj" -c $BUILD_CONFIGURATION -o /app/build
|
||||
|
||||
FROM build AS publish
|
||||
ARG BUILD_CONFIGURATION=Release
|
||||
RUN dotnet publish "VRCAuthProxy.csproj" -c $BUILD_CONFIGURATION -o /app/publish /p:UseAppHost=false
|
||||
|
||||
FROM base AS final
|
||||
WORKDIR /app
|
||||
COPY --from=publish /app/publish .
|
||||
ENTRYPOINT ["dotnet", "VRCAuthProxy.dll"]
|
||||
184
VRCAuthProxy/Program.cs
Normal file
184
VRCAuthProxy/Program.cs
Normal file
|
|
@ -0,0 +1,184 @@
|
|||
// See https://aka.ms/new-console-template for more information
|
||||
|
||||
using System.Text;
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using OtpNet;
|
||||
using VRCAuthProxy;
|
||||
using VRChat.API.Api;
|
||||
using VRChat.API.Client;
|
||||
using VRChat.API.Model;
|
||||
using HttpMethod = System.Net.Http.HttpMethod;
|
||||
|
||||
var httpClient = new HttpClient();
|
||||
|
||||
var apiAccounts = new List<ApiClientWithCookies>();
|
||||
|
||||
|
||||
|
||||
// Push the first account to the end of the list
|
||||
void RotateAccount()
|
||||
{
|
||||
var account = Config.Instance.Accounts.First();
|
||||
Config.Instance.Accounts.Remove(account);
|
||||
Config.Instance.Accounts.Add(account);
|
||||
}
|
||||
|
||||
|
||||
String UserAgent = "VRCAuthProxy V1.0.0 (https://github.com/PrideVRCommunity/VRCAuthProxy)";
|
||||
|
||||
var builder = WebApplication.CreateBuilder(args);
|
||||
var app = builder.Build();
|
||||
|
||||
Config.Instance.Accounts.ForEach(async account =>
|
||||
{
|
||||
try
|
||||
{
|
||||
Console.WriteLine($"Creating API for {account.username}");
|
||||
// clone base config
|
||||
var config = new Configuration();
|
||||
config.UserAgent = UserAgent;
|
||||
config.BasePath = "https://api.vrchat.cloud/api/1";
|
||||
config.Username = account.username;
|
||||
config.Password = account.password;
|
||||
|
||||
// create api client
|
||||
var api = new ApiClientWithCookies();
|
||||
var authApi = new AuthenticationApi(api, api, config);
|
||||
var authResp = authApi.GetCurrentUserWithHttpInfo();
|
||||
if (authResp.RawContent.Contains("totp"))
|
||||
{
|
||||
Console.WriteLine($"TOTP required for {account.username}");
|
||||
if (account.totpSecret == null)
|
||||
{
|
||||
Console.WriteLine($"No TOTP secret found for {account.username}");
|
||||
return;
|
||||
}
|
||||
|
||||
// totp constructor needs a byte array decoded from the base32 secret
|
||||
var totp = new Totp(Base32Encoding.ToBytes(account.totpSecret.Replace(" ", "")));
|
||||
var code = totp.ComputeTotp();
|
||||
if (code == null)
|
||||
{
|
||||
Console.WriteLine($"Failed to generate TOTP for {account.username}");
|
||||
return;
|
||||
}
|
||||
|
||||
var verifyRes = authApi.Verify2FA(new TwoFactorAuthCode(code));
|
||||
if (verifyRes == null || verifyRes.Verified == false)
|
||||
{
|
||||
Console.WriteLine($"Failed to verify TOTP for {account.username}");
|
||||
return;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
var curUser = authApi.GetCurrentUser();
|
||||
if (curUser == null) throw new Exception("Failed to get current user");
|
||||
Console.WriteLine($"Logged in as {curUser.DisplayName}");
|
||||
apiAccounts.Add(api);
|
||||
} catch (ApiException e)
|
||||
{
|
||||
Console.WriteLine($"Failed to create API for {account.username}: {e.Message}, {e.ErrorCode}, {e}");
|
||||
}
|
||||
});
|
||||
|
||||
app.MapGet("/", () => $"Logged in with {apiAccounts.Count} accounts");
|
||||
app.MapGet("/rotate", () =>
|
||||
{
|
||||
RotateAccount();
|
||||
return "Rotated account";
|
||||
});
|
||||
// Proxy all requests starting with /api/1 to the VRChat API
|
||||
app.Use(async (context, next) =>
|
||||
{
|
||||
if (context.Request.Path.StartsWithSegments("/api/1"))
|
||||
{
|
||||
if (apiAccounts.Count == 0)
|
||||
{
|
||||
context.Response.StatusCode = 500;
|
||||
await context.Response.WriteAsync("No accounts available");
|
||||
return;
|
||||
}
|
||||
|
||||
var account = apiAccounts.First();
|
||||
var requestOpts = new RequestOptions();
|
||||
requestOpts.Operation = context.Request.Method;
|
||||
var path = context.Request.Path.ToString().Replace("/api/1", "") + context.Request.QueryString;
|
||||
|
||||
var message = new HttpRequestMessage
|
||||
{
|
||||
RequestUri = new Uri("https://api.vrchat.cloud/api/1" + path),
|
||||
Method = new HttpMethod(context.Request.Method)
|
||||
};
|
||||
|
||||
// Add common headers to request message
|
||||
message.Headers.Add("User-Agent", UserAgent);
|
||||
message.Headers.Add("Cookie", account.GetCookieContainer().GetCookieHeader(new Uri("https://api.vrchat.cloud")));
|
||||
|
||||
// Handle request body for methods that support content (POST, PUT, DELETE)
|
||||
if (context.Request.ContentLength > 0 || context.Request.Headers.ContainsKey("Transfer-Encoding"))
|
||||
{
|
||||
message.Content = new StreamContent(context.Request.Body);
|
||||
|
||||
// Add content-specific headers to message.Content.Headers
|
||||
foreach (var header in context.Request.Headers)
|
||||
{
|
||||
if (header.Key.Equals("Content-Type", StringComparison.OrdinalIgnoreCase) ||
|
||||
header.Key.Equals("Content-Length", StringComparison.OrdinalIgnoreCase) ||
|
||||
header.Key.Equals("Content-Disposition", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
message.Content.Headers.TryAddWithoutValidation(header.Key, header.Value.ToArray());
|
||||
}
|
||||
else if (!message.Headers.Contains(header.Key) && header.Key != "Host")
|
||||
{
|
||||
// Add non-content headers to message.Headers
|
||||
message.Headers.TryAddWithoutValidation(header.Key, header.Value.ToArray());
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Add non-body headers to message.Headers for GET and other bodyless requests
|
||||
foreach (var header in context.Request.Headers)
|
||||
{
|
||||
if (!message.Headers.Contains(header.Key) && header.Key != "Host")
|
||||
{
|
||||
message.Headers.TryAddWithoutValidation(header.Key, header.Value.ToArray());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Send the request
|
||||
var response = await httpClient.SendAsync(message, HttpCompletionOption.ResponseHeadersRead);
|
||||
|
||||
// Copy response status code and headers
|
||||
context.Response.StatusCode = (int)response.StatusCode;
|
||||
foreach (var header in response.Headers)
|
||||
{
|
||||
context.Response.Headers[header.Key] = header.Value.ToArray();
|
||||
}
|
||||
foreach (var header in response.Content.Headers)
|
||||
{
|
||||
context.Response.Headers[header.Key] = header.Value.ToArray();
|
||||
}
|
||||
context.Response.Headers.Remove("transfer-encoding");
|
||||
|
||||
// Copy response content to the response body
|
||||
using (var responseStream = await response.Content.ReadAsStreamAsync())
|
||||
using (var memoryStream = new MemoryStream())
|
||||
{
|
||||
await responseStream.CopyToAsync(memoryStream);
|
||||
memoryStream.Position = 0;
|
||||
await memoryStream.CopyToAsync(context.Response.Body);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
await next();
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
|
||||
app.Run();
|
||||
32
VRCAuthProxy/VRCAuthProxy.csproj
Normal file
32
VRCAuthProxy/VRCAuthProxy.csproj
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<DockerDefaultTargetOS>Linux</DockerDefaultTargetOS>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Content Include="..\.dockerignore">
|
||||
<Link>.dockerignore</Link>
|
||||
</Content>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.AspNetCore.App" Version="2.2.8"/>
|
||||
<PackageReference Include="Otp.NET" Version="1.4.0"/>
|
||||
<PackageReference Include="VRChat.API" Version="1.18.8"/>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Update="appsettings.json">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="appsettings.template.json">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</None>
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
9
VRCAuthProxy/appsettings.template.json
Normal file
9
VRCAuthProxy/appsettings.template.json
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
{
|
||||
"accounts": [
|
||||
{
|
||||
"username": "username",
|
||||
"password": "password",
|
||||
"totpSecret": "totp secret"
|
||||
}
|
||||
]
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue