Compare commits
91 Commits
ios-workfl
...
auth_ios_t
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4e3448d97e | ||
|
|
6ac99e6e97 | ||
|
|
b57b012840 | ||
|
|
8c2cb6dcad | ||
|
|
692f77c9bc | ||
|
|
dd2a5d6191 | ||
|
|
e66045dc74 | ||
|
|
d9e860466c | ||
|
|
1633c478e3 | ||
|
|
5eb7cadc4d | ||
|
|
af5de77880 | ||
|
|
58279b668b | ||
|
|
7537614fc2 | ||
|
|
0db2385dbd | ||
|
|
fbb5397217 | ||
|
|
f4605f86a4 | ||
|
|
1f1cad181f | ||
|
|
7942c4d642 | ||
|
|
625410852b | ||
|
|
970ca59077 | ||
|
|
3ee021c9de | ||
|
|
0f8a8a7579 | ||
|
|
33703072eb | ||
|
|
83395641ef | ||
|
|
8cbdd4cc98 | ||
|
|
df5a7d6c19 | ||
|
|
95347022e8 | ||
|
|
4f224e7eba | ||
|
|
1c2f8a74c4 | ||
|
|
d86f9d2ffa | ||
|
|
ffdc21d15c | ||
|
|
abe5548202 | ||
|
|
769adb75c5 | ||
|
|
1648f62da6 | ||
|
|
97d66a3afa | ||
|
|
99556dbbcd | ||
|
|
d7fdca78f7 | ||
|
|
f7858a96ed | ||
|
|
63f24966ce | ||
|
|
047c2954f8 | ||
|
|
11786057e2 | ||
|
|
12648ce726 | ||
|
|
782618d26f | ||
|
|
824b071af4 | ||
|
|
f1f84af3a7 | ||
|
|
8d484528e7 | ||
|
|
e95aa55339 | ||
|
|
0d139df652 | ||
|
|
568c5393a8 | ||
|
|
8d8ce6487f | ||
|
|
658ba49186 | ||
|
|
75bc07f08f | ||
|
|
b7ff0ca985 | ||
|
|
a9cba0c7a6 | ||
|
|
a4b938b5d5 | ||
|
|
0174d82829 | ||
|
|
1c14896fd6 | ||
|
|
fcc90c6725 | ||
|
|
7037d67a45 | ||
|
|
04e3ad2b77 | ||
|
|
b64a69ebf0 | ||
|
|
227ea4a371 | ||
|
|
6b5131ec45 | ||
|
|
4e8fa3babd | ||
|
|
e443838621 | ||
|
|
d9710555ea | ||
|
|
6bed9bd8a2 | ||
|
|
c57d467965 | ||
|
|
e8e7f81593 | ||
|
|
8b6d7e049a | ||
|
|
e69276cf5f | ||
|
|
eb8737cb46 | ||
|
|
02b93b12fc | ||
|
|
2d0d914fd3 | ||
|
|
dbb1ad66d3 | ||
|
|
3aa419b430 | ||
|
|
944bdfc7fa | ||
|
|
13420e4440 | ||
|
|
51c00eefd4 | ||
|
|
8d108dc719 | ||
|
|
2e49f581c4 | ||
|
|
c5d9b2408f | ||
|
|
46ba71a15a | ||
|
|
99f4d4ca4d | ||
|
|
47f0722687 | ||
|
|
bbf4462c6c | ||
|
|
cf5aabbde1 | ||
|
|
b9b239c207 | ||
|
|
aa181b1f1f | ||
|
|
daec225ef8 | ||
|
|
c94878e190 |
7
.github/workflows/rust-lint.yml
vendored
@@ -15,6 +15,9 @@ concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
env:
|
||||
RUSTFLAGS: -D warnings
|
||||
|
||||
jobs:
|
||||
lint:
|
||||
runs-on: ubuntu-latest
|
||||
@@ -33,9 +36,9 @@ jobs:
|
||||
~/.cargo/git
|
||||
target
|
||||
key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }}
|
||||
|
||||
|
||||
- run: cargo fmt --check
|
||||
|
||||
- run: cargo clippy
|
||||
- run: cargo clippy --all-targets --all-features
|
||||
|
||||
- run: cargo build
|
||||
|
||||
@@ -48,7 +48,7 @@ See [docs/](docs/README.md) for how to edit these documents.
|
||||
|
||||
## Code contributions
|
||||
|
||||
If you'd like to contribute code, it is best to start small. Consider some well-scoped changes, say like adding more [custom icons to auth](auth/docs/adding-icons.md), or fixing a specific bug.
|
||||
If you'd like to contribute code, it is best to start small. Consider some well-scoped changes, say like adding more [custom icons to auth](mobile/apps/auth/docs/adding-icons.md), or fixing a specific bug.
|
||||
|
||||
Code that changes the behaviour of the product might not get merged, at least not initially. The PR can serve as a discussion bed, but you might find it easier to just start a discussion instead, or post your perspective in the (likely) existing thread about the behaviour change or new feature you wish for.
|
||||
|
||||
|
||||
@@ -98,7 +98,7 @@ more, see [docs/adding-icons](docs/adding-icons.md).
|
||||
The best way to support this project is by checking out [Ente
|
||||
Photos](../mobile/README.md) or spreading the word.
|
||||
|
||||
For more ways to contribute, see [../CONTRIBUTING.md](../../../CONTRIBUTING.md).
|
||||
For more ways to contribute, see [../../../CONTRIBUTING.md](../../../CONTRIBUTING.md).
|
||||
|
||||
## Certificate Fingerprints
|
||||
|
||||
|
||||
@@ -60,6 +60,19 @@
|
||||
"slug": "amtrak",
|
||||
"hex": "003A5D"
|
||||
},
|
||||
{
|
||||
"title": "Animal Crossing",
|
||||
"slug:": "animal_crossing",
|
||||
"altNames": [
|
||||
"AnimalCrossing",
|
||||
"Bell Tree Forums",
|
||||
"BellTree Forums"
|
||||
]
|
||||
},
|
||||
{
|
||||
"title": "AnimeZ.to",
|
||||
"slug": "animez"
|
||||
},
|
||||
{
|
||||
"title": "Ankama",
|
||||
"slug": "ankama"
|
||||
@@ -81,6 +94,13 @@
|
||||
"Docaposte AR24"
|
||||
]
|
||||
},
|
||||
{
|
||||
"title": "Art Fight",
|
||||
"slug": "art_fight",
|
||||
"altNames": [
|
||||
"ArtFight"
|
||||
]
|
||||
},
|
||||
{
|
||||
"title": "Aruba",
|
||||
"slug": "aruba",
|
||||
@@ -106,6 +126,10 @@
|
||||
{
|
||||
"title": "availity"
|
||||
},
|
||||
{
|
||||
"title": "AvistaZ.to",
|
||||
"slug": "avistaz"
|
||||
},
|
||||
{
|
||||
"title": "AzurHosts",
|
||||
"slug": "azurhosts",
|
||||
@@ -306,6 +330,14 @@
|
||||
{
|
||||
"title": "Capacities"
|
||||
},
|
||||
{
|
||||
"title": "Capcom",
|
||||
"slug": "capcom",
|
||||
"hex": "0D4DA2",
|
||||
"altNames": [
|
||||
"Capcom Co., Ltd."
|
||||
]
|
||||
},
|
||||
{
|
||||
"title": "Carta",
|
||||
"altNames": [
|
||||
@@ -341,6 +373,13 @@
|
||||
"slug": "cih",
|
||||
"hex": "D14633"
|
||||
},
|
||||
{
|
||||
"title": "Chucklefish"
|
||||
},
|
||||
{
|
||||
"title": "CinemaZ.to",
|
||||
"slug": "cinemaz"
|
||||
},
|
||||
{
|
||||
"title": "Clipper",
|
||||
"slug": "clippercard",
|
||||
@@ -593,6 +632,10 @@
|
||||
],
|
||||
"hex": "17AB17"
|
||||
},
|
||||
{
|
||||
"title": "ExoticaZ.to",
|
||||
"slug": "exoticaz"
|
||||
},
|
||||
{
|
||||
"title": "Experian",
|
||||
"slug": "experian",
|
||||
@@ -1312,6 +1355,10 @@
|
||||
"title": "Privacy.com",
|
||||
"slug": "privacy"
|
||||
},
|
||||
{
|
||||
"title": "PrivateHD.to",
|
||||
"slug": "privatehd"
|
||||
},
|
||||
{
|
||||
"title": "Proton"
|
||||
},
|
||||
@@ -1505,6 +1552,9 @@
|
||||
{
|
||||
"title": "Skinport"
|
||||
},
|
||||
{
|
||||
"title": "Smogon"
|
||||
},
|
||||
{
|
||||
"title": "SMSPool",
|
||||
"slug": "sms_pool_net",
|
||||
@@ -1664,6 +1714,12 @@
|
||||
{
|
||||
"title": "TorGuard"
|
||||
},
|
||||
{
|
||||
"title": "Toyhouse",
|
||||
"altNames": [
|
||||
"Toyhou.se"
|
||||
]
|
||||
},
|
||||
{
|
||||
"title": "Trading 212"
|
||||
},
|
||||
@@ -1685,6 +1741,15 @@
|
||||
"T Rowe Price Group, Inc"
|
||||
]
|
||||
},
|
||||
{
|
||||
"title": "TU Dresden",
|
||||
"slug": "tu_dresden",
|
||||
"altNames": [
|
||||
"Technische Universität Dresden",
|
||||
"Dresden University of Technology"
|
||||
],
|
||||
"hex": "00305d"
|
||||
},
|
||||
{
|
||||
"title": "Tweakers"
|
||||
},
|
||||
@@ -1699,6 +1764,12 @@
|
||||
"Twitch tv"
|
||||
]
|
||||
},
|
||||
{
|
||||
"title": "Twitter",
|
||||
"altNames": [
|
||||
"X"
|
||||
]
|
||||
},
|
||||
{
|
||||
"title": "Ubiquiti",
|
||||
"slug": "ubiquiti",
|
||||
@@ -1764,7 +1835,7 @@
|
||||
"title": "Warner Bros.",
|
||||
"slug": "warner_bros",
|
||||
"altNames": [
|
||||
"Warner Brothers"
|
||||
"Warner Brothers"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -1777,7 +1848,7 @@
|
||||
"title": "WEB.DE",
|
||||
"slug": "web_de"
|
||||
},
|
||||
{
|
||||
{
|
||||
"title": "WeMod",
|
||||
"slug": "wemod",
|
||||
"altNames": [
|
||||
@@ -1906,6 +1977,22 @@
|
||||
{
|
||||
"title": "Co-Wheels",
|
||||
"slug": "cowheels"
|
||||
},
|
||||
{
|
||||
"title": "Zivver",
|
||||
"slug": "zivver"
|
||||
},
|
||||
{
|
||||
"title": "Meesman Indexbeleggen",
|
||||
"slug": "meesman"
|
||||
},
|
||||
{
|
||||
"title": "Scouting Nederland",
|
||||
"slug": "scoutingnederland"
|
||||
},
|
||||
{
|
||||
"title": "ISC2",
|
||||
"slug": "isc2"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 326.24 311.07"><path fill="#1aae5e" d="M266.38 1.26c-.93 9.12-7.71 47.21-27.28 83.44a1 1 0 0 1-1.7-.06c-4-8.24-24.86-44.58-79.29-44.91C96.71 39.35 66.67 87 66.67 87s-23.45 32.93-33.49 94.89-22.61 79-22.61 79L.39 277a2.54 2.54 0 0 0 1.11 3.67c13 5.81 72.68 30.38 151.68 30.38 53.8 0 70-7.58 74.25-10.55a.61.61 0 0 0-.35-1.12c-10.55 1.16-51.2-5.26-51.2-44 0-34.79 25.3-51.62 51.81-52.83 12.56-.58 39.35 4.74 47.72 29.3 7 20.66-2.31 39.15-5.45 44.5a.62.62 0 0 0 .77.89c9.52-3.84 53.71-25.77 55.47-93.95 1.55-59.91-47.64-85.62-70.8-86.71a1.18 1.18 0 0 1-1-1.62c6.23-15.94 45-57.27 51.84-64.46a1.57 1.57 0 0 0 0-2.17A159.05 159.05 0 0 0 268.46.17a1.42 1.42 0 0 0-2.08 1.09Z"/></svg>
|
||||
|
After Width: | Height: | Size: 730 B |
1
mobile/apps/auth/assets/custom-icons/icons/animez.svg
Normal file
|
After Width: | Height: | Size: 25 KiB |
307
mobile/apps/auth/assets/custom-icons/icons/art_fight.svg
Normal file
|
After Width: | Height: | Size: 35 KiB |
1
mobile/apps/auth/assets/custom-icons/icons/avistaz.svg
Normal file
|
After Width: | Height: | Size: 24 KiB |
13
mobile/apps/auth/assets/custom-icons/icons/best_buy.svg
Normal file
@@ -0,0 +1,13 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="1000" height="584.48486" >
|
||||
<g transform="translate(-137.5,-22.390163)">
|
||||
<path style="fill:#1c252c" d="m 137.5,29.631487 0,252.320873 132.1114,0 c 51.99819,0 100.05059,-18.25003 100.05059,-71.85789 0,-36.24066 -26.10767,-52.7841 -54.82326,-61.4437 17.4955,-7.01771 37.3423,-21.76056 37.3423,-52.666034 0,-39.55613 -39.24823,-66.353249 -92.09117,-66.353249 l -122.58986,0 z m 80.93311,58.76577 29.75482,0 c 12.11271,0 21.34908,9.487931 21.34908,19.117473 0,8.94677 -9.59745,18.52237 -21.34908,18.52237 l -29.75482,0 0,-37.639843 z m 0,90.008333 39.27636,0 c 13.71131,0 25.21721,10.41366 25.21721,22.31611 0,12.63921 -10.76115,23.13438 -27.59759,23.13438 l -36.89598,0 0,-45.45049 z" id="path2998" />
|
||||
<path id="path3005" d="m 202.9606,309.47481 0,252.32088 132.11141,0 c 51.99819,0 100.05058,-18.25004 100.05058,-71.8579 0,-36.24065 -26.10766,-52.78409 -54.82326,-61.4437 17.4955,-7.01771 37.3423,-21.76056 37.3423,-52.66603 0,-39.55613 -39.24822,-66.35325 -92.09117,-66.35325 l -122.58986,0 z m 80.93312,58.76577 29.75482,0 c 12.11271,0 21.34908,9.48793 21.34908,19.11747 0,8.94678 -9.59745,18.52238 -21.34908,18.52238 l -29.75482,0 0,-37.63985 z m 0,90.00833 39.27636,0 c 13.71131,0 25.21721,10.41366 25.21721,22.31612 0,12.6392 -10.76115,23.13437 -27.5976,23.13437 l -36.89597,0 0,-45.45049 z" style="fill:#1c252c" />
|
||||
<path style="fill:#1c252c" d="m 376.01464,281.92185 0,-252.32088 203.52297,0 0,60.937872 -122.8279,0 0,33.087358 99.73816,0 0,57.12926 -99.73816,0 0,40.46655 122.8279,0 0,60.69984 z" id="path3007"/>
|
||||
<path style="fill:#1c252c" d="m 689.78812,289.1442 c 57.28767,0 103.11071,-32.67744 103.11071,-85.63169 0,-85.46143 -111.17753,-72.31037 -111.17753,-98.91916 0,-10.278288 10.80934,-15.732656 21.89462,-15.732656 19.10438,0 32.9075,12.586126 32.9075,12.586126 L 784.35446,55.903565 C 765.2233,37.697019 735.00886,22.390163 694.30743,22.390163 c -61.12759,0 -101.11859,36.280243 -101.11859,80.044867 0,86.54828 109.57491,73.98694 109.57491,101.14304 0,9.51935 -9.15835,19.09619 -25.76901,19.09619 -18.85922,0 -33.79879,-11.38479 -45.42578,-21.04418 l -48.11128,45.86837 c 19.37303,18.87022 50.47517,41.64575 106.33044,41.64575 z" id="path3009" />
|
||||
<path style="fill:#1c252c" d="m 855.18627,281.92185 0,-191.621047 -66.6508,0 0,-60.699833 214.23473,0 0,60.699833 -66.65082,0 0,191.621047 z" id="path3011" />
|
||||
<path style="fill:#1c252c" d="m 437.90467,309.29628 80.69507,0 0,151.15449 c 0,15.34925 15.27608,29.49386 31.20285,29.49386 15.02419,0 30.2111,-12.77313 30.2111,-30.30098 l 0,-150.34737 80.45703,0 0,149.31902 c 0,59.25051 -49.19276,106.51359 -112.80036,106.51359 -63.96969,0 -109.76569,-51.43651 -109.76569,-109.74208 z" id="path3013" />
|
||||
<path style="fill:#1c252c;fill-opacity:1;stroke:none" d="m 759.97084,561.61716 0,-90.16156 -94.84461,-162.15932 81.6448,0 53.66637,86.88408 53.85177,-86.88408 81.83021,0 -95.21543,163.08638 0,89.2345 z" id="path3015" />
|
||||
<path style="fill:#ffed31" d="m 936.11938,447.38916 -47.60772,47.60772 0,64.27041 47.60772,47.60771 201.38062,0 0,-159.48584 z" id="path3017" />
|
||||
<path style="fill:#1c252c" id="path3024" d="m 469,573.36218 c 0,2.20914 -1.79086,4 -4,4 -2.20914,0 -4,-1.79086 -4,-4 0,-2.20914 1.79086,-4 4,-4 2.20914,0 4,1.79086 4,4 z" transform="matrix(2.6779338,0,0,2.6779338,-327.65077,-1008.3244)" />
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 3.3 KiB |
71
mobile/apps/auth/assets/custom-icons/icons/capcom.svg
Normal file
@@ -0,0 +1,71 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
version="1.1"
|
||||
width="926.65619"
|
||||
height="958.5625"
|
||||
id="svg2"
|
||||
inkscape:version="0.48.1 "
|
||||
sodipodi:docname="Capcom icon.svg">
|
||||
<metadata
|
||||
id="metadata3015">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title></dc:title>
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<sodipodi:namedview
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1"
|
||||
objecttolerance="10"
|
||||
gridtolerance="10"
|
||||
guidetolerance="10"
|
||||
inkscape:pageopacity="0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:window-width="1835"
|
||||
inkscape:window-height="1058"
|
||||
id="namedview3013"
|
||||
showgrid="false"
|
||||
inkscape:zoom="0.32"
|
||||
inkscape:cx="1623.239"
|
||||
inkscape:cy="170.71783"
|
||||
inkscape:window-x="-8"
|
||||
inkscape:window-y="-8"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="layer1"
|
||||
fit-margin-top="0"
|
||||
fit-margin-left="0"
|
||||
fit-margin-right="0"
|
||||
fit-margin-bottom="0" />
|
||||
<defs
|
||||
id="defs4" />
|
||||
<g
|
||||
transform="translate(2426,-55.799683)"
|
||||
id="layer1">
|
||||
<path
|
||||
d="m -1552.0877,1007.6508 4.1189,-358.25737 -199.9062,0 c -15.7424,43.40396 -59.9402,72.71875 -113.25,72.71875 -28.0932,0 -58.13,-6.62565 -82.4688,-21.25 -31.7176,-19.0581 -55.4185,-41.69206 -74.9375,-81.90625 -15.3567,-37.21359 -27.109,-82.77963 -28.4687,-123.09375 4.6791,-79.72921 28.9019,-108.14789 73.25,-141.03125 9.8313,-7.28971 37.2996,-13.1189 45.2187,-13.15625 17.4221,-0.0822 31.9427,10.48615 39.8125,26.0625 11.4471,22.65651 -5.3079,50.1006 -21.875,69.625 l 161.625,111.34375 249.625,-261.1875 c -87.0859,-138.79748 -246.2201,-231.718747 -428.0937,-231.718747 -275.3534,0 -498.5625,212.989027 -498.5625,475.718747 0,262.72972 216.0662,461.41517 498.5625,482.84377 l 376.5933,-1.6875 c 0.2227,-314.9224 -12.7573,-290.91079 -1.2435,-5.0239 z"
|
||||
id="path3840"
|
||||
style="fill:#0c4da2;fill-opacity:1;stroke:none"
|
||||
inkscape:connector-curvature="0"
|
||||
sodipodi:nodetypes="cccssccssscccssccc" />
|
||||
<path
|
||||
d="m -1927.7708,145.18332 c -226.9823,1.42857 -412.3662,166.7661 -412.3662,383.05133 0,216.28523 204.6469,402.07109 452.3662,396.47808 l 240.7127,0.6783 0,-189.50724 -45.4634,0 c -14.2837,35.73115 -80.9968,70.68064 -129.3669,70.68064 -25.4902,0 -89.6606,-3.46506 -113.3988,-11.7792 -41.9106,-14.67892 -101.712,-35.75045 -153.7082,-123.14142 -13.9338,-30.6351 -53.1686,-105.28898 -44.4023,-214.19078 4.2456,-65.63495 45.9366,-147.56776 120.7486,-184.67165 32.6212,-16.17886 65.4607,-18.83348 123.1208,-13.60101 41.3957,3.75653 82.5276,32.86328 96.8886,67.79717 11.107,27.01833 7.9153,59.94169 -5.16,78.69532 -0.9328,1.33796 -0.9528,2.52293 -0.7595,3.80017 0.1471,0.97218 1.408,2.592 1.408,2.592 l 22.7845,17.79932 c 0,0 6.4866,4.50696 14.0636,4.30516 4.4004,-0.11719 12.2667,-4.89104 12.2667,-4.89104 l 130.1212,-136.55317 c -80.4452,-108.54703 -223.4056,-147.54198 -309.8556,-147.54198 z"
|
||||
id="path3950"
|
||||
style="fill:#ffcb08;fill-opacity:1;stroke:none"
|
||||
inkscape:connector-curvature="0" />
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 3.6 KiB |
75
mobile/apps/auth/assets/custom-icons/icons/chucklefish.svg
Normal file
|
After Width: | Height: | Size: 16 KiB |
1
mobile/apps/auth/assets/custom-icons/icons/cinemaz.svg
Normal file
@@ -0,0 +1 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg width="100%" height="100%" viewBox="0 0 438 438" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;"><rect id="Artboard1" x="0" y="0" width="437.5" height="437.5" style="fill:none;"/><g id="layer101"><path d="M80.861,420.364c-4.353,-1.171 -8.1,-4.433 -10.133,-8.799l-1.102,-2.397l0.27,-99.121c0.291,-94.079 0.36,-100.573 1.396,-103.303c0.216,-0.469 -1.105,-1.372 -5.198,-3.524c-25.794,-13.562 -44.713,-37.051 -52.206,-64.809c-2.864,-10.522 -3.359,-14.797 -3.306,-28.127c0.096,-13.244 0.362,-15.721 3.139,-26.22c4.859,-18.438 14.538,-34.891 28.434,-48.251c15.784,-15.189 35.348,-24.767 57.621,-28.224c6.712,-1.041 23.289,-0.974 30.121,0.164c17.719,2.848 33.842,9.706 47.898,20.401c5.197,3.951 12.219,10.687 16.429,15.746c1.786,2.143 3.359,3.944 3.53,4.03c0.17,0.043 1.457,-1.276 2.831,-2.98c1.374,-1.703 4.464,-5.023 6.823,-7.364c16,-15.872 36.506,-26.044 59.249,-29.328c6.883,-0.998 22.306,-0.936 29.095,0.117c24.21,3.771 45.357,14.964 61.863,32.804c14.591,15.824 23.698,35.557 26.684,57.999c0.705,5.216 0.645,20.34 -0.103,25.635c-3.387,24.211 -13.551,44.465 -30.708,61.272c-8.322,8.17 -17.577,14.713 -27.465,19.501c-1.841,0.89 -2.398,1.358 -2.187,1.871c1.355,3.125 1.433,5.048 1.331,30.426c-0.056,14.056 -0.017,25.549 0.111,25.55c0.128,0 18.029,-10.353 39.741,-22.998c21.712,-12.602 40.555,-23.464 41.839,-24.057c1.969,-0.932 2.952,-1.099 6.199,-1.086c3.29,0.013 4.229,0.188 6.276,1.178c3.241,1.509 6.433,4.598 7.874,7.594l1.186,2.398l-0.204,83.099c-0.171,63.745 -0.335,83.526 -0.726,84.977c-1.733,6.188 -8.375,11.117 -14.998,11.091c-4.742,-0.019 -4.742,-0.019 -46.94,-25.012l-40.62,-24.046l-0.32,26.574c-0.309,23.754 -0.407,26.83 -1.054,28.451c-1.899,4.649 -5.458,7.967 -10.164,9.444c-2.695,0.843 -3.763,0.839 -116.64,0.344c-88.824,-0.356 -114.33,-0.586 -115.866,-1.02Zm140.434,-223.059c-0.256,-0.215 -1.661,-1.331 -3.195,-2.448c-6.858,-5.197 -12.604,-10.732 -18.726,-18.148c-1.021,-1.243 -2,-2.187 -2.128,-2.145c-0.171,0.085 -1.759,1.873 -3.562,4.002c-4.25,5.067 -10.769,11.151 -16.426,15.444l-4.585,3.442l24.524,0.098c13.458,0.054 24.31,-0.073 24.098,-0.245Zm-94.961,-25.46c25.4,-5.495 44.191,-24.646 49.163,-50.005c1.571,-8.239 1.017,-19.307 -1.512,-27.991c-5.519,-19.291 -20.708,-35.245 -39.61,-41.602c-7.594,-2.551 -10.669,-3.033 -19.726,-3.069c-8.673,-0.035 -11.751,0.38 -18.767,2.573c-19.293,5.99 -35.166,22.119 -40.926,41.621c-2.598,8.663 -3.241,19.726 -1.736,27.978c2.552,13.511 8.999,25.415 18.788,34.683c9.024,8.495 19.726,13.836 32.534,16.194c4.355,0.787 17.43,0.583 21.792,-0.382Zm167.009,0.498c5.987,-1.343 8.683,-2.315 14.804,-5.324c17.081,-8.391 28.977,-23.639 33.369,-42.847c0.87,-3.756 1.006,-5.636 1.033,-12.344c0.026,-6.707 -0.094,-8.588 -0.934,-12.351c-5.454,-24.845 -23.837,-43.29 -48.596,-48.773c-5.165,-1.174 -18.751,-1.228 -23.925,-0.096c-25.999,5.536 -45.305,25.454 -49.553,51.114c-0.831,4.91 -0.869,14.31 -0.077,19.226c4.125,26.164 24.251,46.796 50.422,51.686c6.233,1.178 17.642,1.01 23.457,-0.291Z" style="fill:#fff;fill-rule:nonzero;"/></g></svg>
|
||||
|
After Width: | Height: | Size: 3.2 KiB |
1
mobile/apps/auth/assets/custom-icons/icons/exoticaz.svg
Normal file
|
After Width: | Height: | Size: 17 KiB |
4
mobile/apps/auth/assets/custom-icons/icons/isc2.svg
Normal file
@@ -0,0 +1,4 @@
|
||||
<svg width="76" height="28" viewBox="0 0 76 28" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M3.6137 0.263554V24.2528C3.6137 24.3057 3.59223 24.3565 3.55413 24.3932L0.140163 27.6896C0.0876965 27.7404 0 27.703 0 27.6301V0.263554C0 0.217915 0.0370442 0.180891 0.0827069 0.180891H3.53099C3.57666 0.180891 3.6137 0.217915 3.6137 0.263554ZM12.9151 7.14572C12.9151 4.77676 14.8577 3.33976 17.4613 3.33976C19.7149 3.33976 21.3857 4.27186 23.173 5.94205C23.445 6.21361 23.5321 6.36292 23.8042 6.05236L25.4188 4.31055C25.6908 4 25.5433 3.92232 25.2713 3.65076C23.3283 1.39816 20.4529 0 17.4223 0C12.9538 0 9.41785 2.91284 9.41785 7.10703C9.41785 15.8838 22.9397 14.0583 22.9397 20.1554C22.9397 22.6408 20.764 24.5827 17.5388 24.5827C14.4361 24.5827 12.8017 23.1551 11.0921 21.524C10.8201 21.2522 10.7614 21.1884 10.5283 21.4216L8.68301 23.1634C8.411 23.3966 8.43746 23.4164 8.63175 23.7269C10.3993 26.1087 13.6923 27.9612 17.539 27.9612C22.3572 27.9612 26.5147 24.7768 26.5147 20.3107C26.5147 11.0679 12.9153 13.1263 12.9153 7.14557L12.9151 7.14572ZM44.5835 3.53395C47.3423 3.53395 49.596 4.58257 51.4999 6.17492C51.5444 6.213 51.6963 6.33632 51.743 6.37576C51.9289 6.53262 51.9644 6.53973 52.1883 6.30851C52.2428 6.25229 52.4023 6.08032 52.4023 6.08032L54.0336 4.29771C54.2087 4.09279 54.1792 4.07768 54.0528 3.94545C54.0011 3.89134 53.9102 3.80702 53.8311 3.72813C51.3835 1.55367 48.2361 0 44.5835 0C36.8514 0 30.8288 6.21361 30.8288 14.0196C30.8288 21.8255 36.8514 28 44.5835 28C48.2361 28 51.3906 26.3504 53.8384 24.1369C54.1104 23.9037 54.2128 23.8197 53.9409 23.5477L52.278 21.729C52.006 21.4571 51.9349 21.5213 51.6629 21.7545C49.759 23.3466 47.3422 24.4659 44.5834 24.4659C39.0271 24.4659 34.5199 19.9612 34.5199 14.0194C34.5199 8.03869 39.0272 3.53395 44.5835 3.53395ZM66.7904 0.0562167C63.2934 0.0562167 60.5212 1.59885 58.8115 3.73463C58.7651 3.78873 58.6609 3.93985 58.6299 3.98821C58.505 4.18286 58.541 4.19736 58.7175 4.39382C59.1585 4.88481 60.5292 6.43666 60.5292 6.43666C60.5868 6.49756 60.6405 6.49998 60.6904 6.45585C60.7448 6.4078 60.863 6.29536 60.9279 6.23174C62.0159 5.10544 63.8762 3.24107 66.7514 3.24107C69.9763 3.24107 71.9191 5.26049 71.9191 8.21187C71.9191 12.3999 69.5179 14.3919 64.1713 18.8858C62.6162 20.1928 59.7455 22.7114 59.029 23.3411C58.9234 23.4338 58.8637 23.5669 58.8637 23.7071V27.4699C58.8637 27.6378 59.0612 27.7277 59.1874 27.6169C60.6628 26.3236 66.7627 20.9799 68.7448 19.2968C73.8769 14.9387 75.2608 11.9788 75.2608 8.17288C75.2608 3.55163 71.9193 0.0563678 66.7902 0.0563678L66.7904 0.0562167Z" fill="black"/>
|
||||
<path d="M75.4003 24.6003H66.7791C66.6688 24.6003 66.5794 24.6896 66.5794 24.7999V27.3522C66.5794 27.4624 66.6688 27.5518 66.7791 27.5518H75.4003C75.5106 27.5518 75.6 27.4624 75.6 27.3522V24.7999C75.6 24.6896 75.5106 24.6003 75.4003 24.6003Z" fill="#468145"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.8 KiB |
1
mobile/apps/auth/assets/custom-icons/icons/meesman.svg
Normal file
|
After Width: | Height: | Size: 13 KiB |
1
mobile/apps/auth/assets/custom-icons/icons/privatehd.svg
Normal file
@@ -0,0 +1 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg width="100%" height="100%" viewBox="0 0 557 557" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;"><rect id="Artboard1" x="0" y="0" width="556.25" height="556.25" style="fill:none;"/><g><path d="M45.455,412.973c-8.755,-1.966 -14.046,-4.401 -21.21,-9.692c-9.88,-7.304 -16.341,-17.418 -19.9,-31.231c-0.655,-2.481 -0.749,-13.157 -0.889,-90.601c-0.141,-62.086 -0.047,-89.102 0.328,-92.333c2.013,-17.746 12.969,-33.384 28.795,-41.251c8.896,-4.401 14.609,-5.478 29.545,-5.478l10.956,0l0,109.096l121.27,0l0,-109.189l12.97,0.187c12.267,0.187 13.297,0.281 17.699,1.451c18.682,4.964 32.963,19.432 37.832,38.348l1.498,5.759l0,180.266l-1.451,5.618c-3.886,14.984 -12.642,26.408 -25.893,33.712c-4.495,2.482 -8.849,4.074 -14.702,5.338c-3.933,0.843 -6.087,0.983 -16.247,0.983l-11.706,0l0,-107.223l-121.27,0l0,107.223l-11.799,-0.046c-9.879,-0.047 -12.501,-0.188 -15.826,-0.937Z" style="fill:url(#_Linear1);fill-rule:nonzero;"/><path d="M272.625,413.956l0,-271.569l118.226,0.094l118.226,0.14l4.449,1.218c8.943,2.481 16.668,6.883 23.177,13.11c8.053,7.726 12.829,16.294 15.17,27.204c0.983,4.541 0.983,4.963 0.983,94.112l0,89.571l-1.123,4.917c-2.342,10.254 -6.275,17.558 -13.485,25.097c-7.679,8.006 -16.294,12.782 -27.157,15.123l-4.589,0.983l-233.877,0Zm215.148,-61.805l0,-147.958l-150.767,0l0,147.958l150.767,0Z" style="fill:url(#_Linear2);fill-rule:nonzero;"/></g><defs><linearGradient id="_Linear1" x1="0" y1="0" x2="1" y2="0" gradientUnits="userSpaceOnUse" gradientTransform="matrix(1.66288e-14,271.569,-271.569,1.66288e-14,269.5,142.387)"><stop offset="0" style="stop-color:#decc84;stop-opacity:1"/><stop offset="0.49" style="stop-color:#e1b821;stop-opacity:1"/><stop offset="1" style="stop-color:#e2b201;stop-opacity:1"/></linearGradient><linearGradient id="_Linear2" x1="0" y1="0" x2="1" y2="0" gradientUnits="userSpaceOnUse" gradientTransform="matrix(1.66288e-14,271.569,-271.569,1.66288e-14,272.625,142.387)"><stop offset="0" style="stop-color:#decc84;stop-opacity:1"/><stop offset="0.49" style="stop-color:#e1b821;stop-opacity:1"/><stop offset="1" style="stop-color:#e2b201;stop-opacity:1"/></linearGradient></defs></svg>
|
||||
|
After Width: | Height: | Size: 2.4 KiB |
129
mobile/apps/auth/assets/custom-icons/icons/scoutingnederland.svg
Normal file
@@ -0,0 +1,129 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
|
||||
<svg
|
||||
version="1.1"
|
||||
id="svg1"
|
||||
width="351.54666"
|
||||
height="329.84"
|
||||
viewBox="0 0 351.54666 329.84"
|
||||
sodipodi:docname="SN_logo_CMYK.eps"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg">
|
||||
<defs
|
||||
id="defs1" />
|
||||
<sodipodi:namedview
|
||||
id="namedview1"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#000000"
|
||||
borderopacity="0.25"
|
||||
inkscape:showpageshadow="2"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pagecheckerboard="0"
|
||||
inkscape:deskcolor="#d1d1d1">
|
||||
<inkscape:page
|
||||
x="0"
|
||||
y="0"
|
||||
inkscape:label="1"
|
||||
id="page1"
|
||||
width="351.54666"
|
||||
height="329.84"
|
||||
margin="0"
|
||||
bleed="0" />
|
||||
</sodipodi:namedview>
|
||||
<g
|
||||
id="g1"
|
||||
inkscape:groupmode="layer"
|
||||
inkscape:label="1">
|
||||
<g
|
||||
id="group-R5">
|
||||
<path
|
||||
id="path2"
|
||||
d="m 1090.05,2469.06 c -24.19,-7.15 -103.737,-35.43 -111.945,-99.52 -0.417,-3.28 -0.664,-6.75 -0.664,-10.38 0,-1.78 0.235,-3.7 0.364,-5.56 -10.262,0.29 -19.739,0.6 -26.133,0.81 l -65.332,1.34 -23.559,-4.16 c -28.023,-4.55 -103.66,-16.9 -105.203,-94.63 -0.223,-5.16 -1.117,-25.66 -1.117,-25.66 0,-46.17 9.445,-96.59 26.602,-141.96 5.046,-13.56 12.753,-23.97 18.382,-31.57 0.071,-0.11 3.368,-4.86 4.36,-6.29 9.855,-38.77 26.3,-70.63 45.929,-98.74 -53.027,-6.36 -106.531,-15 -159.277,-30.85 l 10.656,1.99 c -28.793,-2.16 -56.379,-17.47 -84.343,-46.81 l -2.582,-2.71 -2.157,-3.07 c -9.359,-13.34 -16.133,-26.39 -20.597,-39.33 -108.993,-32.27 -198.711,-59.86 -271.504,-83.66 -64.18,-21.16 -191.942,-75.53 -197.344,-77.84 l -3.488,-1.48 c 0,0 -0.75,-0.5 -0.969,-0.64 -0.926,-0.38 -1.899,-0.79 -1.899,-0.79 C 20.5117,1627.77 0,1560.47 0,1511.02 c 0,-77.67 60.293,-99.23 89.2734,-109.59 6.5352,-2.34 13.3046,-4.76 16.7896,-6.72 39.054,-21.38 76.554,-14 103.933,-8.62 l 18.535,3.23 c 13.426,1.65 28.203,6.02 42.918,10.64 13.539,-28.39 38.336,-54.64 77.246,-62.14 33.27,-6.51 55.453,-0.36 73.301,6.07 0.996,-10.01 3.031,-19.97 7.145,-29.66 6.875,-16.22 18.355,-31.11 32.617,-43.77 -23.574,-18.45 -57.028,-51.65 -57.028,-91.97 0,-3.61 0.274,-7.28 0.844,-11 -0.008,0.04 0.399,-2.76 0.399,-2.76 3.066,-21.52 11.211,-78.7 89.461,-110.76 30.101,-12.33 64.925,-15.82 103.968,-10.82 3.43,-11.8 8.594,-22.92 15.438,-33.02 -21.535,-10.618 -26.383,-14.302 -29.688,-16.97 l -1.097,-0.91 c -14.344,-12.238 -35.231,-31.379 -50.989,-55.961 -6.539,-9.769 -26.257,-42.648 -26.257,-80.109 0,-6.758 0.64,-13.668 2.121,-20.629 4.996,-23.449 18.433,-42.66 38.859,-55.539 28.457,-17.571 59.524,-19.403 83.496,-17.114 3.903,-25.226 22.399,-54.019 79.567,-70.777 l 3.652,-1.082 c 18.387,-5.598 43.184,-4.641 95.445,-0.18 l 21.008,1.61 c 6.332,0.222 16.938,1.801 29.336,4.019 0.02,-25.679 7.574,-48.519 21.941,-65.519 l 1.036,-1.188 c 32.378,-35.941 81.164,-45.679 100.636,-48.211 21.704,-3.019 49.384,6.219 103.054,28.078 l 13.14,5.274 c 2.61,1.027 9.13,2.937 16.79,5.066 2.08,-2.797 3.56,-5.718 6.13,-8.359 11.57,-11.93 32.27,-25.891 65.17,-24.75 30.02,0.793 80.65,12.051 86.31,13.332 l 3.03,0.68 2.91,1.05 c 62.56,22.559 268.81,97.289 296.36,112.11 l 1.04,0.57 c 22.84,11.539 205.84,69.93 283.99,94.859 l 56.19,18 c 82.34,25.911 157.95,84.43 179.73,107.129 l 0.8,0.852 c 18.25,19.91 30.13,47.211 35.35,81.149 0.57,3.61 0.85,7.19 0.85,10.73 0,17.83 -6.96,34.73 -16.23,50.72 42.8,19.11 84.58,37.78 85.78,38.31 15.35,5.69 54.79,23.33 71.3,59.78 3.85,8.53 5.57,16.91 6.1,25.08 45.66,11.71 96.03,24.63 96.03,24.63 0.05,0.01 1.58,0.37 1.58,0.37 28.95,6.86 105.87,25.09 177.5,60.59 66.98,32.41 79.83,70.82 79.83,103.21 0,4.38 -0.23,8.66 -0.6,12.8 l -0.24,2.95 -0.24,-3.74 c 0.31,2.41 0.46,4.85 0.46,7.29 0,33.59 -27.8,69.46 -53.31,88.19 -0.41,1.45 -0.69,2.46 -0.69,2.46 -16.17,19.47 -40.32,20.74 -51.92,21.36 l 1.3,-0.09 -14.24,2.33 c -18.43,3.88 -38.47,6.86 -81.29,5.91 -1.01,12.89 -3.02,25.28 -7.03,36.8 -25.36,76.06 -92.5,130.37 -170.88,137.82 -49.09,6.17 -87.12,3.96 -128.02,0.48 35.15,41.09 71.23,87.59 80.71,115.32 8.24,23.4 23.39,130.23 24.13,140.16 l 0.1,1.48 0.21,13.73 c 0,29.46 -2.97,49.47 -5.44,65.61 -0.18,1.62 -3.11,28.05 -3.39,30.67 -2.34,70.84 -14.17,133.75 -16.55,145.76 l -23.69,117.63 -70.81,-96.45 c -9.23,-12.78 -20.6,-28.08 -25.51,-34.26 -14.11,-10.83 -79.29,-55.93 -128.62,-86.83 -11.04,44.47 -37.4,83.58 -79.17,113.49 l -1.87,1.35 -1.99,1.16 c -52.54,30.73 -110.3,59.29 -176.56,60.18 -66.4,2.28 -128.22,-11.25 -183.74,-40.23 -33.75,-18.23 -58.71,-44.83 -80.73,-68.3 -13.84,-14.71 -26.65,-28.35 -40.27,-39.12 l 2.63,1.94 c -12.03,-8.28 -23.19,-17.08 -33.97,-26 -8.34,43.79 -18.04,92.87 -39.65,140.64 -26.22,56.12 -61.23,104.55 -110.28,152.92 -23.83,24.47 -67.53,43.26 -91.52,52.28 l -2.9,1.03 -14.98,5.04 -16.03,-4.73"
|
||||
style="fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none"
|
||||
transform="matrix(0.13333333,0,0,-0.13333333,0,329.84)" />
|
||||
<path
|
||||
id="path3"
|
||||
d="m 473.715,1191.47 c -2.852,17.82 20.695,43.53 44.914,60.65 24.98,17.09 64.922,34.93 89.207,43.49 24.219,7.87 41.348,13.57 54.894,17.87 78.485,27.09 255.391,89.13 262.52,91.97 7.832,3.61 409.42,134.83 443.68,142.01 33.48,7.13 177.58,71.27 192.62,84.14 9.92,8.56 301.68,42.09 339.51,23.53 37.06,-18.53 -12.12,-145.54 55.66,-126.98 66.96,18.53 275.99,51.36 313.07,-8.52 37.09,-59.97 7.18,-89.93 -19.21,-101.31 -25.71,-10.75 -511.44,-131.98 -511.44,-131.98 0,0 -691.94,-186.88 -718.33,-191.14 -20.68,-3.61 -163.326,-47.83 -217.564,-65.61 -14.961,-5 2.149,0.69 -26.355,-7.88 -36.418,-10.65 -29.981,-5 -46.399,-2.12 -36.367,7.12 -64.199,32.12 -67.07,66.3 -2.84,33.53 53.527,52.79 49.269,52.79 -4.289,0 -106.289,-52.09 -179.757,-22.13 -54.942,22.13 -56.375,56.4 -59.219,74.92"
|
||||
style="fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none"
|
||||
transform="matrix(0.13333333,0,0,-0.13333333,0,329.84)" />
|
||||
<path
|
||||
id="path4"
|
||||
d="m 728.52,1007.06 c 0,0 369.52,114.79 409.46,129.84 39.93,14.95 473.58,134.1 503.58,140.49 29.22,6.43 334.55,100.61 369.43,109.88 34.96,9.26 265.42,70.62 265.42,70.62 0,0 73.48,17.78 147.66,21.34 74.18,2.88 76.27,-5.69 103.4,-7.83 27.13,-1.42 10.69,-7.13 19.31,-10 7.82,-2.13 41.31,-36.35 39.22,-52.78 -2.18,-16.4 16.34,-44.22 -51.4,-77 -67.74,-33.58 -141.23,-50.67 -168.36,-57.1 -27.05,-6.39 -158.37,-40.62 -158.37,-40.62 0,0 32.18,-15 22.18,-37.13 -10,-22.09 -44.96,-34.22 -44.96,-34.22 l -166.19,-74.22 c 0,0 41.39,-12.08 47.05,-20.66 5.74,-8.56 20.7,-29.96 18.52,-43.47 -2.09,-13.62 -7.13,-37.88 -22.78,-54.97 -15.74,-16.39 -84.18,-70.609 -159.06,-94.191 -74.88,-24.211 -323.16,-101.957 -349.51,-116.957 -26.44,-14.223 -289.63,-109.102 -289.63,-109.102 0,0 -50.62,-11.441 -77.03,-12.128 -41.35,-1.43 -38.51,42.789 -38.51,42.789 0,0 -53.48,-12.871 -67.07,-18.571 -13.53,-5 -74.88,-32.09 -90.552,-29.91 -16.437,2.129 -49.961,9.949 -69.902,32.09 -19.289,22.82 -6.418,68.441 0.691,73.48 6.418,5 21.379,28.481 21.379,28.481 0,0 -94.121,-22.09 -113.41,-22.781 -18.516,-0.7 -83.438,-8.621 -99.871,-3.61 -16.367,5.039 -54.895,13.571 -47.766,43.52 7.129,30.699 26.387,53.488 47.071,62.051 20.695,8.609 -74.903,-26.352 -74.903,-26.352 0,0 -45.633,-16.43 -79.16,4.262 -32.832,20.699 -12.129,67.789 0.703,87.008 12.824,20 31.387,36.402 41.367,44.921 10.676,8.61 111.993,52.83 111.993,52.83"
|
||||
style="fill:#1c63b7;fill-opacity:1;fill-rule:nonzero;stroke:none"
|
||||
transform="matrix(0.13333333,0,0,-0.13333333,0,329.84)" />
|
||||
<path
|
||||
id="path5"
|
||||
d="m 144.184,1624.47 c 0,0 130.488,55.6 193.277,76.31 144.078,47.09 321.703,98.43 380.211,115.57 55.613,17.09 227.519,69.18 373.038,113.4 115.6,34.96 215.41,64.93 226.15,67.75 24.94,5.7 524.24,162.66 540.68,143.4 16.4,-18.52 67.78,-64.92 64.92,-132.01 -2.87,-67.74 -126.97,-232.5 -148.37,-243.89 -22.13,-10.73 -391.6,-83.49 -391.6,-83.49 0,0 191.15,75.62 199.68,-2.86 7.91,-78.45 -148.36,-106.28 -156.19,-110.54 -7.83,-5 -310.29,-99.87 -537.097,-172.63 -150.559,-48.47 -247.531,-85.56 -256.828,-87 -24.219,-2.14 -66.309,-34.96 -108.426,-18.56 -28.508,11.38 -64.875,43.47 -48.488,83.44 16.418,40.7 51.379,48.52 51.379,48.52 0,0 -22.844,-7.82 -71.329,-16.43 -48.527,-7.84 -57.089,-26.35 -97.031,-18.53 -40.637,7.83 -51.379,56.36 -49.246,69.92 2.848,13.53 -57.023,-14.27 -86.293,-17.87 -29.957,-3.56 -62.07,-17.09 -92.031,-0.69 -29.195,16.43 -80.59,16.43 -80.59,72.74 0,87.05 87.77,108.49 94.184,113.45"
|
||||
style="fill:#ed1c24;fill-opacity:1;fill-rule:nonzero;stroke:none"
|
||||
transform="matrix(0.13333333,0,0,-0.13333333,0,329.84)" />
|
||||
<path
|
||||
id="path6"
|
||||
d="m 1933.07,2015.15 c 0,0.04 -5.39,22.36 -5.39,22.36 -4.7,18.3 -10.48,41.04 -10.96,50.74 -4.61,40.61 -26.09,74.92 -63.92,102 -46.75,27.35 -97.36,52.66 -152.11,53.36 -57.99,2.13 -111.74,-9.48 -159.79,-34.57 -26,-14.04 -47.04,-36.48 -67.44,-58.22 -14.31,-15.22 -29.08,-30.96 -45.7,-44.09 -20.05,-13.84 -38.66,-30.05 -56.66,-45.7 l -31.8,-27.05 c -0.72,-0.18 -4.38,-1.78 -5.48,-1.26 l -0.72,1.26 c -6.5,24.43 -11.26,49.57 -15.89,73.88 -8.42,44.27 -17.09,90.05 -37,134.05 -23.51,50.31 -55.42,94.32 -100.45,138.58 -20.18,20.83 -71.6,39.74 -73.76,40.57 l -0.9,0.3 -0.89,-0.25 c -2.96,-0.88 -71.84,-21.48 -76.51,-57.93 -2.35,-18.43 12.11,-37.17 43.03,-55.7 -0.05,0 0.7,-0.6 1.37,-1.13 -8.55,-5.69 -79.213,-3.35 -122.104,-1.91 l -59.785,1.22 c -0.547,-0.09 -18.703,-3.3 -18.703,-3.3 -29.695,-4.84 -63.379,-10.22 -63.985,-46.58 -0.71,-7.95 -1.062,-16.09 -1.062,-24.48 0,-40.04 8.191,-84.14 23.367,-124.27 2.637,-7.08 7.305,-13.44 11.809,-19.52 4.433,-6.09 8.672,-11.83 11.023,-18.22 15.195,-66.48 54.719,-109.79 96.594,-155.71 0,0 0.476,-0.56 0.691,-0.78 -2.468,-0.23 -5.019,-0.48 -5.019,-0.48 -78.934,-7.39 -160.645,-15.05 -238.086,-38.31 -15.672,-1.17 -32.852,-11.47 -51.875,-31.44 -12.324,-17.56 -17.891,-32.35 -17.891,-46.96 -0.633,-4.26 -0.886,-8.56 -0.886,-12.92 0,-24.47 9.433,-49.61 29.218,-77.83 26.043,-41 54.942,-74.09 88.289,-101.18 42.285,-31.87 88.555,-54.04 150.059,-71.88 27.148,-8.39 49.328,7.18 70.832,22.27 10.242,7.18 20.918,13.95 31.964,18.65 -15.987,-28.7 -28.362,-64 -28.362,-98.22 0,-25.97 6.984,-51.06 24.812,-71.62 20.02,-21.57 48.7,-32.36 73.99,-40.78 15,-2.79 29.26,-2.48 44.35,-2.09 l 28.42,-0.18 c 31.61,2 64.33,18.92 83.75,43.13 56.2,83.31 84.5,172.06 84.5,264.42 0,10.65 -0.39,21.39 -1.15,32.14 0.4,0.87 3.15,7 3.15,7 0,0 1.06,2.3 2.11,4.52 3.59,-3.26 25.67,-23.13 25.67,-23.13 27.53,-21.57 28.79,-24.4 44.32,-58.66 l 31.3,-66.01 c 7.92,-15.48 11.61,-37.96 11.61,-65.26 0,-123.88 -76.31,-346.73 -183.8,-465.79 -23.87,-26.48 -35.65,-45.479 -35.65,-57.569 0,-3.082 0.76,-5.699 2.26,-7.91 12.61,-18.09 71.96,4.738 78.68,7.437 l 0.84,0.301 0.48,0.781 c 1.53,2.391 151.32,240.16 199.37,429.52 7.92,31.17 12.79,66.31 12.79,102.49 0,74.31 -20.44,153.15 -76.97,211.23 -0.61,0.48 -11.65,9.7 -11.65,9.7 -4.61,4.35 -9.91,9.13 -16.05,12.82 1.31,2.62 3.74,7.8 5.05,10.53 28,-7.34 53.83,-18.74 78.74,-29.88 30,-13.43 58.27,-26.08 90.4,-32.21 11.35,-2.79 19.36,-7.44 27.09,-11.96 11.74,-6.83 22.78,-13.22 43.49,-12.34 l 31.91,3.38 c 14.4,2.13 23.87,3.56 33.83,-0.99 3.56,-1.62 8.44,-0.79 14.79,2.69 27.56,15 77.74,76.52 81.92,105.79 0.17,1.65 0.17,3.61 0.17,5.7 0,24.92 -9.39,77.96 -37.57,99.66 -14.09,11.04 -43.22,25.78 -68.88,38.74 -20.43,10.31 -45.22,22.88 -49.74,27.92 75.44,40.96 143.45,9.44 144.14,9.12 70.75,-45.56 93.75,-60.35 101.88,-64.43 0.35,-0.26 0.14,-0.83 0.53,-1.04 0,0 0.34,0.47 0.43,0.56 3.13,-1.48 3.74,-1.09 4.3,-0.35 l 1.61,2.48 -3.26,2.13 c -2.57,1.65 2.66,12.61 7.22,22.27 10.74,22.6 26.91,56.73 19.7,96.39"
|
||||
style="fill:#00a650;fill-opacity:1;fill-rule:nonzero;stroke:none"
|
||||
transform="matrix(0.13333333,0,0,-0.13333333,0,329.84)" />
|
||||
<path
|
||||
id="path7"
|
||||
d="m 1775.14,1881.4 v -0.04 0.04"
|
||||
style="fill:#00a650;fill-opacity:1;fill-rule:nonzero;stroke:none"
|
||||
transform="matrix(0.13333333,0,0,-0.13333333,0,329.84)" />
|
||||
<path
|
||||
id="path8"
|
||||
d="m 2381.63,1549.8 c -19.35,58.06 -69.83,99.1 -128.66,104.58 -50.74,6.52 -87.62,3.26 -134.32,-0.87 l -31.66,-2.69 c -7.73,0 -42.09,-13.79 -89.83,-33.23 -11.31,-4.64 -22.13,-9.04 -31.31,-12.69 26.09,21.52 67.58,64.78 102.79,105.45 40.62,45.56 86.8,102.61 94.71,125.74 5.87,16.7 20.78,117.1 21.56,127.67 0.09,4.04 0.18,7.87 0.18,11.52 0,26.18 -2.65,43.61 -5.04,59.23 l -3.65,33.04 c -2.18,73.53 -15.66,139.58 -15.78,140.27 l -1.27,6.31 -3.83,-5.22 c -0.26,-0.35 -24.26,-33.52 -30.48,-40.44 -6.3,-6.31 -104.57,-74.44 -159.83,-107.13 -87.37,-52.18 -126.85,-119.76 -139.81,-141.98 l -3.09,-5.22 c -8.12,-13.3 -17.43,-33.96 -26.38,-53.91 -8.4,-18.66 -16.31,-36.31 -22.09,-45.18 -8.18,-12.05 -17.87,-38.87 -24.96,-58.53 -3.14,-8.65 -6.14,-16.88 -7.22,-18.56 -5.74,-6.14 -8.05,-18.71 -10.26,-30.83 -1.26,-6.96 -2.7,-13.4 -4.7,-18.18 -0.91,1.74 -1.91,3.92 -2.7,6.3 -0.26,0.92 -7.74,26.13 -55.13,113.89 -43.83,81.75 -50.53,81.18 -132.49,73.96 l -30.75,-2.57 c -90.52,-6.61 -157.61,-53.31 -193.62,-78.39 l -16.61,-11.19 c -18.61,-11.47 -55.14,-62.95 -97.33,-122.56 l -47.75,-66.71 6.37,0.65 c 11.18,1.31 21.61,-0.22 32.74,-1.83 11.58,-1.65 23.6,-3.08 36.38,-1.29 -0.72,-3.44 -1.52,-6.92 -1.52,-10.41 0,-17.38 9.08,-34.64 25.87,-47.91 20.93,-20.91 41.13,-33.3 67.29,-41.13 3.28,-0.13 5.2,-4.53 6.97,-8.83 2.01,-4.66 4.05,-9.52 7.94,-12.17 l 1.46,-1.05 1.91,0.92 c 3.26,1.83 4.67,9.43 6.87,82.87 0.39,14.61 0.85,29.75 1.24,31.61 15.78,49.92 54.74,81.71 106.66,86.96 19.22,2.09 74.44,-4.86 82.53,-45.47 0.22,-0.75 10.95,-24.74 17.31,-31.26 0.08,-0.32 18,-51.75 18,-51.75 11.04,-32.75 19.74,-58.61 39.09,-98.84 2.65,-4.78 2.91,-9.82 3.21,-15.09 l 0.83,-8.43 c 0.87,-4.26 1.3,-8.78 1.3,-13.43 0,-16.27 -5.08,-34.71 -14.56,-52.49 -7.78,-13.52 -24.35,-33.78 -36.57,-36.35 -31.65,-6 -39.7,-0.78 -59.13,11.91 l -18.01,11.26 c -13.12,7.7 -25.6,64.01 -29.34,85.06 4.38,42.96 10.56,117.18 3.3,123.05 -4.39,3.91 -12.74,-2.61 -63.44,-49.7 -30.74,-28.56 -68.98,-64.09 -100.56,-87.96 -45.07,-33.66 -67.46,-75.7 -83.84,-106.44 -7.11,-13.35 -13.27,-24.88 -19.27,-31.58 -8.53,-9.09 -5.27,-21.34 -2.15,-33.13 l 2.84,-12.04 c 5.53,-28.18 22.73,-49.4 39.34,-69.92 l 3.37,-4.17 c 11.22,-15.49 44.61,-56.01 44.93,-56.41 0.81,-0.73 56.85,-53.65 102.47,-14.61 64.27,20.79 94.79,40.35 96,41.18 22.88,11.31 47.88,22.87 60.19,27.48 -20.7,-20.05 -62.1,-74.3 -76.53,-118.36 -14.4,-45.26 -44.27,-117.611 -51.92,-133.002 l -7.83,-15.269 c -15.09,-29.688 -50.35,-99.227 -59.46,-108.868 -11.5,-11.492 -15.96,-19.402 -8.11,-30.711 1.65,-2.429 3.53,-3.691 5.7,-3.91 3.86,-0.308 7.12,2.918 11.22,6.961 3.86,3.828 8.7,8.598 15.17,11.688 9.26,4.832 59.84,51.711 113.31,101.32 42.71,39.57 86.84,80.492 101.19,91.57 29.53,23.012 51.87,45.401 80.14,73.701 l 7.91,7.87 c 0,0 73.97,75.23 85.4,86.84 9.13,-5.74 56.18,-34.52 92.88,-46.18 26.53,-5.22 57.83,-9.22 90.78,0 58.71,17.35 147.76,82.4 212.11,133.88 l 3.74,3 -4.34,1.87 c -11.92,5.13 -25.57,0.65 -42.87,-5 -21.49,-6.96 -48.28,-15.7 -83.32,-9.78 -50.75,7.43 -69.05,14.7 -82.4,20 l -9.22,3.44 c -0.04,0 -5.47,1.74 -5.47,1.74 -34.22,10.6 -69.62,21.7 -90.05,51.34 -0.18,0.53 -0.27,1.05 -0.27,1.65 0,2.36 1,5.75 2.09,9.49 l 2.17,8.91 c 4.66,17.61 18.09,29.57 31.05,41.14 l 11.66,10.83 c 23.99,29.13 46.61,47.92 71.22,59.34 20.48,10.49 52.53,23.61 89.66,26.88 l 22.35,0.21 c 24.34,0.35 47.3,0.7 71.13,-3.08 17.83,-2.35 22.57,-21.23 27.14,-39.49 l 1.21,-4.77 c 0.62,-12.05 -1.95,-27.23 -7.08,-41.1 -4.44,-3.82 -57.1,-9.25 -88.49,-12.52 -43.75,-4.48 -43.78,-4.65 -44.44,-7.65 l -0.26,-1.13 0.83,-1.22 c 12.3,-18.87 53.01,-27.22 76.22,-30.48 65.05,-9.13 153.67,-3.05 183.41,22.78 38.96,35.74 67.83,74.32 83.53,111.58 18.05,47.96 22.04,84.96 12.13,113.35"
|
||||
style="fill:#fddd04;fill-opacity:1;fill-rule:nonzero;stroke:none"
|
||||
transform="matrix(0.13333333,0,0,-0.13333333,0,329.84)" />
|
||||
<path
|
||||
id="path9"
|
||||
d="m 712.691,1138.68 c -4.218,-0.13 -106.554,-52.31 -179.718,-22.31 -54.871,22.53 -56.582,56.49 -59.414,74.88 -1.864,12.31 8.027,28.09 22.16,42.35 -22.903,-17.22 -43.379,-41.22 -40.723,-58.53 2.801,-18.35 4.52,-52.35 59.395,-74.83 56.238,-23.05 129.707,2.39 162.578,15.53 15.547,14.96 38.465,23 35.722,22.91"
|
||||
style="fill:#231f20;fill-opacity:1;fill-rule:nonzero;stroke:none"
|
||||
transform="matrix(0.13333333,0,0,-0.13333333,0,329.84)" />
|
||||
<path
|
||||
id="path10"
|
||||
d="m 789.223,1026.02 c -18.789,-4.34 -46.356,-8.56 -58.817,-6.22 -36.328,6.83 -64.043,31.83 -66.965,66.22 -0.781,9.18 2.872,17.22 8.418,24.13 -13.757,-8.95 -28.476,-22.56 -26.98,-40.3 2.891,-34.36 30.633,-59.36 66.961,-66.18 2.305,-0.43 4.133,-0.91 5.703,-1.39 6.758,2.95 10.977,4.78 10.977,4.78 0,0 24.152,7.56 60.703,18.96"
|
||||
style="fill:#231f20;fill-opacity:1;fill-rule:nonzero;stroke:none"
|
||||
transform="matrix(0.13333333,0,0,-0.13333333,0,329.84)" />
|
||||
<path
|
||||
id="path11"
|
||||
d="m 51.7539,239.469 112.6801,7.051 c 2.437,-18.29 7.402,-32.239 14.898,-41.829 12.195,-15.5 29.629,-23.261 52.289,-23.261 16.899,0 29.934,3.961 39.082,11.902 9.152,7.93 13.727,17.117 13.727,27.566 0,9.942 -4.364,18.821 -13.075,26.672 -8.714,7.852 -28.933,15.25 -60.652,22.219 -51.937,11.68 -88.973,27.191 -111.1092,46.531 -22.3086,19.352 -33.461,44.012 -33.461,73.989 0,19.703 5.707,38.3 17.125,55.82 11.4102,17.512 28.5822,31.281 51.5042,41.301 22.914,10.031 54.332,15.039 94.242,15.039 48.976,0 86.312,-9.11 112.019,-27.328 25.711,-18.211 41.004,-47.18 45.887,-86.922 l -111.633,-6.528 c -2.964,17.25 -9.191,29.797 -18.691,37.641 -9.5,7.84 -22.617,11.758 -39.344,11.758 -13.769,0 -24.14,-2.918 -31.113,-8.75 -6.973,-5.852 -10.453,-12.949 -10.453,-21.309 0,-6.101 2.875,-11.59 8.625,-16.472 5.578,-5.059 18.824,-9.758 39.738,-14.11 51.762,-11.168 88.844,-22.449 111.238,-33.859 22.395,-11.418 38.692,-25.578 48.887,-42.481 10.195,-16.918 15.293,-35.82 15.293,-56.73 0,-24.578 -6.797,-47.238 -20.391,-67.981 -13.593,-20.738 -32.589,-36.457 -56.992,-47.187 -24.402,-10.711 -55.16,-16.07 -92.285,-16.07 -65.184,0 -110.32,12.55 -135.4179,37.648 -25.0977,25.09 -39.3086,56.992 -42.6172,95.68"
|
||||
style="fill:#ed1c24;fill-opacity:1;fill-rule:nonzero;stroke:none"
|
||||
transform="matrix(0.13333333,0,0,-0.13333333,0,329.84)" />
|
||||
<path
|
||||
id="path12"
|
||||
d="m 659.059,221.43 101.171,-11.5 c -5.578,-21.098 -14.726,-39.348 -27.453,-54.782 -12.722,-15.418 -28.972,-27.41 -48.75,-35.937 -19.785,-8.539 -44.929,-12.813 -75.422,-12.813 -29.46,0 -53.992,2.75 -73.593,8.223 -19.614,5.481 -36.473,14.367 -50.586,26.649 -14.121,12.269 -25.192,26.679 -33.203,43.222 -8.02,16.539 -12.028,38.477 -12.028,65.828 0,28.551 4.875,52.321 14.637,71.309 7.145,13.922 16.906,26.41 29.281,37.48 12.375,11.051 25.102,19.27 38.172,24.68 20.735,8.531 47.32,12.801 79.735,12.801 45.312,0 79.863,-8.11 103.652,-24.32 23.801,-16.211 40.48,-39.911 50.07,-71.098 l -100.129,-13.34 c -3.133,11.848 -8.847,20.777 -17.129,26.789 -8.281,6.02 -19.382,9.027 -33.328,9.027 -17.601,0 -31.851,-6.308 -42.746,-18.929 -10.89,-12.598 -16.336,-31.699 -16.336,-57.27 0,-22.801 5.399,-40.109 16.211,-51.937 10.801,-11.821 24.57,-17.742 41.305,-17.742 13.937,0 25.656,3.562 35.16,10.71 9.5,7.149 16.602,18.129 21.309,32.95"
|
||||
style="fill:#ed1c24;fill-opacity:1;fill-rule:nonzero;stroke:none"
|
||||
transform="matrix(0.13333333,0,0,-0.13333333,0,329.84)" />
|
||||
<path
|
||||
id="path13"
|
||||
d="m 902.711,250.969 c 0,-24.75 5,-43.047 15.027,-54.899 10.02,-11.859 22.617,-17.781 37.774,-17.781 15.332,0 27.929,5.84 37.785,17.52 9.843,11.671 14.763,30.41 14.763,56.211 0,24.05 -4.97,41.949 -14.9,53.722 -9.933,11.77 -22.219,17.637 -36.855,17.637 -15.52,0 -28.332,-5.969 -38.438,-17.91 -10.109,-11.93 -15.156,-30.098 -15.156,-54.5 z m -106.672,-0.258 c 0,42.348 14.289,77.25 42.883,104.699 28.574,27.449 67.187,41.18 115.808,41.18 55.6,0 97.6,-16.129 126.01,-48.371 22.83,-25.969 34.25,-57.949 34.25,-95.949 0,-42.688 -14.16,-77.68 -42.49,-104.961 -28.32,-27.27 -67.49,-40.911 -117.504,-40.911 -44.621,0 -80.703,11.321 -108.234,33.993 -33.817,28.05 -50.723,64.828 -50.723,110.32"
|
||||
style="fill:#ed1c24;fill-opacity:1;fill-rule:nonzero;stroke:none"
|
||||
transform="matrix(0.13333333,0,0,-0.13333333,0,329.84)" />
|
||||
<path
|
||||
id="path14"
|
||||
d="m 1458.77,112.672 h -99.36 v 44.969 c -14.8,-18.469 -29.76,-31.641 -44.83,-39.481 -15.07,-7.84 -33.59,-11.762 -55.55,-11.762 -29.28,0 -52.25,8.75 -68.89,26.274 -16.64,17.519 -24.96,44.476 -24.96,80.91 v 176.727 h 106.92 v -152.68 c 0,-17.43 3.23,-29.797 9.67,-37.117 6.45,-7.321 15.52,-10.973 27.19,-10.973 12.72,0 23.14,4.871 31.25,14.641 8.09,9.75 12.15,27.269 12.15,52.539 v 133.59 h 106.41 V 112.672"
|
||||
style="fill:#ed1c24;fill-opacity:1;fill-rule:nonzero;stroke:none"
|
||||
transform="matrix(0.13333333,0,0,-0.13333333,0,329.84)" />
|
||||
<path
|
||||
id="path15"
|
||||
d="M 1651.7,495.93 V 390.309 h 58.55 V 312.41 h -58.55 v -98.371 c 0,-11.828 1.13,-19.66 3.4,-23.48 3.47,-5.918 9.57,-8.868 18.3,-8.868 7.83,0 18.81,2.258 32.93,6.778 l 7.85,-73.438 c -26.33,-5.761 -50.9,-8.633 -73.73,-8.633 -26.49,0 -46.02,3.391 -58.56,10.184 -12.55,6.789 -21.83,17.098 -27.85,30.938 -5.99,13.839 -9,36.25 -9,67.23 v 97.66 h -39.22 v 77.899 h 39.22 v 50.98 l 106.66,54.641"
|
||||
style="fill:#ed1c24;fill-opacity:1;fill-rule:nonzero;stroke:none"
|
||||
transform="matrix(0.13333333,0,0,-0.13333333,0,329.84)" />
|
||||
<path
|
||||
id="path16"
|
||||
d="m 1764.88,390.309 h 106.41 V 112.672 h -106.41 z m 0,105.621 h 106.41 v -72.418 h -106.41 v 72.418"
|
||||
style="fill:#ed1c24;fill-opacity:1;fill-rule:nonzero;stroke:none"
|
||||
transform="matrix(0.13333333,0,0,-0.13333333,0,329.84)" />
|
||||
<path
|
||||
id="path17"
|
||||
d="m 1939.26,390.309 h 99.08 v -45.227 c 14.8,18.469 29.8,31.668 44.96,39.609 15.18,7.93 33.63,11.899 55.43,11.899 29.45,0 52.5,-8.758 69.14,-26.281 16.66,-17.508 24.98,-44.567 24.98,-81.168 V 112.672 h -106.93 v 152.68 c 0,17.418 -3.22,29.75 -9.67,36.988 -6.45,7.23 -15.51,10.851 -27.19,10.851 -12.91,0 -23.36,-4.882 -31.36,-14.64 -8.03,-9.762 -12.04,-27.281 -12.04,-52.551 V 112.672 h -106.4 v 277.637"
|
||||
style="fill:#ed1c24;fill-opacity:1;fill-rule:nonzero;stroke:none"
|
||||
transform="matrix(0.13333333,0,0,-0.13333333,0,329.84)" />
|
||||
<path
|
||||
id="path18"
|
||||
d="m 2389.43,255.672 c 0,-20.043 4.28,-34.891 12.82,-44.57 8.53,-9.672 19.78,-14.512 33.73,-14.512 13.24,0 24.35,5.012 33.32,15.039 8.98,10.019 13.47,25.133 13.47,45.351 0,20.211 -4.7,35.68 -14.12,46.399 -9.41,10.73 -20.92,16.082 -34.51,16.082 -13.59,0 -24.43,-4.922 -32.54,-14.77 -8.1,-9.851 -12.17,-26.191 -12.17,-49.019 z m 100.14,134.637 h 99.61 V 128.09 l 0.25,-12.281 c 0,-17.4301 -3.71,-34.0277 -11.11,-49.809 -7.4,-15.7695 -17.25,-28.5312 -29.53,-38.2891 C 2536.48,17.9492 2520.9,10.8906 2501.99,6.53125 2483.07,2.17188 2461.43,0 2437.01,0 c -55.76,0 -94.06,8.37109 -114.88,25.0898 -20.84,16.7305 -31.25,39.129 -31.25,67.1915 0,3.4882 0.17,8.1877 0.53,14.1167 l 103.26,-11.7691 c 2.62,-9.5781 6.62,-16.207 12.03,-19.8672 7.83,-5.4023 17.68,-8.1015 29.53,-8.1015 15.33,0 26.8,4.1015 34.38,12.289 7.59,8.1914 11.38,22.4808 11.38,42.8708 v 42.09 c -10.47,-12.371 -20.92,-21.351 -31.38,-26.918 -16.37,-8.722 -34.07,-13.082 -53.07,-13.082 -37.11,0 -67.09,16.211 -89.92,48.629 -16.21,23 -24.32,53.41 -24.32,91.242 0,43.219 10.45,76.16 31.37,98.821 20.92,22.648 48.28,33.988 82.09,33.988 21.6,0 39.43,-3.66 53.45,-10.988 14.05,-7.313 27.15,-19.43 39.36,-36.34 v 41.047"
|
||||
style="fill:#ed1c24;fill-opacity:1;fill-rule:nonzero;stroke:none"
|
||||
transform="matrix(0.13333333,0,0,-0.13333333,0,329.84)" />
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 23 KiB |
170
mobile/apps/auth/assets/custom-icons/icons/smogon.svg
Normal file
@@ -0,0 +1,170 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
<svg xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:cc="http://creativecommons.org/ns#" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:svg="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg" version="1.1" width="451.00259" height="507.332" viewBox="0 0 451.00259 507.332" id="svg2">
|
||||
<defs id="defs4"/>
|
||||
<metadata id="metadata7">
|
||||
<rdf:RDF>
|
||||
<cc:Work rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type rdf:resource="http://purl.org/dc/dcmitype/StillImage"/>
|
||||
<dc:title/>
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<g transform="translate(-197.35585,-58.696183)" id="layer1">
|
||||
<g transform="translate(28.601602,6.2831826)" id="g2986">
|
||||
<g id="g2988">
|
||||
<g id="g2990">
|
||||
<path d="m 284.953,425.625 c -41.441,-0.008 -74.219,-15.391 -94.77,-44.48 -23.637,-33.461 -28.066,-83.176 -11.559,-129.75 21.262,-60.016 68.266,-89.355 68.742,-89.641 l 2.801,-1.703 4.555,-14.98 21.422,4.02 14.844,29.734 -0.773,5.273 c -0.738,4.98 -1.48,9.875 -2.215,14.695 l -0.191,1.238 c -5.277,34.695 -9.832,64.66 -6.102,82.488 2.242,10.711 7.199,17.898 15.895,22.906 2.984,-3.594 5.02,-5.305 7.535,-5.305 h 0.812 l 0.98,0.34 c 3.582,1.434 4.047,5.508 3.922,10.246 0.348,0.098 0.703,0.199 1.07,0.289 6.363,1.621 11.062,5.375 13.602,10.852 4.191,9.082 0.234,17.125 -3.254,24.215 -2.578,5.238 -6.379,12.953 -8.754,23.418 3.586,6.008 4.141,11.75 3.934,15.641 -0.297,5.516 -2.406,11.066 -5.996,15.848 1.102,4.875 0.484,9.473 -1.844,13.688 -5.285,9.566 -15.68,10.465 -19.094,10.754 -1.574,0.137 -3.34,0.215 -5.262,0.215 h -0.3 z" id="path2992" style="fill:#260859"/>
|
||||
</g>
|
||||
<g id="g2994">
|
||||
<path d="m 506.766,425.695 c -6.77,0 -12.57,-2.73 -16.773,-7.902 -8.246,-10.133 -6.812,-26.621 -3.918,-41.938 -3.145,-2.297 -7.34,-5.594 -9.633,-10.934 l -5.355,-12.438 11.875,-6.527 c 24.914,-13.723 34.227,-39.723 27.668,-77.289 -2.957,-4.148 -7.086,-11.844 -10.824,-25.562 -2.727,-10.008 -13.16,-72.691 -7.508,-83.227 1.012,-1.898 5.035,-8.109 14.039,-8.109 5.695,0 19.23,3.664 28.004,12.398 0.246,-0.012 0.488,-0.016 0.734,-0.016 5.488,0 9.648,2.668 13.672,5.242 l 0.477,0.301 c 1.07,0.68 2.219,1.41 3.492,2.164 2.246,1.336 6.668,3.961 10.598,9.719 6.301,1.035 11.359,5.949 20.273,15.086 0.207,0.219 0.996,1.027 1.203,1.227 2.219,2.195 3.977,5.051 5.602,9.125 11.438,4.383 18.34,21.609 19.629,27.852 l 0.508,1.938 c 15.383,58.23 11.477,106.727 -11.297,140.25 -18.512,27.266 -48.637,43.574 -89.535,48.465 -1.045,0.117 -1.994,0.175 -2.931,0.175 l 0,0 z" id="path2996" style="fill:#260859"/>
|
||||
</g>
|
||||
</g>
|
||||
<path d="M 382.699,555.371 C 362.824,548.375 188.613,484.344 188.613,390.375 l 0.012,-281.641 c 0,-25.367 21.531,-46.004 47.996,-46.004 16.855,0 32.289,8.32 40.957,21.902 31.453,-3.801 73.332,-11.934 99.66,-23.809 l 2.559,-1.129 15.438,-7.281 16.172,7.629 c 26.121,12.301 69,20.699 101.461,24.602 8.672,-13.59 24.102,-21.914 40.949,-21.914 26.473,0 48.008,20.551 48.008,45.812 l -0.027,281.832 c 0,94.301 -174.176,158.012 -194.039,164.965 l -12.523,4.406 -12.537,-4.374 z" id="path2998" style="fill:#260859"/>
|
||||
<path d="M 389.637,535.469 C 371.207,528.989 209.688,469.871 209.688,390.375 L 209.7,108.734 c 0,-13.93 12.086,-24.93 26.922,-24.93 14.336,0 26.137,10.309 26.867,23.469 l 0.004,0.012 c 35.363,-3.055 88.648,-12.023 122.41,-27.25 l 2.426,-1.07 6.906,-3.254 7.176,3.387 c 33.441,15.754 87.988,25.031 124.539,28.188 h 0.004 c 0.73,-13.168 12.527,-23.48 26.863,-23.48 14.852,0 26.934,11.098 26.934,24.738 l -0.027,281.832 c 0,79.676 -161.52,138.625 -179.938,145.082 l -5.57,1.961 -5.579,-1.95 z" id="path3000" style="fill:#7c3a00"/>
|
||||
<path d="m 395.156,112.82 -1.527,0.621 c -37.711,15.406 -92.113,24.406 -127.242,27.43 l -22.969,1.984 -0.008,130.859 H 395.156 V 112.82 z" id="path3002" style="fill:#ffef6f"/>
|
||||
<path d="m 395.223,75.715 -6.895,3.25 -2.426,1.07 c -33.762,15.227 -87.047,24.195 -122.41,27.25 l -0.004,-0.012 c -0.73,-13.16 -12.531,-23.469 -26.867,-23.469 -14.836,0 -26.922,11 -26.922,24.93 l -0.012,281.641 c 0,79.496 161.52,138.613 179.949,145.094 l 5.578,1.949 0.008,-0.004 V 75.715 z" id="path3004" style="fill:#e8941a"/>
|
||||
<g id="g3006">
|
||||
<path d="m 553.816,100.664 c -5.395,0 -9.801,3.312 -10.035,7.539 l -0.941,17.375 -17.34,-1.5 c -38.246,-3.301 -94.871,-13.051 -130.277,-29.734 -0.773,0.363 -1.609,0.707 -2.402,1.062 0.016,-0.004 0.031,-0.012 0.043,-0.016 v 307.098 l 171.008,-126.707 0.02,-167.238 c -0.001,-4.27 -4.615,-7.879 -10.076,-7.879 z" id="path3008" style="fill:#e8941a"/>
|
||||
</g>
|
||||
<path d="M 392.863,397.402 V 95.391 c -35.527,16.031 -90.5,25.457 -127.926,28.688 l -17.336,1.5 -0.941,-17.367 c -0.234,-4.234 -4.645,-7.547 -10.039,-7.547 -5.766,0 -10.082,3.891 -10.09,7.219 l 0.027,0.852 -0.004,152.738 166.309,135.928 z" id="path3010" style="fill:#ffef6f"/>
|
||||
<path d="m 525.5,124.078 c -38.246,-3.301 -94.871,-13.051 -130.277,-29.734 -0.492,0.23 -1.031,0.449 -1.527,0.676 v 41.531 l 30.055,-1.555 113.195,21.496 10.082,-13.633 -4.188,-17.281 -17.34,-1.5 z" id="path3012" style="fill:#e8941a"/>
|
||||
<path d="m 393.695,95.02 c -35.492,16.262 -91.043,25.805 -128.758,29.059 l -17.336,1.5 -4.184,17.277 19.438,6.918 102.445,-11.754 28.395,-1.469 V 95.02 z" id="path3014" style="fill:#ffffff"/>
|
||||
<path d="m 226.555,243.211 -0.008,147.652 c 0,55.215 105.336,106.918 168.656,129.176 63.324,-22.238 168.66,-73.918 168.66,-129.176 l 0.016,-147.652 H 226.555 z" id="path3016" style="fill:#ffd26c"/>
|
||||
<path d="M 393.746,508.047 C 318.914,480.473 243.406,435.543 243.406,397.313 l 0.012,-254.457 22.969,-1.984 c 35.129,-3.023 89.531,-12.023 127.242,-27.43 l 1.594,-0.648 1.59,0.648 c 37.711,15.406 92.109,24.406 127.242,27.43 l 22.973,1.988 -0.023,254.453 c 0,38.266 -75.508,83.188 -150.344,110.734 l -1.457,0.535 -1.458,-0.535 z" id="path3018" style="fill:#ffffff"/>
|
||||
<path d="m 524.055,140.871 c -35.133,-3.023 -89.531,-12.023 -127.242,-27.43 l -1.59,-0.648 -1.594,0.648 1.527,-0.621 v 160.895 h 151.859 l 0.012,-130.855 -22.972,-1.989 z" id="path3020" style="fill:#ffd26c"/>
|
||||
<path d="m 395.156,112.82 -1.527,0.621 c -37.711,15.406 -92.113,24.406 -127.242,27.43 l -22.969,1.984 -0.008,130.859 H 395.156 V 112.82 z" id="path3022" style="fill:#ffef6f"/>
|
||||
<linearGradient x1="395.21631" y1="132.92529" x2="395.21631" y2="491.23761" id="SVGID_1_" gradientUnits="userSpaceOnUse">
|
||||
<stop id="stop3025" style="stop-color:#e8941a;stop-opacity:1" offset="0"/>
|
||||
<stop id="stop3027" style="stop-color:#ffef6f;stop-opacity:1" offset="1"/>
|
||||
</linearGradient>
|
||||
<path d="M 395.203,494.371 C 315.765,464.609 256.051,422.691 256.051,396.586 l 0.012,-242.137 11.41,-0.984 c 34.82,-2.996 88.879,-11.863 127.75,-27.043 38.859,15.18 92.914,24.047 127.742,27.043 l 11.418,0.988 -0.023,242.133 c -10e-4,26.133 -59.715,68.051 -139.157,97.785 l 0,0 z" id="path3029" style="fill:url(#SVGID_1_)"/>
|
||||
<path d="m 534.359,377.328 c 0,26.133 -59.715,68.051 -139.156,97.785 C 315.777,445.355 256.07,403.449 256.051,377.34 v 23.805 c 0,26.102 59.715,68.02 139.152,97.785 79.441,-29.734 139.156,-71.652 139.156,-97.785 l 0.023,-246.691 0,0 -0.023,222.874 z" id="path3031" style="fill:#260859"/>
|
||||
<path d="m 395.156,126.445 c -38.867,15.164 -92.883,24.023 -127.684,27.02 l -11.41,0.984 v 16.746 l 10.133,-0.867 c 35.152,-2.996 89.707,-11.855 128.961,-27.02 v -16.863 z" id="path3033" style="fill:#a84d10"/>
|
||||
<path d="m 522.965,153.465 c -34.828,-2.996 -88.883,-11.863 -127.742,-27.043 -0.223,0.086 -0.457,0.164 -0.68,0.25 0.199,-0.074 0.41,-0.148 0.613,-0.227 v 16.863 c 0.02,-0.008 0.043,-0.016 0.066,-0.023 39.25,15.18 93.844,24.047 129.023,27.043 l 10.137,0.871 v -16.746 l -11.417,-0.988 z" id="path3035" style="fill:#a84d10"/>
|
||||
<path d="m 256.062,158.086 -0.012,238.5 c 0,7.613 5.105,16.57 14.262,26.176 1.164,0.109 2.312,0.242 3.492,0.328 0.074,1.215 0.168,2.344 0.277,3.438 23.133,22.137 66.586,47.148 118.852,66.977 l -0.059,-1.766 c -0.59,-18.352 -2.348,-35.594 -4.691,-46.121 -5.438,-24.406 -22.613,-48.82 -37.359,-62.367 -6.184,-15.457 -1.742,-25.371 4.391,-39.07 1.871,-4.168 3.992,-8.887 5.699,-13.855 5.145,-14.93 4.266,-38.211 -1.984,-53.98 -2.898,-9.637 -9.184,-16.973 -16.832,-19.625 -2.328,-0.777 -5.027,-1.312 -8.23,-1.625 -3.215,-10.938 -1.609,-22.059 -1.598,-22.148 l 1.754,-10.398 -9.758,-3.961 c -0.977,-0.398 -9.941,-3.902 -23.02,-3.902 -6.027,0 -12.105,0.785 -18.117,2.34 0.82,-5.617 1.695,-11.391 2.598,-17.309 0,0 1.664,-10.949 2.402,-15.922 l 0.676,-4.617 -15.496,-26.328 c 0,0 -3.242,0.398 -5.836,0.617 l -11.411,4.618 z" id="path3037" style="fill:#260859"/>
|
||||
<path d="m 514.695,428.105 c 0.016,-1.758 0.035,-3.516 -0.082,-5.402 2.242,-0.328 4.438,-0.699 6.609,-1.102 8.461,-9.164 13.137,-17.707 13.137,-25.016 l -1.562,-226.52 c 0,0 2.516,-0.133 2.234,-0.102 -3.75,-10.566 -23.34,-16.086 -28.695,-16.086 -7.617,0 -11.059,4.895 -12.184,6.996 -5.09,9.496 4.488,69.965 7.688,81.676 0.309,1.141 0.621,2.191 0.938,3.234 l -1.672,8.418 c -14.316,-13.652 -32.395,-28.645 -42.766,-28.645 -0.656,0 -1.289,0.055 -1.898,0.156 -2.648,0.461 -4.789,1.961 -6.016,4.215 -1.777,3.258 -1.703,7.801 0.27,14.133 -5.043,-1.027 -11.129,-1.98 -16.34,-1.98 -6.387,0 -10.586,1.453 -12.836,4.434 -4.473,5.934 -2.328,15.238 6.367,27.66 2.562,3.668 5.789,7.285 8.969,10.848 0.66,0.746 1.328,1.492 1.992,2.242 -9.152,14.402 -11.535,36.027 -6.352,58.285 2.27,9.738 5.41,16.656 8.188,22.77 1.766,3.883 3.203,7.047 4.09,10.273 -21.77,18.578 -37.285,42.938 -42.648,67.016 -2.184,9.785 -3.836,25.16 -4.547,42.199 l -0.23,5.742 c 51.096,-19.35 93.811,-43.674 117.346,-65.444 z" id="path3039" style="fill:#260859"/>
|
||||
<g id="g3041">
|
||||
<path d="m 321.699,323.621 c -1.984,-4.289 -5.723,-7.242 -10.809,-8.539 -19.477,-4.91 -29.746,-14.688 -33.309,-31.699 -3.887,-18.574 0.723,-48.887 6.062,-83.988 l 0.188,-1.234 c 0.734,-4.816 1.477,-9.707 2.211,-14.672 l 0.582,-3.965 -13.316,-26.676 -15.652,-2.938 -3.922,12.906 -4.168,2.527 c -0.457,0.281 -46.105,28.566 -66.969,87.457 -16.059,45.312 -11.832,93.551 11.023,125.91 19.73,27.922 51.32,42.691 91.348,42.699 1.902,0.008 3.645,-0.066 5.184,-0.195 4.969,-0.43 12.004,-1.789 15.77,-8.598 2.121,-3.844 2.418,-7.98 0.891,-12.609 3.871,-4.461 6.141,-9.797 6.426,-15.113 0.195,-3.613 -0.395,-9.031 -4.211,-14.613 2.449,-11.852 6.652,-20.379 9.449,-26.062 3.375,-6.86 6.57,-13.344 3.222,-20.598 z" id="path3043" style="fill:#260859"/>
|
||||
<g id="g3045">
|
||||
<path d="m 199.93,259.562 c 14.34,-66.531 55.953,-77.508 55.953,-77.508 l -1.012,36.066 0.816,76.336 c 0,0 -2.602,60.848 30.262,73.891 32.863,13.039 6.941,42.215 -36.23,31.109 C 208.5,389.602 184.465,330.16 199.93,259.562 z" id="path3047" style="fill:#ffffff"/>
|
||||
<g id="g3049">
|
||||
<path d="m 228.879,317.406 c -10.066,-45.742 11.844,-91.457 22.941,-105.285 l 13.945,-45.91 7.914,15.453 c -4.523,30.586 -9.375,58.598 -9.957,80.766 -0.121,4.418 0.922,20.125 1.629,23.516 3.336,15.93 11.039,27.066 24.359,34.395 2.242,1.234 13.094,-16.984 15.656,-15.965 3.352,1.344 -1.461,21.836 2.469,22.828 9.133,2.305 -6.898,13.684 -11.938,45.445 -21.198,1.91 -56.975,-9.61 -67.018,-55.243 z" id="path3051" style="fill:#f26531"/>
|
||||
</g>
|
||||
<path d="m 194.371,256.984 c 19.648,-55.469 61.68,-80.957 61.68,-80.957 l -0.434,15.422 c -15.742,12.766 -62.27,71.184 -44.375,141.27 13.469,48.656 52.797,63.223 80.523,57.949 -4.102,9.059 14.18,18.254 -6.789,18.254 -91.675,-0.02 -114.8,-83.645 -90.605,-151.938 z" id="path3053" style="fill:#7ac143"/>
|
||||
</g>
|
||||
<polygon points="213.434,210.594 205.309,222.129 239.594,232.855 " id="polygon3055" style="fill:#260859"/>
|
||||
<polygon points="191.312,240.535 186.168,257.379 230.988,256.758 " id="polygon3057" style="fill:#260859"/>
|
||||
<polygon points="183.648,279.039 183.082,296.645 226.18,284.332 " id="polygon3059" style="fill:#260859"/>
|
||||
<polygon points="180.789,326.434 186,346.004 229.383,318.148 " id="polygon3061" style="fill:#260859"/>
|
||||
<polygon points="201.324,373.969 214.367,389.453 241.773,345.785 " id="polygon3063" style="fill:#260859"/>
|
||||
<polygon points="241.258,405.695 259.137,409.742 267.953,367.039 " id="polygon3065" style="fill:#260859"/>
|
||||
</g>
|
||||
<path d="m 324.59,268.086 c 0.148,0.016 0.281,0.008 0.438,0.023 -9.18,-17.043 -5.715,-37.352 -5.715,-37.352 0,0 -16.691,-6.77 -37.332,0.254 -24.391,8.301 -27.098,26.691 -24.246,32.094 2.688,5.09 9.637,6.027 15.934,2.34 3.547,9.219 8,15.848 18.32,24.191 l 34.66,-20.137 c -10e-4,10e-4 -0.837,-0.394 -2.059,-1.413 z" id="path3067" style="fill:#fff200"/>
|
||||
<path d="m 274.98,347.574 c -5.398,0 -8.523,-1.82 -9.355,-2.379 l -9.891,-6.449 12.168,-6.41 c 5.203,-1.285 10.617,-7.621 15.832,-14.719 -1.098,0.18 -2.164,0.363 -3.195,0.535 -6.023,1.031 -11.086,1.895 -14.895,1.895 -0.688,0 -1.328,-0.031 -1.918,-0.082 -0.203,-0.02 -0.395,-0.027 -0.59,-0.027 -4.445,0 -10.258,3.727 -12.125,5.164 l -6.609,5.445 -4.531,-8.77 c -0.234,-0.688 -2.207,-6.969 1.93,-14.664 3.105,-5.758 12.41,-13.875 23.41,-13.875 1.82,0 3.633,0.219 5.387,0.652 0.594,0.148 1.184,0.223 1.734,0.223 2.828,0 5.781,-1.98 8.781,-5.883 0.738,-1.004 1.242,-1.891 1.648,-2.598 0.473,-0.82 0.875,-1.5 1.395,-2.191 1.762,-2.109 4.008,-3.098 6.93,-3.098 5.004,0 11.262,2.648 13.82,10.074 l 0,0 c 0.008,0 0.719,1.465 4.141,4.703 3.637,0.766 6.613,2.043 8.863,3.809 3.297,2.598 5.059,6.297 4.953,10.43 -0.207,8.285 -7.871,17.668 -23.434,28.684 -11.148,7.875 -19.449,9.531 -24.449,9.531 l 0,0 z" id="path3069" style="fill:#260859"/>
|
||||
<path d="m 285.051,298.215 c -8.113,-6.562 -13.383,-12.48 -17.387,-19.559 -0.559,0.035 -1.121,0.059 -1.676,0.059 -7.941,0 -14.508,-3.812 -18.008,-10.461 -2.953,-5.582 -2.887,-13.828 0.176,-21.527 2.715,-6.844 10.266,-19.348 30.266,-26.16 7.363,-2.504 15.043,-3.773 22.824,-3.773 12.699,0 21.285,3.363 22.227,3.746 l 8.191,3.324 -1.473,8.734 c -0.02,0.137 -2.246,15.293 3.441,28.035 l 16.324,8.004 -17.734,10.391 -41.266,23.961 -5.905,-4.774 z" id="path3071" style="fill:#260859"/>
|
||||
<path d="m 313.297,327.484 c -4.66,0 -5.777,-4.406 -6.758,-8.293 -0.754,-2.98 -1.535,-6.062 -3.594,-8.117 -7.707,-7.703 -18.688,-12.191 -18.801,-12.238 l -1.008,-0.406 -0.25,-1.062 c -1.91,-8.031 -0.074,-15.875 5.312,-22.695 8.305,-10.508 25,-17.848 40.602,-17.848 4.613,0 8.863,0.637 12.629,1.895 11.438,3.969 18.797,19.078 16.785,34.398 -2.59,19.672 -19.195,32.516 -44.418,34.352 l -0.499,0.014 z" id="path3073" style="fill:#260859"/>
|
||||
<path d="m 390.77,491.805 c -0.586,-18.211 -2.32,-35.305 -4.645,-45.73 -5.391,-24.207 -22.441,-48.285 -37.074,-61.586 -6.785,-16.547 -1.922,-27.41 4.227,-41.141 1.859,-4.137 3.961,-8.82 5.645,-13.711 6.371,-18.496 3.301,-52.016 -9.367,-64.766 l -0.875,-0.883 -17.277,4.875 -0.285,1.207 c -3.629,15.34 -12.52,35.328 -19.969,40.625 l -5.223,3.711 0.672,6.355 c 1.371,13.16 -5.613,28.898 -23.094,64.328 -6.949,14.074 -9.164,28.023 -7.043,43.664 23.328,21.27 64.762,44.902 114.336,63.93 l -0.028,-0.878 z" id="path3075" style="fill:#260859"/>
|
||||
<path d="m 286.379,296.578 c -8.293,-6.707 -13.605,-12.801 -17.535,-20.16 -0.957,0.125 -1.914,0.188 -2.855,0.188 -7.129,0 -13.016,-3.398 -16.145,-9.336 -2.672,-5.051 -2.57,-12.621 0.27,-19.766 2.586,-6.512 9.793,-18.406 28.988,-24.941 7.148,-2.434 14.594,-3.664 22.145,-3.664 12.223,0 20.52,3.219 21.426,3.59 l 6.637,2.691 -1.195,7.066 c -0.023,0.141 -2.43,16.492 3.918,29.945 l 13.477,6.609 -14.352,8.406 -40,23.23 -4.779,-3.858 z" id="path3077" style="fill:#260859"/>
|
||||
<path d="m 324.59,268.086 c 0.148,0.016 0.281,0.008 0.438,0.023 -9.18,-17.043 -5.715,-37.352 -5.715,-37.352 0,0 -16.691,-6.77 -37.332,0.254 -24.391,8.301 -27.098,26.691 -24.246,32.094 2.688,5.09 9.637,6.027 15.934,2.34 3.547,9.219 8,15.848 18.32,24.191 l 34.66,-20.137 c -10e-4,10e-4 -0.837,-0.394 -2.059,-1.413 z" id="path3079" style="fill:#fff200"/>
|
||||
<path d="m 291.426,228.664 c -3.023,0.5 -6.176,1.234 -9.445,2.348 -1.793,0.609 -3.449,1.281 -5.012,1.988 0.133,0.762 0.27,1.52 0.402,2.305 2.273,13.105 5.395,30.973 23.766,49.016 l 3.77,-2.191 4.488,-4.691 c -0.477,-0.453 -0.945,-0.914 -1.398,-1.371 -15.052,-15.072 -17.841,-27.463 -16.571,-47.404 z" id="path3081" style="fill:#f26531"/>
|
||||
<path d="m 319.312,230.758 c 0,0 -4.73,-1.898 -12.234,-2.641 -2.945,19.641 0.68,32.223 5.871,43.66 2.867,-2.016 6.352,-4.246 12.078,-3.668 -9.179,-17.043 -5.715,-37.351 -5.715,-37.351 z" id="path3083" style="fill:#f26531"/>
|
||||
<path d="m 340.762,260.719 c -22.492,-7.508 -62.18,9.445 -55.824,36.164 0,0 11.375,4.582 19.496,12.699 5.488,5.477 3.191,16.223 9.211,15.781 55.078,-4.008 48.554,-57.203 27.117,-64.644 z" id="path3085" style="fill:#260859"/>
|
||||
<path d="m 384.066,446.531 c -5.324,-23.91 -22.367,-47.836 -36.781,-60.797 -7.402,-17.637 -2.094,-29.484 4.07,-43.25 1.84,-4.102 3.926,-8.746 5.574,-13.535 6.051,-17.562 3.086,-50.559 -8.871,-62.594 l -14.891,4.199 c -3.703,15.652 -12.715,36.105 -20.797,41.855 l -4.219,3 0.543,5.133 c 1.426,13.711 -5.629,29.66 -23.301,65.484 -7.051,14.281 -9.199,28.594 -6.531,44.879 23.355,20.395 62.828,42.727 109.797,60.965 -0.671,-20.983 -2.706,-36.886 -4.593,-45.339 z" id="path3087" style="fill:#260859"/>
|
||||
<path d="m 375.43,448.457 c -4.688,-21.027 -20.555,-44.578 -35.52,-57.488 -0.02,-0.051 -0.031,-0.094 -0.059,-0.145 -12.793,-28.352 1.895,-44.961 8.711,-64.754 5.188,-15.062 2.301,-44.332 -6.785,-53.473 -2.863,12.109 -12.199,38.438 -24.281,47.027 1.711,16.387 -6.383,34.281 -24.168,70.316 -7.707,15.625 -8.574,31.184 -3.348,49.906 22.02,16.477 53.324,33.613 89.688,48.441 -0.727,-17.475 -2.406,-31.607 -4.238,-39.83 z" id="path3089" style="fill:#f26531"/>
|
||||
<path d="m 336.758,268.195 c -10.891,-5.066 -39.801,0.785 -49.402,24.723 0,0 8.891,5.105 14.688,12.75 3.91,5.168 3.672,14.082 8.707,14.211 42.866,1.105 42.554,-43.981 26.007,-51.684 z" id="path3091" style="fill:#f26531"/>
|
||||
<path d="m 272.738,340.402 c -1.93,0 -3.652,-0.895 -4.738,-2.457 -1.965,-2.816 -0.754,-6.086 -0.109,-7.84 3.441,-9.297 12.891,-18.176 21.5,-20.219 l 16.016,-4.246 1.781,0.863 c 4.453,2.152 2.496,-3.094 5.336,-1.969 11.52,4.566 5.867,13.898 4.336,16.051 -3.465,4.875 -7.785,4.871 -10.066,5.266 -9.285,1.57 -14.492,4.105 -18.293,5.965 -1.504,0.734 -2.801,1.367 -4.184,1.898 -0.977,0.379 -3.066,2.02 -4.32,3 -2.829,2.224 -4.7,3.688 -7.259,3.688 l 0,0 z" id="path3093" style="fill:#260859"/>
|
||||
<path d="m 304.859,311.324 -14.234,3.773 c -6.875,1.625 -14.824,9.062 -17.707,16.859 -2.891,7.805 4.785,-1.43 9.469,-3.234 4.668,-1.812 10.121,-5.887 23.5,-8.156 13.379,-2.265 -1.028,-9.242 -1.028,-9.242 z" id="path3095" style="fill:#f26531"/>
|
||||
<path d="m 290.859,310.074 c 0,0 -11.062,22.945 -25.168,28.18 l 10.75,1.375 c 0,0 15.59,-9.383 18.441,-19.707 2.852,-10.312 2.551,-15.832 2.551,-15.832 l -6.574,5.984 z" id="path3097" style="fill:#260859"/>
|
||||
<path d="m 313.492,304.547 c -6.148,-4.84 -21.82,-2.699 -21.82,-2.699 l 8.758,5.172 c -8.391,9.762 -18.191,29.141 -30.828,32.25 0,0 8.082,5.398 25.703,-7.062 17.621,-12.46 24.347,-22.825 18.187,-27.661 z" id="path3099" style="fill:#fff200"/>
|
||||
<path d="m 289.879,287.703 c -0.773,1.035 -1.34,2.477 -3.102,4.871 -2.613,3.402 -8.828,10.496 -17.902,8.246 -9.074,-2.25 -18.012,4.523 -20.785,9.676 -2.77,5.152 -1.465,8.965 -1.465,8.965 0,0 9.391,-7.359 17.73,-6.609 8.348,0.758 32.059,-6.914 50.258,-4.016 0,0 -14.355,-10.16 -16.457,-16.086 -2.011,-5.848 -7.902,-5.488 -8.277,-5.047 z" id="path3101" style="fill:#fff200"/>
|
||||
<g id="g3103">
|
||||
<path d="m 318.129,275.305 c -8.238,10.934 -12.203,12.973 -19.973,17.445 18.227,33.57 52.637,-9.355 19.973,-17.445 z" id="path3105" style="fill:#260859"/>
|
||||
</g>
|
||||
<g id="g3107">
|
||||
<path d="m 315.082,300.051 c -3.988,0 -7.168,-2.73 -9.383,-5.508 5.203,-3.199 9.039,-6.363 14.363,-13.004 4.559,1.859 6.637,4.664 6.188,8.344 -0.574,4.703 -5.578,10.168 -11.168,10.168 l 0,0 z" id="path3109" style="fill:#ffffff"/>
|
||||
</g>
|
||||
<path d="m 317.875,282.395 c -2.621,1.168 -5.152,2.633 -7.699,3.727 -1.414,0.609 -3.875,2.562 -3.633,3.16 1.148,2.824 3.023,5.551 6.082,6.75 0.297,0.117 0.602,0.211 0.906,0.289 0.695,-1.246 1.336,-2.582 1.898,-4.008 1.118,-2.86 2.009,-6.751 2.446,-9.918 z" id="path3111" style="fill:#260859"/>
|
||||
<path d="m 313.707,296.379 c 0,-2.535 -0.289,-4.934 -0.781,-7.129 -2.305,2.059 -4.594,3.672 -7.227,5.293 1.91,2.398 4.551,4.75 7.801,5.352 0.125,-1.137 0.207,-2.305 0.207,-3.516 z" id="path3113" style="fill:#f37344"/>
|
||||
<path d="m 280.828,251.062 c 0.438,9.141 4.355,19.59 12.043,29.02 l 4.793,-4.211 c -11.609,-7.101 -16.836,-24.809 -16.836,-24.809 z" id="path3115" style="fill:#260859"/>
|
||||
<path d="m 291.141,238.789 c -1.156,10.66 1.484,23.461 8.672,35.766 l 6.305,-4 c -12.141,-10.336 -14.977,-31.766 -14.977,-31.766 z" id="path3117" style="fill:#260859"/>
|
||||
<path d="m 306.738,231.996 c -2.785,10.348 -2.152,23.402 3.047,36.672 l 6.844,-2.973 c -10.399,-12.082 -9.891,-33.699 -9.891,-33.699 z" id="path3119" style="fill:#260859"/>
|
||||
<path d="m 334.832,367.363 c 0.266,-6.996 2.133,-13.312 4.527,-19.387 0.395,-1.004 -7.34,5.734 -15.191,7.973 -7.262,2.07 -14.66,-0.41 -14.953,0.293 -2.434,5.727 -5.387,12.094 -8.852,19.297 19.012,1.656 27.176,-2.223 34.469,-8.176 z" id="path3121" style="fill:#7ac143"/>
|
||||
<path d="m 337.445,353.391 c 1.25,-3.918 2.812,-7.699 4.465,-11.438 -12.93,7.711 -20.523,10.559 -30.961,10.07 -1.262,3.164 -2.719,6.547 -4.34,10.152 13.114,1.403 24.301,-3.511 30.836,-8.784 z" id="path3123" style="fill:#ffffff"/>
|
||||
<path d="m 293.305,389.996 c -7.684,15.605 -8.547,31.148 -3.324,49.852 12.047,9.012 26.852,18.227 43.773,27.152 12.125,-36.82 -2.57,-65.309 -8.16,-75.688 -8.168,2.25 -33.199,11.153 -32.289,-1.316 z" id="path3125" style="fill:#ffffff"/>
|
||||
<g id="g3127">
|
||||
<path d="m 426.086,226.523 c -3.566,0 -6.266,-1.336 -7.719,-2.051 -0.879,-0.445 -2,-1.012 -7.145,-4.84 -5.227,1.496 -10.617,2.254 -16.043,2.254 -5.52,0 -10.984,-0.777 -16.266,-2.309 -5.227,3.891 -6.375,4.465 -7.148,4.855 -1.371,0.684 -4.199,2.09 -7.797,2.09 -4.008,0 -7.691,-1.621 -10.949,-4.805 -4.895,-4.824 -6.535,-9.34 -5.664,-15.602 0.137,-0.996 0.441,-2.527 1.691,-6.785 -1.613,-2.074 -3.102,-4.285 -4.441,-6.59 -9.844,-3.238 -12.664,-4.578 -14.113,-5.461 -4.582,-2.82 -6.859,-8.195 -6.98,-16.441 -0.117,-8.219 1.965,-13.617 6.363,-16.496 0.707,-0.461 1.859,-1.211 8.289,-4.105 0.246,-1.074 0.527,-2.141 0.832,-3.195 -5.164,-7.422 -5.906,-9.02 -6.309,-9.887 -1.41,-3.07 -2.191,-8.109 2.059,-14.746 1.551,-2.414 7.227,-10.281 15.102,-10.281 h 0.797 l 1.406,0.211 c 0.941,0.184 2.262,0.449 9.582,3.125 4.242,-2.965 8.844,-5.352 13.707,-7.109 2.277,-5.695 3.047,-6.969 3.422,-7.594 2.812,-4.617 8.301,-6.957 16.328,-6.957 8.25,0 13.824,2.352 16.57,6.988 0.398,0.676 1.055,1.781 3.363,7.566 4.855,1.758 9.449,4.141 13.676,7.098 7.227,-2.648 8.523,-2.91 9.391,-3.09 l 0.734,-0.148 1.395,-0.086 c 7.91,0 13.363,7.188 15.348,10.277 4.25,6.629 3.473,11.66 2.07,14.715 -0.418,0.906 -1.176,2.527 -6.289,9.871 0.312,1.074 0.594,2.156 0.844,3.246 6.473,2.906 7.566,3.625 8.297,4.105 4.398,2.875 6.48,8.273 6.359,16.496 -0.113,8.125 -2.453,13.648 -6.957,16.414 -1.016,0.625 -2.891,1.785 -14.141,5.484 -1.391,2.406 -2.957,4.711 -4.656,6.879 1.348,4.625 1.508,5.785 1.605,6.496 0.871,6.266 -0.762,10.777 -5.645,15.582 -3.269,3.201 -6.956,4.826 -10.968,4.826 l 0,0 z" id="path3129" style="fill:#260859"/>
|
||||
<path d="m 426.086,224.414 c -3.094,0 -5.512,-1.203 -6.812,-1.844 -0.746,-0.379 -1.895,-0.953 -7.633,-5.262 -5.348,1.641 -10.875,2.473 -16.461,2.473 -5.676,0 -11.273,-0.848 -16.676,-2.527 -5.824,4.367 -6.977,4.941 -7.691,5.301 -1.324,0.656 -3.75,1.859 -6.844,1.859 -3.438,0 -6.621,-1.41 -9.477,-4.203 -4.41,-4.344 -5.828,-8.211 -5.051,-13.801 0.137,-1 0.48,-2.645 1.938,-7.531 -1.992,-2.461 -3.781,-5.105 -5.344,-7.887 -10.289,-3.363 -13.133,-4.711 -14.441,-5.512 -3.922,-2.41 -5.867,-7.207 -5.973,-14.668 -0.109,-7.441 1.664,-12.254 5.41,-14.707 0.672,-0.438 1.848,-1.199 8.965,-4.375 0.359,-1.711 0.801,-3.402 1.309,-5.059 -5.598,-8.008 -6.336,-9.613 -6.707,-10.41 -1.188,-2.582 -1.816,-6.879 1.922,-12.715 1.41,-2.195 6.527,-9.309 13.328,-9.309 h 0.797 l 1.051,0.18 c 0.832,0.164 2.242,0.457 10.258,3.406 4.551,-3.312 9.668,-5.977 14.992,-7.801 2.492,-6.301 3.273,-7.594 3.621,-8.176 2.402,-3.941 7.281,-5.934 14.523,-5.934 7.453,0 12.418,2.004 14.754,5.953 0.371,0.625 1.039,1.754 3.574,8.16 5.312,1.828 10.418,4.48 14.965,7.789 8.012,-2.953 9.32,-3.219 10.129,-3.383 l 0.738,-0.152 1.047,-0.039 c 7.012,0 12.098,7.125 13.496,9.305 3.738,5.828 3.109,10.125 1.93,12.703 -0.387,0.828 -1.137,2.441 -6.684,10.375 0.52,1.68 0.957,3.387 1.32,5.109 7.172,3.195 8.281,3.922 8.969,4.375 3.746,2.449 5.52,7.262 5.414,14.707 -0.105,7.359 -2.109,12.285 -5.953,14.648 -0.922,0.566 -2.789,1.711 -14.469,5.527 -1.621,2.895 -3.48,5.625 -5.559,8.168 1.59,5.371 1.758,6.566 1.852,7.25 0.777,5.59 -0.633,9.457 -5.035,13.785 -2.863,2.808 -6.055,4.222 -9.492,4.222 l 0,0 z" id="path3131" style="fill:#260859"/>
|
||||
<path d="m 409.34,122.93 c 0,0 -5.383,-14.453 -6.75,-16.773 -1.375,-2.324 -13.246,-2.52 -14.793,0.031 -1.535,2.566 -6.75,16.742 -6.75,16.742 h 28.293 z" id="path3133" style="fill:#7f3f98"/>
|
||||
<path d="m 380.41,205.176 c 0,0 -11.297,8.801 -13.367,9.836 -2.066,1.031 -3.934,1.836 -6.656,-0.824 -2.676,-2.637 -2.969,-3.941 -2.598,-6.621 0.371,-2.668 4.875,-16.578 4.875,-16.578 l 17.746,14.187 z" id="path3135" style="fill:#7f3f98"/>
|
||||
<path d="m 409.645,205.176 c 0,0 11.297,8.801 13.367,9.836 2.066,1.031 3.938,1.836 6.656,-0.824 2.676,-2.637 2.969,-3.941 2.598,-6.621 -0.371,-2.668 -4.875,-16.578 -4.875,-16.578 l -17.746,14.187 z" id="path3137" style="fill:#7f3f98"/>
|
||||
<path d="m 352.539,155.469 c 0,0 -14.668,6.238 -16.891,7.691 -2.211,1.449 -2.164,13.59 0.336,15.125 2.5,1.531 17.105,6.121 17.105,6.121 l -0.55,-28.937 z" id="path3139" style="fill:#7f3f98"/>
|
||||
<path d="m 437.82,155.469 c 0,0 14.668,6.238 16.891,7.691 2.215,1.449 2.164,13.59 -0.336,15.125 -2.5,1.531 -17.105,6.121 -17.105,6.121 l 0.55,-28.937 z" id="path3141" style="fill:#7f3f98"/>
|
||||
<path d="m 348.719,153.543 -7.418,3.023 c -0.465,4.172 0.828,11.812 6.457,16.184 5.629,4.359 0.961,-19.207 0.961,-19.207 z" id="path3143" style="fill:#260859"/>
|
||||
<path d="m 441.246,153.543 7.418,3.023 c 0.465,4.172 -0.828,11.812 -6.457,16.184 -5.629,4.359 -0.961,-19.207 -0.961,-19.207 z" id="path3145" style="fill:#260859"/>
|
||||
<path d="m 367.875,129.066 c 0,0 -15.164,-5.836 -17.758,-6.371 -2.598,-0.547 -9.082,7.383 -7.863,10.039 1.223,2.66 10.508,15.508 10.508,15.508 l 15.113,-19.176 z" id="path3147" style="fill:#7f3f98"/>
|
||||
<path d="m 422.438,129.066 c 0,0 15.164,-5.836 17.754,-6.371 2.598,-0.547 9.086,7.383 7.867,10.039 -1.223,2.66 -10.508,15.508 -10.508,15.508 l -15.113,-19.176 z" id="path3149" style="fill:#7f3f98"/>
|
||||
<path d="m 359.793,121.949 c -2.27,3.754 -6.828,9.09 -3.359,17.332 2.875,-3.961 11.133,-15.062 11.43,-16.781 l -8.071,-0.551 z" id="path3151" style="fill:#260859"/>
|
||||
<path d="m 423.164,198.848 10.629,-4.398 3.391,4.844 c -4.156,3.188 -7.762,6.547 -14.637,6.121 -8.692,-0.552 0.617,-6.567 0.617,-6.567 z" id="path3153" style="fill:#260859"/>
|
||||
<path d="m 366.891,198.848 -10.629,-4.398 -3.391,4.844 c 4.156,3.188 7.762,6.547 14.637,6.121 8.695,-0.552 -0.617,-6.567 -0.617,-6.567 z" id="path3155" style="fill:#260859"/>
|
||||
<path d="m 430.566,121.949 c 2.266,3.754 6.824,9.09 3.352,17.332 -2.867,-3.961 -11.133,-15.062 -11.43,-16.781 l 8.078,-0.551 z" id="path3157" style="fill:#260859"/>
|
||||
<path d="m 443.168,163.367 c 0,26.504 -21.488,47.984 -47.988,47.984 -26.508,0 -47.992,-21.48 -47.992,-47.984 0,-26.504 21.484,-47.996 47.992,-47.996 26.5,0 47.988,21.492 47.988,47.996 z" id="path3159" style="fill:#7f3f98"/>
|
||||
</g>
|
||||
<path d="m 499.035,379.352 c -24.148,-14.574 -50.754,-40.875 -46.246,-67.043 1.824,-10.57 -6.406,-19.781 -14.363,-28.688 -3.098,-3.477 -6.305,-7.066 -8.812,-10.648 -5.574,-7.969 -11.219,-18.809 -6.41,-25.188 1.828,-2.426 5.477,-3.598 11.152,-3.598 6.438,0 14.301,1.52 19.57,2.734 -2.094,-5.496 -3.824,-12.004 -1.652,-15.984 0.93,-1.707 2.5,-2.793 4.531,-3.148 0.484,-0.082 1,-0.121 1.535,-0.121 10.691,0 31.27,18.254 44.082,30.727 l 2.691,-13.578 1.098,2.488 c 0.855,0.215 21,5.586 31.73,37.02 15.328,14.332 30.715,37.719 28.375,60.863 l -1.391,13.645 -24.707,-4.203 -10.504,-1.258 10.469,12.742 -6.188,10.285 -10.91,2.156 c -4.785,0.953 -10.098,1.809 -15.793,2.543 l -4.441,0.562 -3.816,-2.308 z" id="path3161" style="fill:#260859"/>
|
||||
<path d="m 499.883,380.711 c -8.453,-18.031 -15.75,-33.605 -11.617,-49.598 5.477,-21.191 4.926,-37.324 -1.633,-47.938 -5.246,-8.492 -12.496,-10.414 -13.895,-10.715 l -2.395,-0.512 -14.07,3.293 -0.402,0.141 c -17.102,5.828 -29.754,33.488 -21.32,69.688 2.219,9.543 5.305,16.332 8.051,22.375 2.02,4.445 3.656,8.047 4.535,11.91 -21.969,18.406 -37.598,42.656 -42.953,66.711 -2.16,9.668 -3.797,24.91 -4.496,41.824 l -0.195,4.844 c 48.699,-18.664 89.555,-41.785 113.125,-62.766 0.469,-12.918 -2.551,-27.004 -8.867,-40.922 -1.278,-2.812 -2.579,-5.585 -3.868,-8.335 z" id="path3163" style="fill:#260859"/>
|
||||
<path d="m 506.766,421.48 c -5.457,0 -10.125,-2.191 -13.5,-6.348 -7.531,-9.25 -5.598,-25.848 -2.543,-41.09 -0.52,-0.402 -1.082,-0.809 -1.566,-1.16 -2.887,-2.09 -6.832,-4.945 -8.84,-9.621 l -3.844,-8.93 8.52,-4.684 c 26.812,-14.766 36.797,-42.465 29.68,-82.332 -0.055,-0.164 -0.105,-0.328 -0.164,-0.488 -2.809,-3.723 -6.871,-11.012 -10.637,-24.828 -3.523,-12.914 -12.309,-71.832 -7.859,-80.129 0.945,-1.77 3.852,-5.887 10.324,-5.887 4.68,0 18.578,3.645 26.375,12.609 0.766,-0.152 1.562,-0.227 2.363,-0.227 4.25,0 7.562,2.121 11.398,4.578 1.246,0.789 2.582,1.645 4.094,2.543 2.199,1.305 6.66,3.945 10.301,10.102 5.594,0.066 9.668,3.734 19.703,14.016 0.09,0.102 1.055,1.09 1.293,1.32 2.059,2.035 3.676,4.914 5.316,9.516 10.035,2.156 17.312,18.496 18.711,25.281 0.051,0.262 0.152,0.633 0.289,1.145 l 0.273,1.02 c 15.062,57.008 11.363,104.316 -10.707,136.801 -17.801,26.219 -46.922,41.906 -86.551,46.645 -0.859,0.102 -1.648,0.148 -2.429,0.148 l 0,0 z" id="path3165" style="fill:#260859"/>
|
||||
<path d="m 595.402,237.887 c -1.785,-8.676 -10.664,-20.074 -11.051,-16.191 -0.199,2.031 1.609,13.535 2.16,20.777 -0.961,-0.691 -2.066,-1.449 -2.703,-1.969 -2.98,-14.074 -6.547,-29.066 -9.473,-31.961 -2.211,-2.176 -14.605,-15.398 -13.977,-11.551 2.879,17.59 7.48,30.176 11.324,39.035 -3.078,-2.262 -4.473,-3.328 -6.055,-4.422 -6.062,-4.195 -8.738,-30.359 -13.359,-39.422 -2.363,-4.652 -5.629,-6.57 -7.172,-7.484 -9.922,-5.895 -12.629,-9.863 -10.863,2.387 1.348,9.336 2.344,21.816 14.742,47.855 l -8.207,-6.227 c -6.785,-5.465 -11.07,-36.008 -13.523,-48.082 -2.031,-10.043 -21.027,-15.141 -21.797,-13.699 -1.18,2.203 3.742,53.883 8.754,72.25 5.008,18.359 10.426,24.254 10.836,25.301 3.816,20.711 5.387,47.844 -8.359,70.012 -2.25,3.625 -31.09,-18.539 -34.219,-15.227 -3.129,3.312 19.469,32.105 15.348,34.992 -2.375,1.672 -4.922,3.262 -7.656,4.766 1.898,4.414 10.406,6.801 11.91,12.598 0.566,0 -11.004,41.09 5.863,39.07 136.755,-16.336 89.27,-164.133 87.477,-172.808 z" id="path3167" style="fill:#ffffff"/>
|
||||
<path d="m 519.477,379.414 c -7.062,-21.422 -20.711,-50.16 -20.711,-50.16 l -28.637,-11.207 19.031,63.051 -6.086,-5.641 -1.121,4.543 10.562,19.551 c 0,0 27.895,4.254 31.422,-6.102 -1.546,-5.48 -2.132,-6.969 -4.46,-14.035 z" id="path3169" style="fill:#aaa4c4"/>
|
||||
<path d="m 506.004,389.176 c -3.055,0.156 -3.531,-6.988 -5.977,-6.602 -1.98,12.016 -3.234,29.453 7.898,28.121 105.012,-12.539 101.387,-102.594 93.238,-147.656 -1.339,28.816 1.314,121.039 -95.159,126.137 z" id="path3171" style="fill:#aaa4c4"/>
|
||||
<path d="m 499.363,403.242 c 1.051,4.82 3.543,8.055 8.562,7.453 48.184,-5.754 73.453,-27.84 85.957,-54.395 -20.577,34.462 -64.964,42.055 -94.519,46.942 z" id="path3173" style="fill:#f2f1f8"/>
|
||||
<path d="m 536.113,285.504 c -10.438,-31.156 -30.414,-36.156 -30.414,-36.156 l -1.723,13.531 c -1.992,-1.926 -34.645,-35.117 -46.812,-33.012 -8.293,1.434 -0.457,18.242 0.266,20.086 -6.105,-1.598 -28.066,-6.832 -32.543,-0.898 -4.473,5.93 2.891,17.617 6.457,22.711 8.84,12.648 26.277,24.93 23.523,40.898 -4.41,25.586 22.328,51.047 45.258,64.883 l 3.191,1.93 3.703,-0.469 c 5.539,-0.715 10.805,-1.555 15.66,-2.52 l 9.992,-1.977 4.938,-8.211 -12.848,-15.648 15.812,1.898 22.48,3.824 1.168,-11.398 c 2.088,-20.64 -11.045,-43.691 -28.108,-59.472 z" id="path3175" style="fill:#260859"/>
|
||||
<path d="m 511.441,262.371 0.492,25.715 20.984,8.66 C 522.91,277.422 511.441,262.371 511.441,262.371 z" id="path3177" style="fill:#6b5f91"/>
|
||||
<path d="m 497.977,381.613 c -8.625,-18.406 -16.074,-34.297 -11.754,-51.027 5.328,-20.621 4.859,-36.199 -1.383,-46.305 -4.797,-7.758 -11.289,-9.492 -12.543,-9.758 l -1.934,-0.414 -13.398,3.125 -0.414,0.145 c -16.215,5.523 -28.109,32.18 -19.945,67.215 2.172,9.352 5.219,16.047 7.902,21.953 2.297,5.055 4.102,9.027 4.949,13.617 -21.887,18.016 -37.902,42.508 -43.219,66.363 -2.129,9.539 -3.754,24.648 -4.445,41.449 l -0.16,3.938 c 46.297,-17.949 85.312,-39.887 108.82,-60.047 0.855,-12.961 -2.055,-27.484 -8.621,-41.949 -1.273,-2.801 -2.57,-5.566 -3.855,-8.305 z" id="path3179" style="fill:#260859"/>
|
||||
<path d="m 414.879,448.457 c -1.84,8.227 -3.52,22.379 -4.238,39.879 36.367,-14.809 67.703,-31.938 89.738,-48.418 3.441,-14 0.355,-31.008 -6.609,-46.34 -10.18,-22.418 -21.957,-42.605 -16.117,-65.207 10.691,-41.363 -7.207,-45.191 -7.207,-45.191 l -11.039,2.574 c -11.812,4.027 -21.172,26.828 -14.184,56.836 4.383,18.836 12.867,26.305 13.566,41.469 -24.969,19.054 -39.453,44.386 -43.91,64.398 z" id="path3181" style="fill:#ffffff"/>
|
||||
<polygon points="524.949,339.023 553.559,343.895 540.949,327.73 497.863,316.172 478.828,322.18 505.211,341.258 515.43,341.129 518.512,346.129 " id="polygon3183" style="fill:#42316f"/>
|
||||
<path d="m 512.535,370.281 c -0.336,-0.113 -1.629,-0.211 -2.324,-0.262 -3.934,-0.297 -11.246,-0.84 -16.621,-8.176 -9.293,-12.676 -18.852,-30.539 -18.945,-30.719 l -1.918,-3.586 16.203,-15.387 3.598,2.605 c 10.914,7.887 31.23,22.578 27.18,51.25 l -0.93,6.586 -6.243,-2.311 z" id="path3185" style="fill:#260859"/>
|
||||
<path d="m 507.996,273.875 c -8.719,-4.91 -1.633,8.113 -1.633,8.113 l 17.391,8.082 c 0,0 -7.035,-11.285 -15.758,-16.195 z" id="path3187" style="fill:#260859"/>
|
||||
<path d="m 497.902,358.684 c 5.445,7.418 12.918,5.246 16.504,6.578 3.41,-24.145 -12.492,-37.109 -25.016,-46.168 l -10.02,9.52 c 10e-4,-10e-4 9.341,17.515 18.532,30.07 z" id="path3189" style="fill:#f6a0a6"/>
|
||||
<polygon points="494.504,330.309 504.152,329.352 500.211,320.141 490.449,320.98 " id="polygon3191" style="fill:#260859"/>
|
||||
<path d="m 477.652,328.371 c 0.344,-1.586 0.645,-3.094 0.914,-4.574 l -0.824,-32.625 -7.129,-5.695 -4.527,-1.277 -6.68,1.555 c -3.07,1.051 -5.98,3.387 -8.48,6.777 4.602,7.199 9.676,10.191 8.43,22.199 -1.32,12.793 10.867,25.336 16.84,28.691 -0.165,-4.742 0.284,-9.649 1.456,-15.051 z" id="path3193" style="fill:#aaa4c4"/>
|
||||
<path d="m 515.441,362.789 c 2.875,-0.449 5.562,-0.922 8.031,-1.41 -11.984,-10.055 -24.457,-25.418 -30.348,-40.766 1.129,-0.16 2.285,-0.285 3.461,-0.387 l -12.074,-9.066 -1.441,17.137 20.973,26.426 11.398,8.066 z" id="path3195" style="fill:#260859"/>
|
||||
<g id="g3197">
|
||||
<path d="m 520.594,365.984 c -11.988,-10.059 -24.457,-25.414 -30.348,-40.773 14.125,-2 31.789,-0.09 63.312,18.684 2.402,-23.664 -8.188,-64.223 -120.211,-88.75 19.531,31.617 35.305,33.953 32.133,58.875 -2.477,19.504 17.742,40.82 40.18,54.359 5.613,-0.715 10.582,-1.535 14.934,-2.395 z" id="path3199" style="fill:#ffffff"/>
|
||||
</g>
|
||||
<polygon points="502.367,321.293 514.109,336.367 528.445,325.293 " id="polygon3201" style="fill:#ffffff"/>
|
||||
<polygon points="474.906,256.566 459.891,260.328 490.523,302.746 " id="polygon3203" style="fill:#260859"/>
|
||||
<polygon points="519.023,350.121 506.781,357.129 514.066,363.012 " id="polygon3205" style="fill:#ffffff"/>
|
||||
<polygon points="515.023,361.746 520.594,365.984 523.348,358.102 " id="polygon3207" style="fill:#ffffff"/>
|
||||
<g id="g3209">
|
||||
<path d="m 462.426,239.809 c 11.012,28.695 21.609,50.254 24.316,56.68 3.309,7.875 6.352,11.246 16.789,16.836 6.863,3.676 19.824,10.402 31.555,10.422 -8.766,-21.824 -16.195,-32.91 -38.211,-53.188 -11.328,-10.43 -24.145,-21.758 -34.449,-30.75 z" id="path3211" style="fill:#594c82"/>
|
||||
</g>
|
||||
<g id="g3213">
|
||||
<path d="M 521.926,322.051 C 505.781,318.36 496.391,306.5 495.473,288.668 l -0.387,-7.441 6.039,4.363 c 20.16,14.57 24.406,26.645 25.137,32.543 l 0.633,5.062 -4.969,-1.144 z" id="path3215" style="fill:#260859"/>
|
||||
</g>
|
||||
<g id="g3217">
|
||||
<path d="m 504.73,294.418 c 0.742,14.297 7.789,26.453 23.688,30.094 -0.844,-6.774 -6.383,-17.582 -23.688,-30.094 z" id="path3219" style="fill:#ffffff"/>
|
||||
</g>
|
||||
<path d="m 521.02,307.242 -1.926,-0.84 c -0.336,0.574 -0.645,1.184 -0.918,1.844 -1.785,4.355 -1.414,8.625 0.836,9.559 1.629,0.664 3.961,-0.762 5.711,-3.18 l -3.703,-7.383 z" id="path3221" style="fill:#260859"/>
|
||||
<path d="m 493.77,393.578 c -0.039,-0.094 -0.086,-0.184 -0.125,-0.273 -1.496,2.672 -2.945,5.547 -2.945,5.547 0,0 -19.02,-6.207 -25.148,-7.895 -5.867,10.895 -20.238,38.785 -8.461,75.781 16.672,-8.82 31.375,-17.91 43.289,-26.82 3.44,-14 0.354,-31.008 -6.61,-46.34 z" id="path3223" style="fill:#6b5f91"/>
|
||||
<path d="m 282.871,437.34 c 22.973,18.836 59.043,39.004 101.59,55.91 v -12.582 c -45.699,-18.512 -81.504,-40.121 -101.59,-58.043 v 14.715 z" id="path3225" style="fill:#260859"/>
|
||||
<path d="m 406.188,493.156 c 42.293,-16.809 78.164,-36.848 101.102,-55.594 v -14.703 c -19.926,17.703 -55.094,38.953 -99.949,57.25 l -1.153,13.047 z" id="path3227" style="fill:#260859"/>
|
||||
<path d="m 403.402,148.789 c -0.047,-0.816 0.004,-1.645 0.133,-2.477 1.031,-5.996 6.719,-10.016 12.719,-8.984 5.887,1.008 9.855,6.508 9.039,12.367 -7.613,-1.144 -14.949,-1.629 -21.891,-0.906 z" id="path3229" style="fill:#ffffff"/>
|
||||
<path d="m 365.43,162.512 c 6.816,9.141 17.707,15.051 29.973,15.051 12.27,0 23.16,-5.91 29.973,-15.051 H 365.43 z" id="path3231" style="fill:#260859"/>
|
||||
<path d="m 416.684,170.902 c -4.809,-1.645 -12.762,-2.707 -21.766,-2.707 -8.594,0 -16.242,0.969 -21.102,2.484 6.102,4.32 13.543,6.883 21.586,6.883 7.907,0 15.239,-2.472 21.282,-6.66 z" id="path3233" style="fill:#b30838"/>
|
||||
<path d="m 359.141,162.512 0.438,1.328 c 3.145,9.539 4.848,12.016 8.293,12.043 h 0.027 c 3.363,0 5.168,-2.57 8.371,-11.938 l 0.496,-1.434 h -17.625 z" id="path3235" style="fill:#260859"/>
|
||||
<g id="g3237">
|
||||
<path d="m 363.828,162.43 c 0,0 2.953,8.965 4.055,8.973 1.102,0.012 4.156,-8.914 4.156,-8.914 l -8.211,-0.059 z" id="path3239" style="fill:#ffffff"/>
|
||||
</g>
|
||||
<path d="m 414.266,162.512 0.438,1.328 c 3.145,9.539 4.848,12.016 8.293,12.043 h 0.027 c 3.363,0 5.168,-2.57 8.375,-11.938 l 0.492,-1.434 h -17.625 z" id="path3241" style="fill:#260859"/>
|
||||
<g id="g3243">
|
||||
<path d="m 418.953,162.43 c 0,0 2.953,8.965 4.055,8.973 1.105,0.012 4.156,-8.914 4.156,-8.914 l -8.211,-0.059 z" id="path3245" style="fill:#ffffff"/>
|
||||
</g>
|
||||
<path d="m 411.148,196.828 c 0,4.668 -7.133,8.449 -15.926,8.449 -8.797,0 -15.93,-3.781 -15.93,-8.449 0,-4.668 7.133,-8.449 15.93,-8.449 8.794,0 15.926,3.781 15.926,8.449 z" id="path3247" style="fill:#ffef6f"/>
|
||||
<path d="m 400.66,196.828 c 0,1.594 -2.434,2.883 -5.438,2.883 -3,0 -5.438,-1.289 -5.438,-2.883 0,-1.598 2.438,-2.891 5.438,-2.891 3.005,10e-4 5.438,1.293 5.438,2.891 z" id="path3249" style="fill:#7f3f98"/>
|
||||
<path d="m 410.676,141.695 c 0.078,2.168 1.301,3.871 2.711,3.816 1.422,-0.066 2.496,-1.863 2.402,-4.031 -0.082,-2.176 -1.309,-3.895 -2.727,-3.832 -1.414,0.059 -2.492,1.868 -2.386,4.047 z" id="path3251" style="fill:#260859"/>
|
||||
<path d="m 386.906,148.789 c 0.051,-0.816 -0.004,-1.645 -0.133,-2.477 -1.027,-5.996 -6.715,-10.016 -12.715,-8.984 -5.887,1.008 -9.855,6.508 -9.039,12.367 7.614,-1.144 14.95,-1.629 21.887,-0.906 z" id="path3253" style="fill:#ffffff"/>
|
||||
<path d="m 379.633,141.695 c -0.082,2.168 -1.301,3.871 -2.707,3.816 -1.426,-0.066 -2.504,-1.863 -2.402,-4.031 0.082,-2.176 1.309,-3.895 2.723,-3.832 1.417,0.059 2.495,1.868 2.386,4.047 z" id="path3255" style="fill:#260859"/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 39 KiB |
49
mobile/apps/auth/assets/custom-icons/icons/toyhouse.svg
Normal file
@@ -0,0 +1,49 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
|
||||
<svg
|
||||
width="197.72693mm"
|
||||
height="190.30197mm"
|
||||
viewBox="0 0 197.72693 190.30197"
|
||||
version="1.1"
|
||||
id="svg5"
|
||||
xml:space="preserve"
|
||||
inkscape:version="1.2.2 (732a01da63, 2022-12-09)"
|
||||
sodipodi:docname="Toyhouse.svg"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"><sodipodi:namedview
|
||||
id="namedview7"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1.0"
|
||||
inkscape:showpageshadow="2"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pagecheckerboard="0"
|
||||
inkscape:deskcolor="#d1d1d1"
|
||||
inkscape:document-units="mm"
|
||||
showgrid="false"
|
||||
inkscape:zoom="0.5"
|
||||
inkscape:cx="969"
|
||||
inkscape:cy="450"
|
||||
inkscape:window-width="1920"
|
||||
inkscape:window-height="1009"
|
||||
inkscape:window-x="-8"
|
||||
inkscape:window-y="-8"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="layer1" /><defs
|
||||
id="defs2" /><g
|
||||
inkscape:label="Capa 1"
|
||||
inkscape:groupmode="layer"
|
||||
id="layer1"
|
||||
transform="translate(6.2923729,-54.858845)"><g
|
||||
id="g1824"><path
|
||||
style="fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.264583px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
d="M -6.2923729,154.22213 92.377447,54.858845 121.69053,84.093271 V 69.076617 h 31.14565 v 46.273543 l 38.59837,38.59837 h -32.36923 v 91.21228 H 25.02776 v -91.10105 z"
|
||||
id="path428"
|
||||
sodipodi:nodetypes="cccccccccccc" /><path
|
||||
style="fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.264583px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
d="m 54.338047,170.6337 v 8.39821 H 70.80075 v 53.94872 h 11.012215 v -54.00434 h 17.920872 c 0,0 2.72e-4,-0.23357 2.72e-4,2.12606 v 51.91208 h 8.966631 v -29.12162 h 26.03469 v 28.76768 h 9.28125 v -61.66526 h -8.96663 v 25.72008 h -26.42796 l 0.22247,-26.09986 z"
|
||||
id="path526"
|
||||
sodipodi:nodetypes="cccccccscccccccccccc" /></g></g></svg>
|
||||
|
After Width: | Height: | Size: 2.2 KiB |
|
After Width: | Height: | Size: 6.6 KiB |
4
mobile/apps/auth/assets/custom-icons/icons/twitter.svg
Normal file
@@ -0,0 +1,4 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xml:space="preserve" viewBox="0 0 248 204">
|
||||
<path fill="#1d9bf0" d="M221.95 51.29c.15 2.17.15 4.34.15 6.53 0 66.73-50.8 143.69-143.69 143.69v-.04c-27.44.04-54.31-7.82-77.41-22.64 3.99.48 8 .72 12.02.73 22.74.02 44.83-7.61 62.72-21.66-21.61-.41-40.56-14.5-47.18-35.07 7.57 1.46 15.37 1.16 22.8-.87-23.56-4.76-40.51-25.46-40.51-49.5v-.64c7.02 3.91 14.88 6.08 22.92 6.32C11.58 63.31 4.74 33.79 18.14 10.71c25.64 31.55 63.47 50.73 104.08 52.76-4.07-17.54 1.49-35.92 14.61-48.25 20.34-19.12 52.33-18.14 71.45 2.19 11.31-2.23 22.15-6.38 32.07-12.26-3.77 11.69-11.66 21.62-22.2 27.93 10.01-1.18 19.79-3.86 29-7.95-6.78 10.16-15.32 19.01-25.2 26.16z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 732 B |
@@ -1,7 +1 @@
|
||||
<?xml version="1.0" encoding="utf-8"?><!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
|
||||
<svg xmlns="http://www.w3.org/2000/svg"
|
||||
aria-label="Ubiquiti" role="img"
|
||||
viewBox="0 0 512 512"><rect
|
||||
width="512" height="512"
|
||||
rx="15"
|
||||
fill="#399cdb"/><path d="M112 94v18h18V94h-18zm288 0c-82 0-90 31-90 61v172a147 147 0 01-3 28c43-9 72-36 86-82l7-23V94zm-234 18v18h18v-18h-18zm-18 18v18h18v-18h-18zm36 9v18h18v-18h-18zm-72 4v147c0 73 53 128 144 128 0 0-54-30-54-91V197h-18v66h-18v-39h-18v17h-18v-98h-18zm54 18v18h18v-18h-18zm-18 27v18h18v-18h-18zm252 87c-19 64-65 92-131 89-24-1-43-7-57-16 10 42 46 63 48 64l10 6c82-5 130-59 130-128v-15z" fill="#ffffff"/></svg>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xml:space="preserve" viewBox="0 0 512 512"><path d="M494.2 0h-31.8v31.8h31.8zM383.1 222.4v-63.6h63.5v63.5h63.5c1.1 58.9-3.4 110.2-33.3 161.6-86.6 152.4-300.5 172.9-414 39.2C36.3 392.4 17.2 355 8.3 315c-4.5-21.7-6.5-49.2-6.5-72.5V4h127l.2 242c.6 31.3 6.3 63.5 25 88 53.9 73 167.9 66.3 212.1-13.1 15.9-26.6 17.3-68.7 17-98.5m15.8-174.8h47.6v47.6H510v63.5h-63.5V95.3h-47.6z" style="fill:#005ed9"/></svg>
|
||||
|
Before Width: | Height: | Size: 679 B After Width: | Height: | Size: 440 B |
21
mobile/apps/auth/assets/custom-icons/icons/zivver.svg
Normal file
@@ -0,0 +1,21 @@
|
||||
<?xml version="1.0" standalone="no"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 20010904//EN"
|
||||
"http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
|
||||
<svg version="1.0" xmlns="http://www.w3.org/2000/svg"
|
||||
width="240.000000pt" height="240.000000pt" viewBox="0 0 240.000000 240.000000"
|
||||
preserveAspectRatio="xMidYMid meet">
|
||||
|
||||
<g transform="translate(0.000000,240.000000) scale(0.100000,-0.100000)"
|
||||
fill="#000000" stroke="none">
|
||||
<path d="M765 2069 c-179 -12 -224 -23 -273 -65 -74 -63 -73 -55 -70 -619 l3
|
||||
-500 34 -70 c61 -125 213 -256 420 -362 110 -56 289 -126 321 -125 14 0 77 21
|
||||
140 47 313 126 514 275 597 442 l38 77 3 483 c2 341 0 495 -9 525 -7 28 -27
|
||||
59 -58 89 -56 56 -85 64 -309 79 -178 12 -648 11 -837 -1z m850 -403 c39 -1
|
||||
99 2 135 7 l65 7 -100 -34 c-95 -33 -103 -38 -167 -105 l-68 -71 0 -73 c0
|
||||
-100 -22 -138 -128 -222 -116 -93 -137 -142 -167 -390 -9 -71 -18 -141 -21
|
||||
-154 l-5 -24 -21 20 c-11 12 -27 45 -35 74 -21 80 -27 325 -10 434 9 55 11 97
|
||||
5 107 -4 10 -36 36 -69 58 -33 23 -92 71 -132 106 -55 51 -102 80 -202 129
|
||||
l-130 64 183 0 182 1 122 -45 c66 -24 136 -47 154 -51 31 -6 35 -3 63 42 71
|
||||
118 103 137 211 127 36 -3 97 -6 135 -7z"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.1 KiB |
BIN
mobile/apps/auth/assets/icons/auth-icon-monochrome.png
Normal file
|
After Width: | Height: | Size: 28 KiB |
@@ -87,9 +87,9 @@ PODS:
|
||||
- qr_code_scanner (0.2.0):
|
||||
- Flutter
|
||||
- MTBBarcodeScanner
|
||||
- SDWebImage (5.21.0):
|
||||
- SDWebImage/Core (= 5.21.0)
|
||||
- SDWebImage/Core (5.21.0)
|
||||
- SDWebImage (5.21.1):
|
||||
- SDWebImage/Core (= 5.21.1)
|
||||
- SDWebImage/Core (5.21.1)
|
||||
- Sentry/HybridSDK (8.46.0)
|
||||
- sentry_flutter (8.14.2):
|
||||
- Flutter
|
||||
@@ -258,7 +258,7 @@ SPEC CHECKSUMS:
|
||||
path_provider_foundation: 2b6b4c569c0fb62ec74538f866245ac84301af46
|
||||
privacy_screen: 1a131c052ceb3c3659934b003b0d397c2381a24e
|
||||
qr_code_scanner: bb67d64904c3b9658ada8c402e8b4d406d5d796e
|
||||
SDWebImage: f84b0feeb08d2d11e6a9b843cb06d75ebf5b8868
|
||||
SDWebImage: f29024626962457f3470184232766516dee8dfea
|
||||
Sentry: da60d980b197a46db0b35ea12cb8f39af48d8854
|
||||
sentry_flutter: 2df8b0aab7e4aba81261c230cbea31c82a62dd1b
|
||||
share_plus: 8875f4f2500512ea181eef553c3e27dba5135aad
|
||||
|
||||
@@ -199,9 +199,11 @@ class _AppState extends State<App>
|
||||
switch (menuItem.key) {
|
||||
case 'hide_window':
|
||||
windowManager.hide();
|
||||
windowManager.setSkipTaskbar(true);
|
||||
break;
|
||||
case 'show_window':
|
||||
windowManager.show();
|
||||
windowManager.setSkipTaskbar(false);
|
||||
break;
|
||||
case 'exit_app':
|
||||
windowManager.destroy();
|
||||
|
||||
@@ -40,8 +40,10 @@ Future<void> initSystemTray() async {
|
||||
if (PlatformUtil.isMobile()) return;
|
||||
String path = Platform.isWindows
|
||||
? 'assets/icons/auth-icon.ico'
|
||||
: 'assets/icons/auth-icon.png';
|
||||
await trayManager.setIcon(path);
|
||||
: Platform.isMacOS
|
||||
? 'assets/icons/auth-icon-monochrome.png'
|
||||
: 'assets/icons/auth-icon.png';
|
||||
await trayManager.setIcon(path, isTemplate: true);
|
||||
Menu menu = Menu(
|
||||
items: [
|
||||
MenuItem(
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
</screenshot>
|
||||
</screenshots>
|
||||
<releases>
|
||||
<release version="4.4.4" date="2025-08-09" />
|
||||
<release version="4.4.3" date="2025-06-21" />
|
||||
<release version="4.4.2" date="2025-06-21" />
|
||||
<release version="4.4.0" date="2025-05-31" />
|
||||
|
||||
@@ -157,33 +157,33 @@ EXTERNAL SOURCES:
|
||||
:path: Flutter/ephemeral/.symlinks/plugins/window_manager/macos
|
||||
|
||||
SPEC CHECKSUMS:
|
||||
app_links: 9028728e32c83a0831d9db8cf91c526d16cc5468
|
||||
connectivity_plus: 3f6c9057f4cd64198dc826edfb0542892f825343
|
||||
cupertino_http: 94ac07f5ff090b8effa6c5e2c47871d48ab7c86c
|
||||
device_info_plus: b0fafc687fb901e2af612763340f1b0d4352f8e5
|
||||
file_saver: e35bd97de451dde55ff8c38862ed7ad0f3418d0f
|
||||
flutter_inappwebview_macos: c2d68649f9f8f1831bfcd98d73fd6256366d9d1d
|
||||
flutter_local_authentication: 2f9a2682f498abcc12d7e9729b5007a947170fdc
|
||||
flutter_local_notifications: 453432cd6399a07d072885bc7828fb2307868856
|
||||
flutter_secure_storage_macos: b2d62a774c23b060f0b99d0173b0b36abb4a8632
|
||||
app_links: 10e0a0ab602ffaf34d142cd4862f29d34b303b2a
|
||||
connectivity_plus: ddd7f30999e1faaef5967c23d5b6d503d10434db
|
||||
cupertino_http: 947a233f40cfea55167a49f2facc18434ea117ba
|
||||
device_info_plus: 5401765fde0b8d062a2f8eb65510fb17e77cf07f
|
||||
file_saver: 44e6fbf666677faf097302460e214e977fdd977b
|
||||
flutter_inappwebview_macos: bdf207b8f4ebd58e86ae06cd96b147de99a67c9b
|
||||
flutter_local_authentication: 85674893931e1c9cfa7c9e4f5973cb8c56b018b0
|
||||
flutter_local_notifications: 3805ca215b2fb7f397d78b66db91f6a747af52e4
|
||||
flutter_secure_storage_macos: 59459653abe1adb92abbc8ea747d79f8d19866c9
|
||||
FlutterMacOS: 8f6f14fa908a6fb3fba0cd85dbd81ec4b251fb24
|
||||
local_auth_darwin: 553ce4f9b16d3fdfeafce9cf042e7c9f77c1c391
|
||||
objective_c: ec13431e45ba099cb734eb2829a5c1cd37986cba
|
||||
local_auth_darwin: 66e40372f1c29f383a314c738c7446e2f7fdadc3
|
||||
objective_c: e5f8194456e8fc943e034d1af00510a1bc29c067
|
||||
OrderedSet: e539b66b644ff081c73a262d24ad552a69be3a94
|
||||
package_info_plus: a8a591e70e87ce97ce5d21b2594f69cea9e0312f
|
||||
path_provider_foundation: 080d55be775b7414fd5a5ef3ac137b97b097e564
|
||||
screen_retriever: 4f97c103641aab8ce183fa5af3b87029df167936
|
||||
package_info_plus: fa739dd842b393193c5ca93c26798dff6e3d0e0c
|
||||
path_provider_foundation: 2b6b4c569c0fb62ec74538f866245ac84301af46
|
||||
screen_retriever: 59634572a57080243dd1bf715e55b6c54f241a38
|
||||
Sentry: da60d980b197a46db0b35ea12cb8f39af48d8854
|
||||
sentry_flutter: 27892878729f42701297c628eb90e7c6529f3684
|
||||
share_plus: 11c7b7fa7020465584eca3ff6392c5bc1e399d6e
|
||||
shared_preferences_foundation: 9e1978ff2562383bd5676f64ec4e9aa8fa06a6f7
|
||||
sodium_libs: b9459e5bfc1185349f43472e79fc5d8e526b2bda
|
||||
sqflite: c35dad70033b8862124f8337cc994a809fcd9fa3
|
||||
sentry_flutter: 2df8b0aab7e4aba81261c230cbea31c82a62dd1b
|
||||
share_plus: 36537c04ce0c3e3f5bd297ce4318b6d5ee5fd6cf
|
||||
shared_preferences_foundation: fcdcbc04712aee1108ac7fda236f363274528f78
|
||||
sodium_libs: d39bd76697736cb11ce4a0be73b9b4bc64466d6f
|
||||
sqflite: 673a0e54cc04b7d6dba8d24fb8095b31c3a99eec
|
||||
sqlite3: 0bb0e6389d824e40296f531b858a2a0b71c0d2fb
|
||||
sqlite3_flutter_libs: 03311aede9d32fb2d24e32bebb8cd01c3b2e6239
|
||||
tray_manager: a104b5c81b578d83f3c3d0f40a997c8b10810166
|
||||
url_launcher_macos: 0fba8ddabfc33ce0a9afe7c5fef5aab3d8d2d673
|
||||
window_manager: 1d01fa7ac65a6e6f83b965471b1a7fdd3f06166c
|
||||
sqlite3_flutter_libs: 5ca46c1a04eddfbeeb5b16566164aa7ad1616e7b
|
||||
tray_manager: 9064e219c56d75c476e46b9a21182087930baf90
|
||||
url_launcher_macos: c82c93949963e55b228a30115bd219499a6fe404
|
||||
window_manager: 3a1844359a6295ab1e47659b1a777e36773cd6e8
|
||||
|
||||
PODFILE CHECKSUM: 6ff827273ace187339fc5d3684072a26ad85c298
|
||||
|
||||
|
||||
@@ -11,6 +11,7 @@ class AppDelegate: FlutterAppDelegate {
|
||||
}
|
||||
|
||||
override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool {
|
||||
NSApp.setActivationPolicy(.accessory)
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
|
||||
name: ente_auth
|
||||
description: ente two-factor authenticator
|
||||
version: 4.4.3+443
|
||||
version: 4.4.4+448
|
||||
publish_to: none
|
||||
|
||||
environment:
|
||||
|
||||
@@ -10,12 +10,12 @@ commit](https://github.com/ente-io/ente/commit/a8cdc811fd20ca4289d8e779c97f08ef5
|
||||
|
||||
Hello world
|
||||
|
||||
To know more about Ente, see [our main README](../README.md) or visit
|
||||
To know more about Ente, see [our main README](../../../README.md) or visit
|
||||
[ente.io](https://ente.io).
|
||||
|
||||
To use Ente Photos on the web, see [../web](../web/README.md). To use Ente
|
||||
Photos on the desktop, see [../desktop](../desktop/README.md). There is a also a
|
||||
[CLI tool](../cli/README.md) for easy / automated exports.
|
||||
To use Ente Photos on the web, see [../../../web](../../../web/README.md). To use Ente
|
||||
Photos on the desktop, see [../../../desktop](../../../desktop/README.md). There is a also a
|
||||
[CLI tool](../../../cli/README.md) for easy / automated exports.
|
||||
|
||||
If you're looking for Ente Auth instead, see [../auth](../auth/README.md).
|
||||
|
||||
@@ -32,16 +32,16 @@ without relying on third party stores.
|
||||
You can alternatively install the build from PlayStore or F-Droid.
|
||||
|
||||
<a href="https://play.google.com/store/apps/details?id=io.ente.photos">
|
||||
<img height="59" src="../.github/assets/play-store-badge.png">
|
||||
<img height="59" src="../../../.github/assets/play-store-badge.png">
|
||||
</a>
|
||||
<a href="https://f-droid.org/packages/io.ente.photos.fdroid/">
|
||||
<img height="59" src="../.github/assets/f-droid-badge.png">
|
||||
<img height="59" src="../../../.github/assets/f-droid-badge.png">
|
||||
</a>
|
||||
|
||||
### iOS
|
||||
|
||||
<a href="https://apps.apple.com/in/app/ente-photos/id1542026904">
|
||||
<img height="59" src="../.github/assets/app-store-badge.svg">
|
||||
<img height="59" src="../../../.github/assets/app-store-badge.svg">
|
||||
</a>
|
||||
|
||||
## 🧑💻 Building from source
|
||||
@@ -99,4 +99,4 @@ apksigner verify --print-certs <path_to_apk>
|
||||
|
||||
## 💚 Contribute
|
||||
|
||||
For more ways to contribute, see [../CONTRIBUTING.md](../CONTRIBUTING.md).
|
||||
For more ways to contribute, see [../../../CONTRIBUTING.md](../../../CONTRIBUTING.md).
|
||||
|
||||
@@ -27,7 +27,7 @@ const subGalleryMultiplier = 10;
|
||||
// used to identify which ente file are available in app cache
|
||||
const String sharedMediaIdentifier = 'ente-shared-media://';
|
||||
|
||||
const galleryThumbnailDiskLoadDeferDuration = Duration(milliseconds: 500);
|
||||
const galleryThumbnailDiskLoadDeferDuration = Duration(milliseconds: 80);
|
||||
const galleryThumbnailServerLoadDeferDuration = Duration(milliseconds: 80);
|
||||
|
||||
// 256 bit key maps to 24 words
|
||||
|
||||
@@ -157,6 +157,23 @@ class UploadLocksDB {
|
||||
);
|
||||
}
|
||||
|
||||
Future<String> getLockData(String id) async {
|
||||
final db = await instance.database;
|
||||
final rows = await db.query(
|
||||
_uploadLocksTable.table,
|
||||
where: '${_uploadLocksTable.columnID} = ?',
|
||||
whereArgs: [id],
|
||||
);
|
||||
if (rows.isEmpty) {
|
||||
return "No lock found for $id";
|
||||
}
|
||||
final row = rows.first;
|
||||
final time = row[_uploadLocksTable.columnTime] as int;
|
||||
final owner = row[_uploadLocksTable.columnOwner] as String;
|
||||
final duration = DateTime.now().millisecondsSinceEpoch - time;
|
||||
return "Lock for $id acquired by $owner since ${Duration(milliseconds: duration)}";
|
||||
}
|
||||
|
||||
Future<bool> isLocked(String id, String owner) async {
|
||||
final db = await instance.database;
|
||||
final rows = await db.query(
|
||||
|
||||
@@ -61,6 +61,7 @@ const kBGTaskTimeout = Duration(seconds: 28);
|
||||
const kBGPushTimeout = Duration(seconds: 28);
|
||||
const kFGTaskDeathTimeoutInMicroseconds = 5000000;
|
||||
bool isProcessBg = true;
|
||||
bool _stopHearBeat = false;
|
||||
|
||||
void main() async {
|
||||
debugRepaintRainbowEnabled = false;
|
||||
@@ -291,6 +292,7 @@ Future<void> _init(bool isBackground, {String via = ''}) async {
|
||||
EnteWakeLockService.instance.init(preferences);
|
||||
logLocalSettings();
|
||||
initComplete = true;
|
||||
_stopHearBeat = true;
|
||||
_logger.info("Initialization done $tlog");
|
||||
} catch (e, s) {
|
||||
_logger.severe("Error in init ", e, s);
|
||||
@@ -299,25 +301,29 @@ Future<void> _init(bool isBackground, {String via = ''}) async {
|
||||
}
|
||||
|
||||
void logLocalSettings() {
|
||||
_logger.info("Show memories: ${memoriesCacheService.showAnyMemories}");
|
||||
_logger
|
||||
.info("Smart memories enabled: ${localSettings.isSmartMemoriesEnabled}");
|
||||
_logger.info("Ml is enabled: ${flagService.hasGrantedMLConsent}");
|
||||
_logger.info(
|
||||
"ML local indexing is enabled: ${localSettings.isMLLocalIndexingEnabled}",
|
||||
);
|
||||
_logger.info(
|
||||
"Multipart upload is enabled: ${localSettings.userEnabledMultiplePart}",
|
||||
);
|
||||
_logger.info("Gallery grid size: ${localSettings.getPhotoGridSize()}");
|
||||
_logger.info(
|
||||
"Video streaming is enalbed: ${VideoPreviewService.instance.isVideoStreamingEnabled}",
|
||||
);
|
||||
final settings = {
|
||||
'Show memories': memoriesCacheService.showAnyMemories,
|
||||
'Smart memories enabled': localSettings.isSmartMemoriesEnabled,
|
||||
'ML enabled': flagService.hasGrantedMLConsent,
|
||||
'ML local indexing enabled': localSettings.isMLLocalIndexingEnabled,
|
||||
'Multipart upload enabled': localSettings.userEnabledMultiplePart,
|
||||
'Gallery grid size': localSettings.getPhotoGridSize(),
|
||||
'Video streaming enabled':
|
||||
VideoPreviewService.instance.isVideoStreamingEnabled,
|
||||
};
|
||||
|
||||
final formattedSettings =
|
||||
settings.entries.map((e) => '${e.key}: ${e.value}').join(', ');
|
||||
_logger.info('Local settings - $formattedSettings');
|
||||
}
|
||||
|
||||
void _heartBeatOnInit(int i) {
|
||||
if (i <= 15) {
|
||||
if (i <= 15 && !_stopHearBeat) {
|
||||
Future.delayed(const Duration(seconds: 1), () {
|
||||
if (_stopHearBeat) {
|
||||
_logger.info("Stopping Heartbeat check at $i");
|
||||
return;
|
||||
}
|
||||
_logger.info("init Heartbeat $i");
|
||||
_heartBeatOnInit(i + 1);
|
||||
});
|
||||
|
||||
@@ -224,18 +224,20 @@ class GalleryGroups {
|
||||
int i = 0;
|
||||
while (!endOfListReached) {
|
||||
gridRowChildren.add(
|
||||
GalleryFileWidget(
|
||||
RepaintBoundary(
|
||||
key: ValueKey(
|
||||
tagPrefix +
|
||||
filesInGroup[firstIndexOfRowWrtFilesInGroup + i]
|
||||
.tag,
|
||||
),
|
||||
file: filesInGroup[firstIndexOfRowWrtFilesInGroup + i],
|
||||
selectedFiles: selectedFiles,
|
||||
limitSelectionToOne: limitSelectionToOne,
|
||||
tag: tagPrefix,
|
||||
photoGridSize: crossAxisCount,
|
||||
currentUserID: currentUserID,
|
||||
child: GalleryFileWidget(
|
||||
file: filesInGroup[firstIndexOfRowWrtFilesInGroup + i],
|
||||
selectedFiles: selectedFiles,
|
||||
limitSelectionToOne: limitSelectionToOne,
|
||||
tag: tagPrefix,
|
||||
photoGridSize: crossAxisCount,
|
||||
currentUserID: currentUserID,
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
@@ -247,18 +249,20 @@ class GalleryGroups {
|
||||
} else {
|
||||
for (int i = 0; i < crossAxisCount; i++) {
|
||||
gridRowChildren.add(
|
||||
GalleryFileWidget(
|
||||
RepaintBoundary(
|
||||
key: ValueKey(
|
||||
tagPrefix +
|
||||
filesInGroup[firstIndexOfRowWrtFilesInGroup + i]
|
||||
.tag,
|
||||
),
|
||||
file: filesInGroup[firstIndexOfRowWrtFilesInGroup + i],
|
||||
selectedFiles: selectedFiles,
|
||||
limitSelectionToOne: limitSelectionToOne,
|
||||
tag: tagPrefix,
|
||||
photoGridSize: crossAxisCount,
|
||||
currentUserID: currentUserID,
|
||||
child: GalleryFileWidget(
|
||||
file: filesInGroup[firstIndexOfRowWrtFilesInGroup + i],
|
||||
selectedFiles: selectedFiles,
|
||||
limitSelectionToOne: limitSelectionToOne,
|
||||
tag: tagPrefix,
|
||||
photoGridSize: crossAxisCount,
|
||||
currentUserID: currentUserID,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -151,13 +151,7 @@ class _ThumbnailWidgetState extends State<ThumbnailWidget> {
|
||||
Widget? image;
|
||||
if (_imageProvider != null) {
|
||||
image = Image(
|
||||
image: optimizedImageHeight != null || optimizedImageWidth != null
|
||||
? ResizeImage(
|
||||
_imageProvider!,
|
||||
width: optimizedImageWidth,
|
||||
height: optimizedImageHeight,
|
||||
)
|
||||
: _imageProvider!,
|
||||
image: _imageProvider!,
|
||||
fit: widget.fit,
|
||||
);
|
||||
}
|
||||
@@ -251,7 +245,11 @@ class _ThumbnailWidgetState extends State<ThumbnailWidget> {
|
||||
final cachedSmallThumbnail =
|
||||
ThumbnailInMemoryLruCache.get(widget.file, thumbnailSmallSize);
|
||||
if (cachedSmallThumbnail != null) {
|
||||
_imageProvider = Image.memory(cachedSmallThumbnail).image;
|
||||
_imageProvider = Image.memory(
|
||||
cachedSmallThumbnail,
|
||||
cacheHeight: optimizedImageHeight,
|
||||
cacheWidth: optimizedImageWidth,
|
||||
).image;
|
||||
_hasLoadedThumbnail = true;
|
||||
} else {
|
||||
if (widget.diskLoadDeferDuration != null) {
|
||||
@@ -296,7 +294,11 @@ class _ThumbnailWidgetState extends State<ThumbnailWidget> {
|
||||
}
|
||||
|
||||
if (mounted) {
|
||||
final imageProvider = Image.memory(thumbData).image;
|
||||
final imageProvider = Image.memory(
|
||||
thumbData,
|
||||
cacheHeight: optimizedImageHeight,
|
||||
cacheWidth: optimizedImageWidth,
|
||||
).image;
|
||||
_cacheAndRender(imageProvider);
|
||||
}
|
||||
ThumbnailInMemoryLruCache.put(
|
||||
@@ -381,10 +383,15 @@ class _ThumbnailWidgetState extends State<ThumbnailWidget> {
|
||||
_isLoadingRemoteThumbnail = true;
|
||||
final cachedThumbnail = ThumbnailInMemoryLruCache.get(widget.file);
|
||||
if (cachedThumbnail != null) {
|
||||
_imageProvider = Image.memory(cachedThumbnail).image;
|
||||
_imageProvider = Image.memory(
|
||||
cachedThumbnail,
|
||||
cacheHeight: optimizedImageHeight,
|
||||
cacheWidth: optimizedImageWidth,
|
||||
).image;
|
||||
_hasLoadedThumbnail = true;
|
||||
return;
|
||||
}
|
||||
|
||||
if (widget.serverLoadDeferDuration != null) {
|
||||
Future.delayed(widget.serverLoadDeferDuration!, () {
|
||||
if (mounted) {
|
||||
@@ -401,7 +408,11 @@ class _ThumbnailWidgetState extends State<ThumbnailWidget> {
|
||||
try {
|
||||
final thumbnail = await getThumbnailFromServer(widget.file);
|
||||
if (mounted) {
|
||||
final imageProvider = Image.memory(thumbnail).image;
|
||||
final imageProvider = Image.memory(
|
||||
thumbnail,
|
||||
cacheHeight: optimizedImageHeight,
|
||||
cacheWidth: optimizedImageWidth,
|
||||
).image;
|
||||
_cacheAndRender(imageProvider);
|
||||
}
|
||||
} catch (e) {
|
||||
|
||||
@@ -42,12 +42,6 @@ class SectionedListSliver<T> extends StatelessWidget {
|
||||
sectionLayouts: sectionLayouts,
|
||||
delegate: SliverChildBuilderDelegate(
|
||||
(context, index) {
|
||||
//TODO:
|
||||
// This could be optimized by using a combination of
|
||||
//linear search and binary search depending on the index (use linear
|
||||
//if index is small) or keep track on lastIndex of section and
|
||||
//go to next section after the last index.
|
||||
// Check if the optimization is required.
|
||||
if (index >= childCount) return null;
|
||||
final sectionLayout = sectionLayouts
|
||||
.firstWhereOrNull((section) => section.hasChild(index));
|
||||
|
||||
@@ -600,6 +600,7 @@ class GalleryState extends State<Gallery> {
|
||||
? const NeverScrollableScrollPhysics()
|
||||
: const ExponentialBouncingScrollPhysics(),
|
||||
controller: _scrollController,
|
||||
cacheExtent: galleryCacheExtent,
|
||||
slivers: [
|
||||
SliverToBoxAdapter(
|
||||
child: SizeChangedLayoutNotifier(
|
||||
@@ -637,6 +638,25 @@ class GalleryState extends State<Gallery> {
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
double get galleryCacheExtent {
|
||||
final int photoGridSize = localSettings.getPhotoGridSize();
|
||||
switch (photoGridSize) {
|
||||
case 2:
|
||||
case 3:
|
||||
return 1000;
|
||||
case 4:
|
||||
return 850;
|
||||
case 5:
|
||||
return 600;
|
||||
case 6:
|
||||
return 300;
|
||||
default:
|
||||
throw StateError(
|
||||
'Invalid photo grid size configuration: $photoGridSize',
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class PinnedGroupHeader extends StatefulWidget {
|
||||
|
||||
@@ -519,7 +519,8 @@ class FileUploader {
|
||||
DateTime.now().microsecondsSinceEpoch,
|
||||
);
|
||||
} catch (e) {
|
||||
_logger.warning("Lock was already taken for " + file.toString());
|
||||
final lockInfo = await _uploadLocks.getLockData(lockKey);
|
||||
_logger.warning("Lock was already taken ($lockInfo) for " + file.tag);
|
||||
throw LockAlreadyAcquiredError();
|
||||
}
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ description: ente photos application
|
||||
# Read more about iOS versioning at
|
||||
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
|
||||
|
||||
version: 1.2.0+1200
|
||||
version: 1.2.0+1203
|
||||
publish_to: none
|
||||
|
||||
environment:
|
||||
|
||||
@@ -5,6 +5,9 @@ import (
|
||||
"database/sql"
|
||||
b64 "encoding/base64"
|
||||
"fmt"
|
||||
"github.com/ente-io/museum/pkg/controller/collections"
|
||||
publicCtrl "github.com/ente-io/museum/pkg/controller/public"
|
||||
"github.com/ente-io/museum/pkg/repo/public"
|
||||
"net/http"
|
||||
"os"
|
||||
"os/signal"
|
||||
@@ -14,8 +17,6 @@ import (
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"github.com/ente-io/museum/pkg/controller/collections"
|
||||
|
||||
"github.com/ente-io/museum/ente/base"
|
||||
"github.com/ente-io/museum/pkg/controller/emergency"
|
||||
"github.com/ente-io/museum/pkg/controller/file_copy"
|
||||
@@ -97,6 +98,7 @@ func main() {
|
||||
}
|
||||
|
||||
viper.SetDefault("apps.public-albums", "https://albums.ente.io")
|
||||
viper.SetDefault("apps.public-locker", "https://locker.ente.io")
|
||||
viper.SetDefault("apps.accounts", "https://accounts.ente.io")
|
||||
viper.SetDefault("apps.cast", "https://cast.ente.io")
|
||||
viper.SetDefault("apps.family", "https://family.ente.io")
|
||||
@@ -174,11 +176,13 @@ func main() {
|
||||
fileRepo := &repo.FileRepository{DB: db, S3Config: s3Config, QueueRepo: queueRepo,
|
||||
ObjectRepo: objectRepo, ObjectCleanupRepo: objectCleanupRepo,
|
||||
ObjectCopiesRepo: objectCopiesRepo, UsageRepo: usageRepo}
|
||||
fileLinkRepo := public.NewFileLinkRepo(db)
|
||||
fileDataRepo := &fileDataRepo.Repository{DB: db, ObjectCleanupRepo: objectCleanupRepo}
|
||||
familyRepo := &repo.FamilyRepository{DB: db}
|
||||
trashRepo := &repo.TrashRepository{DB: db, ObjectRepo: objectRepo, FileRepo: fileRepo, QueueRepo: queueRepo}
|
||||
publicCollectionRepo := repo.NewPublicCollectionRepository(db, viper.GetString("apps.public-albums"))
|
||||
collectionRepo := &repo.CollectionRepository{DB: db, FileRepo: fileRepo, PublicCollectionRepo: publicCollectionRepo,
|
||||
trashRepo := &repo.TrashRepository{DB: db, ObjectRepo: objectRepo, FileRepo: fileRepo, QueueRepo: queueRepo, FileLinkRepo: fileLinkRepo}
|
||||
collectionLinkRepo := public.NewCollectionLinkRepository(db, viper.GetString("apps.public-albums"))
|
||||
|
||||
collectionRepo := &repo.CollectionRepository{DB: db, FileRepo: fileRepo, CollectionLinkRepo: collectionLinkRepo,
|
||||
TrashRepo: trashRepo, SecretEncryptionKey: secretEncryptionKeyBytes, QueueRepo: queueRepo, LatencyLogger: latencyLogger}
|
||||
pushRepo := &repo.PushTokenRepository{DB: db}
|
||||
kexRepo := &kex.Repository{
|
||||
@@ -300,26 +304,27 @@ func main() {
|
||||
UsageRepo: usageRepo,
|
||||
}
|
||||
|
||||
publicCollectionCtrl := &controller.PublicCollectionController{
|
||||
collectionLinkCtrl := &publicCtrl.CollectionLinkController{
|
||||
FileController: fileController,
|
||||
EmailNotificationCtrl: emailNotificationCtrl,
|
||||
PublicCollectionRepo: publicCollectionRepo,
|
||||
CollectionLinkRepo: collectionLinkRepo,
|
||||
FileLinkRepo: fileLinkRepo,
|
||||
CollectionRepo: collectionRepo,
|
||||
UserRepo: userRepo,
|
||||
JwtSecret: jwtSecretBytes,
|
||||
}
|
||||
|
||||
collectionController := &collections.CollectionController{
|
||||
CollectionRepo: collectionRepo,
|
||||
EmailCtrl: emailNotificationCtrl,
|
||||
AccessCtrl: accessCtrl,
|
||||
PublicCollectionCtrl: publicCollectionCtrl,
|
||||
UserRepo: userRepo,
|
||||
FileRepo: fileRepo,
|
||||
CastRepo: &castDb,
|
||||
BillingCtrl: billingController,
|
||||
QueueRepo: queueRepo,
|
||||
TaskRepo: taskLockingRepo,
|
||||
CollectionRepo: collectionRepo,
|
||||
EmailCtrl: emailNotificationCtrl,
|
||||
AccessCtrl: accessCtrl,
|
||||
CollectionLinkCtrl: collectionLinkCtrl,
|
||||
UserRepo: userRepo,
|
||||
FileRepo: fileRepo,
|
||||
CastRepo: &castDb,
|
||||
BillingCtrl: billingController,
|
||||
QueueRepo: queueRepo,
|
||||
TaskRepo: taskLockingRepo,
|
||||
}
|
||||
|
||||
kexCtrl := &kexCtrl.Controller{
|
||||
@@ -351,6 +356,12 @@ func main() {
|
||||
userCache,
|
||||
userCacheCtrl,
|
||||
)
|
||||
fileLinkCtrl := &publicCtrl.FileLinkController{
|
||||
FileController: fileController,
|
||||
FileLinkRepo: fileLinkRepo,
|
||||
FileRepo: fileRepo,
|
||||
JwtSecret: jwtSecretBytes,
|
||||
}
|
||||
|
||||
passkeyCtrl := &controller.PasskeyController{
|
||||
Repo: passkeysRepo,
|
||||
@@ -358,14 +369,21 @@ func main() {
|
||||
}
|
||||
|
||||
authMiddleware := middleware.AuthMiddleware{UserAuthRepo: userAuthRepo, Cache: authCache, UserController: userController}
|
||||
accessTokenMiddleware := middleware.AccessTokenMiddleware{
|
||||
PublicCollectionRepo: publicCollectionRepo,
|
||||
PublicCollectionCtrl: publicCollectionCtrl,
|
||||
collectionLinkMiddleware := middleware.CollectionLinkMiddleware{
|
||||
CollectionLinkRepo: collectionLinkRepo,
|
||||
PublicCollectionCtrl: collectionLinkCtrl,
|
||||
CollectionRepo: collectionRepo,
|
||||
Cache: accessTokenCache,
|
||||
BillingCtrl: billingController,
|
||||
DiscordController: discordController,
|
||||
}
|
||||
fileLinkMiddleware := &middleware.FileLinkMiddleware{
|
||||
FileLinkRepo: fileLinkRepo,
|
||||
FileLinkCtrl: fileLinkCtrl,
|
||||
Cache: accessTokenCache,
|
||||
BillingCtrl: billingController,
|
||||
DiscordController: discordController,
|
||||
}
|
||||
|
||||
if environment != "local" {
|
||||
gin.SetMode(gin.ReleaseMode)
|
||||
@@ -404,7 +422,9 @@ func main() {
|
||||
familiesJwtAuthAPI.Use(rateLimiter.GlobalRateLimiter(), authMiddleware.TokenAuthMiddleware(jwt.FAMILIES.Ptr()), rateLimiter.APIRateLimitForUserMiddleware(urlSanitizer))
|
||||
|
||||
publicCollectionAPI := server.Group("/public-collection")
|
||||
publicCollectionAPI.Use(rateLimiter.GlobalRateLimiter(), accessTokenMiddleware.AccessTokenAuthMiddleware(urlSanitizer))
|
||||
publicCollectionAPI.Use(rateLimiter.GlobalRateLimiter(), collectionLinkMiddleware.Authenticate(urlSanitizer))
|
||||
fileLinkApi := server.GET("/file-link")
|
||||
fileLinkApi.Use(rateLimiter.GlobalRateLimiter(), fileLinkMiddleware.Authenticate(urlSanitizer))
|
||||
|
||||
healthCheckHandler := &api.HealthCheckHandler{
|
||||
DB: db,
|
||||
@@ -432,6 +452,7 @@ func main() {
|
||||
Controller: fileController,
|
||||
FileCopyCtrl: fileCopyCtrl,
|
||||
FileDataCtrl: fileDataCtrl,
|
||||
FileUrlCtrl: fileLinkCtrl,
|
||||
}
|
||||
privateAPI.GET("/files/upload-urls", fileHandler.GetUploadURLs)
|
||||
privateAPI.GET("/files/multipart-upload-urls", fileHandler.GetMultipartUploadURLs)
|
||||
@@ -440,6 +461,11 @@ func main() {
|
||||
privateAPI.GET("/files/preview/:fileID", fileHandler.GetThumbnail)
|
||||
privateAPI.GET("/files/preview/v2/:fileID", fileHandler.GetThumbnail)
|
||||
|
||||
privateAPI.POST("/files/share-url", fileHandler.ShareUrl)
|
||||
privateAPI.PUT("/files/share-url", fileHandler.UpdateFileURL)
|
||||
privateAPI.DELETE("/files/share-url/:fileID", fileHandler.DisableUrl)
|
||||
privateAPI.GET("/files/share-urls/", fileHandler.GetUrls)
|
||||
|
||||
privateAPI.PUT("/files/data", fileHandler.PutFileData)
|
||||
privateAPI.PUT("/files/video-data", fileHandler.PutVideoData)
|
||||
privateAPI.POST("/files/data/status-diff", fileHandler.FileDataStatusDiff)
|
||||
@@ -566,13 +592,19 @@ func main() {
|
||||
privateAPI.PUT("/collections/sharee-magic-metadata", collectionHandler.ShareeMagicMetadataUpdate)
|
||||
|
||||
publicCollectionHandler := &api.PublicCollectionHandler{
|
||||
Controller: publicCollectionCtrl,
|
||||
Controller: collectionLinkCtrl,
|
||||
FileCtrl: fileController,
|
||||
CollectionCtrl: collectionController,
|
||||
FileDataCtrl: fileDataCtrl,
|
||||
StorageBonusController: storageBonusCtrl,
|
||||
}
|
||||
|
||||
fileLinkApi.GET("/info", fileHandler.LinkInfo)
|
||||
fileLinkApi.GET("/pass-info", fileHandler.PasswordInfo)
|
||||
fileLinkApi.GET("/thumbnail", fileHandler.LinkThumbnail)
|
||||
fileLinkApi.GET("/file", fileHandler.LinkFile)
|
||||
fileLinkApi.POST("/verify-password", fileHandler.VerifyPassword)
|
||||
|
||||
publicCollectionAPI.GET("/files/preview/:fileID", publicCollectionHandler.GetThumbnail)
|
||||
publicCollectionAPI.GET("/files/download/:fileID", publicCollectionHandler.GetFile)
|
||||
publicCollectionAPI.GET("/files/data/fetch", publicCollectionHandler.GetFileData)
|
||||
@@ -770,7 +802,7 @@ func main() {
|
||||
setKnownAPIs(server.Routes())
|
||||
setupAndStartBackgroundJobs(objectCleanupController, replicationController3, fileDataCtrl)
|
||||
setupAndStartCrons(
|
||||
userAuthRepo, publicCollectionRepo, twoFactorRepo, passkeysRepo, fileController, taskLockingRepo, emailNotificationCtrl,
|
||||
userAuthRepo, collectionLinkRepo, fileLinkRepo, twoFactorRepo, passkeysRepo, fileController, taskLockingRepo, emailNotificationCtrl,
|
||||
trashController, pushController, objectController, dataCleanupController, storageBonusCtrl, emergencyCtrl,
|
||||
embeddingController, healthCheckHandler, kexCtrl, castDb)
|
||||
|
||||
@@ -899,7 +931,8 @@ func setupAndStartBackgroundJobs(
|
||||
objectCleanupController.StartClearingOrphanObjects()
|
||||
}
|
||||
|
||||
func setupAndStartCrons(userAuthRepo *repo.UserAuthRepository, publicCollectionRepo *repo.PublicCollectionRepository,
|
||||
func setupAndStartCrons(userAuthRepo *repo.UserAuthRepository, collectionLinkRepo *public.CollectionLinkRepo,
|
||||
fileLinkRepo *public.FileLinkRepository,
|
||||
twoFactorRepo *repo.TwoFactorRepository, passkeysRepo *passkey.Repository, fileController *controller.FileController,
|
||||
taskRepo *repo.TaskLockRepository, emailNotificationCtrl *email.EmailNotificationController,
|
||||
trashController *controller.TrashController, pushController *controller.PushController,
|
||||
@@ -925,7 +958,8 @@ func setupAndStartCrons(userAuthRepo *repo.UserAuthRepository, publicCollectionR
|
||||
schedule(c, "@every 24h", func() {
|
||||
_ = userAuthRepo.RemoveDeletedTokens(timeUtil.MicrosecondsBeforeDays(30))
|
||||
_ = castDb.DeleteOldSessions(context.Background(), timeUtil.MicrosecondsBeforeDays(7))
|
||||
_ = publicCollectionRepo.CleanupAccessHistory(context.Background())
|
||||
_ = collectionLinkRepo.CleanupAccessHistory(context.Background())
|
||||
_ = fileLinkRepo.CleanupAccessHistory(context.Background())
|
||||
})
|
||||
|
||||
schedule(c, "@every 1m", func() {
|
||||
|
||||
@@ -79,9 +79,14 @@ http:
|
||||
apps:
|
||||
# Default is https://albums.ente.io
|
||||
#
|
||||
# If you're running a self hosted instance and wish to serve public links,
|
||||
# If you're running a self hosted instance and wish to serve public links for photos,
|
||||
# set this to the URL where your albums web app is running.
|
||||
public-albums:
|
||||
# Default is https://locker.ente.io
|
||||
#
|
||||
# If you're running a self-hosted instance and wish to serve public links for locker,
|
||||
# set this to the URL where your albums web app is running.
|
||||
public-locker:
|
||||
# Default is https://cast.ente.io
|
||||
cast:
|
||||
# Default is https://accounts.ente.io
|
||||
|
||||
@@ -97,8 +97,8 @@ var ErrUserDeleted = errors.New("user account has been deleted")
|
||||
// ErrLockUnavailable is thrown when a lock could not be acquired
|
||||
var ErrLockUnavailable = errors.New("could not acquire lock")
|
||||
|
||||
// ErrActiveLinkAlreadyExists is thrown when the collection already has active public link
|
||||
var ErrActiveLinkAlreadyExists = errors.New("Collection already has active public link")
|
||||
// ErrActiveLinkAlreadyExists is thrown when an active link already exists for entity
|
||||
var ErrActiveLinkAlreadyExists = errors.New("link already exists for this entity")
|
||||
|
||||
// ErrNotImplemented indicates that the action that we tried to perform is not
|
||||
// available at this museum instance. e.g. this could be something that is not
|
||||
@@ -176,6 +176,11 @@ var ErrMaxPasskeysReached = ApiError{
|
||||
Message: "Max passkeys limit reached",
|
||||
HttpStatusCode: http.StatusConflict,
|
||||
}
|
||||
var ErrPassProtectedResource = ApiError{
|
||||
Code: "PASS_PROTECTED_RESOURCE",
|
||||
Message: "This resource is password protected",
|
||||
HttpStatusCode: http.StatusForbidden,
|
||||
}
|
||||
|
||||
var ErrCastPermissionDenied = ApiError{
|
||||
Code: "CAST_PERMISSION_DENIED",
|
||||
|
||||
94
server/ente/file_link.go
Normal file
@@ -0,0 +1,94 @@
|
||||
package ente
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/ente-io/museum/pkg/utils/time"
|
||||
)
|
||||
|
||||
// CreateFileUrl represents an encrypted file in the system
|
||||
type CreateFileUrl struct {
|
||||
FileID int64 `json:"fileID" binding:"required"`
|
||||
App App `json:"app" binding:"required"`
|
||||
}
|
||||
|
||||
// UpdateFileUrl ..
|
||||
type UpdateFileUrl struct {
|
||||
LinkID string `json:"linkID" binding:"required"`
|
||||
FileID int64 `json:"fileID" binding:"required"`
|
||||
ValidTill *int64 `json:"validTill"`
|
||||
DeviceLimit *int `json:"deviceLimit"`
|
||||
PassHash *string
|
||||
Nonce *string
|
||||
MemLimit *int64
|
||||
OpsLimit *int64
|
||||
EnableDownload *bool `json:"enableDownload"`
|
||||
DisablePassword *bool `json:"disablePassword"`
|
||||
}
|
||||
|
||||
func (ut *UpdateFileUrl) Validate() error {
|
||||
if ut.DeviceLimit == nil && ut.ValidTill == nil && ut.DisablePassword == nil &&
|
||||
ut.Nonce == nil && ut.PassHash == nil && ut.EnableDownload == nil {
|
||||
return NewBadRequestWithMessage("all parameters are missing")
|
||||
}
|
||||
|
||||
if ut.DeviceLimit != nil && (*ut.DeviceLimit < 0 || *ut.DeviceLimit > 50) {
|
||||
return NewBadRequestWithMessage(fmt.Sprintf("device limit: %d out of range [0-50]", *ut.DeviceLimit))
|
||||
}
|
||||
|
||||
if ut.ValidTill != nil && *ut.ValidTill != 0 && *ut.ValidTill < time.Microseconds() {
|
||||
return NewBadRequestWithMessage("valid till should be greater than current timestamp")
|
||||
}
|
||||
|
||||
var allPassParamsMissing = ut.Nonce == nil && ut.PassHash == nil && ut.MemLimit == nil && ut.OpsLimit == nil
|
||||
var allPassParamsPresent = ut.Nonce != nil && ut.PassHash != nil && ut.MemLimit != nil && ut.OpsLimit != nil
|
||||
|
||||
if !(allPassParamsMissing || allPassParamsPresent) {
|
||||
return NewBadRequestWithMessage("all password params should be either present or missing")
|
||||
}
|
||||
|
||||
if allPassParamsPresent && ut.DisablePassword != nil && *ut.DisablePassword {
|
||||
return NewBadRequestWithMessage("can not set and disable password in same request")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type FileLinkRow struct {
|
||||
LinkID string
|
||||
OwnerID int64
|
||||
FileID int64
|
||||
Token string
|
||||
DeviceLimit int
|
||||
ValidTill int64
|
||||
IsDisabled bool
|
||||
PassHash *string
|
||||
Nonce *string
|
||||
MemLimit *int64
|
||||
OpsLimit *int64
|
||||
EnableDownload bool
|
||||
CreatedAt int64
|
||||
UpdatedAt int64
|
||||
}
|
||||
|
||||
type FileUrl struct {
|
||||
LinkID string `json:"linkID" binding:"required"`
|
||||
URL string `json:"url" binding:"required"`
|
||||
OwnerID int64 `json:"ownerID" binding:"required"`
|
||||
FileID int64 `json:"fileID" binding:"required"`
|
||||
ValidTill int64 `json:"validTill"`
|
||||
DeviceLimit int `json:"deviceLimit"`
|
||||
PasswordEnabled bool `json:"passwordEnabled"`
|
||||
// Nonce contains the nonce value for the password if the link is password protected.
|
||||
Nonce *string `json:"nonce,omitempty"`
|
||||
MemLimit *int64 `json:"memLimit,omitempty"`
|
||||
OpsLimit *int64 `json:"opsLimit,omitempty"`
|
||||
EnableDownload bool `json:"enableDownload"`
|
||||
CreatedAt int64 `json:"createdAt"`
|
||||
}
|
||||
|
||||
type FileLinkAccessContext struct {
|
||||
LinkID string
|
||||
IP string
|
||||
UserAgent string
|
||||
FileID int64
|
||||
OwnerID int64
|
||||
}
|
||||
@@ -40,13 +40,13 @@ func (w WebCommonJWTClaim) Valid() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// PublicAlbumPasswordClaim refer to token granted post public album password verification
|
||||
type PublicAlbumPasswordClaim struct {
|
||||
// LinkPasswordClaim refer to token granted post link password verification
|
||||
type LinkPasswordClaim struct {
|
||||
PassHash string `json:"passKey"`
|
||||
ExpiryTime int64 `json:"expiryTime"`
|
||||
}
|
||||
|
||||
func (c PublicAlbumPasswordClaim) Valid() error {
|
||||
func (c LinkPasswordClaim) Valid() error {
|
||||
if c.ExpiryTime < time.Microseconds() {
|
||||
return errors.New("token expired")
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@ package ente
|
||||
import (
|
||||
"database/sql/driver"
|
||||
"encoding/json"
|
||||
|
||||
"fmt"
|
||||
"github.com/ente-io/museum/pkg/utils/time"
|
||||
"github.com/ente-io/stacktrace"
|
||||
)
|
||||
@@ -32,6 +32,33 @@ type UpdatePublicAccessTokenRequest struct {
|
||||
EnableJoin *bool `json:"enableJoin"`
|
||||
}
|
||||
|
||||
func (ut *UpdatePublicAccessTokenRequest) Validate() error {
|
||||
if ut.DeviceLimit == nil && ut.ValidTill == nil && ut.DisablePassword == nil &&
|
||||
ut.Nonce == nil && ut.PassHash == nil && ut.EnableDownload == nil && ut.EnableCollect == nil {
|
||||
return NewBadRequestWithMessage("all parameters are missing")
|
||||
}
|
||||
|
||||
if ut.DeviceLimit != nil && (*ut.DeviceLimit < 0 || *ut.DeviceLimit > 50) {
|
||||
return NewBadRequestWithMessage(fmt.Sprintf("device limit: %d out of range [0-50]", *ut.DeviceLimit))
|
||||
}
|
||||
|
||||
if ut.ValidTill != nil && *ut.ValidTill != 0 && *ut.ValidTill < time.Microseconds() {
|
||||
return NewBadRequestWithMessage("valid till should be greater than current timestamp")
|
||||
}
|
||||
|
||||
var allPassParamsMissing = ut.Nonce == nil && ut.PassHash == nil && ut.MemLimit == nil && ut.OpsLimit == nil
|
||||
var allPassParamsPresent = ut.Nonce != nil && ut.PassHash != nil && ut.MemLimit != nil && ut.OpsLimit != nil
|
||||
|
||||
if !(allPassParamsMissing || allPassParamsPresent) {
|
||||
return NewBadRequestWithMessage("all password params should be either present or missing")
|
||||
}
|
||||
|
||||
if allPassParamsPresent && ut.DisablePassword != nil && *ut.DisablePassword {
|
||||
return NewBadRequestWithMessage("can not set and disable password in same request")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type VerifyPasswordRequest struct {
|
||||
PassHash string `json:"passHash" binding:"required"`
|
||||
}
|
||||
@@ -40,8 +67,8 @@ type VerifyPasswordResponse struct {
|
||||
JWTToken string `json:"jwtToken"`
|
||||
}
|
||||
|
||||
// PublicCollectionToken represents row entity for public_collection_token table
|
||||
type PublicCollectionToken struct {
|
||||
// CollectionLinkRow represents row entity for public_collection_token table
|
||||
type CollectionLinkRow struct {
|
||||
ID int64
|
||||
CollectionID int64
|
||||
Token string
|
||||
@@ -57,7 +84,7 @@ type PublicCollectionToken struct {
|
||||
EnableJoin bool
|
||||
}
|
||||
|
||||
func (p PublicCollectionToken) CanJoin() error {
|
||||
func (p CollectionLinkRow) CanJoin() error {
|
||||
if p.IsDisabled {
|
||||
return NewBadRequestWithMessage("link disabled")
|
||||
}
|
||||
|
||||
3
server/migrations/103_single_file_url.down.sql
Normal file
@@ -0,0 +1,3 @@
|
||||
|
||||
DROP TABLE IF EXISTS public_file_tokens_access_history;
|
||||
DROP TABLE IF EXISTS public_file_tokens;
|
||||
46
server/migrations/103_single_file_url.up.sql
Normal file
@@ -0,0 +1,46 @@
|
||||
|
||||
|
||||
CREATE TABLE IF NOT EXISTS public_file_tokens
|
||||
(
|
||||
id text primary key,
|
||||
file_id bigint NOT NULL,
|
||||
owner_id bigint NOT NULL,
|
||||
app text NOT NULL,
|
||||
access_token text not null,
|
||||
valid_till bigint not null DEFAULT 0,
|
||||
device_limit int not null DEFAULT 0,
|
||||
is_disabled bool not null DEFAULT FALSE,
|
||||
enable_download bool not null DEFAULT TRUE,
|
||||
pw_hash TEXT,
|
||||
pw_nonce TEXT,
|
||||
mem_limit BIGINT,
|
||||
ops_limit BIGINT,
|
||||
created_at bigint NOT NULL DEFAULT now_utc_micro_seconds(),
|
||||
updated_at bigint NOT NULL DEFAULT now_utc_micro_seconds()
|
||||
);
|
||||
|
||||
|
||||
CREATE OR REPLACE TRIGGER update_public_file_tokens_updated_at
|
||||
BEFORE UPDATE
|
||||
ON public_file_tokens
|
||||
FOR EACH ROW
|
||||
EXECUTE PROCEDURE
|
||||
trigger_updated_at_microseconds_column();
|
||||
|
||||
|
||||
CREATE TABLE IF NOT EXISTS public_file_tokens_access_history
|
||||
(
|
||||
id text NOT NULL,
|
||||
ip text not null,
|
||||
user_agent text not null,
|
||||
created_at bigint NOT NULL DEFAULT now_utc_micro_seconds(),
|
||||
CONSTRAINT unique_access_id_ip_ua UNIQUE (id, ip, user_agent),
|
||||
CONSTRAINT fk_public_file_history_token_id
|
||||
FOREIGN KEY (id)
|
||||
REFERENCES public_file_tokens (id)
|
||||
ON DELETE CASCADE
|
||||
);
|
||||
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS public_file_token_unique_idx ON public_file_tokens (access_token) WHERE is_disabled = FALSE;
|
||||
CREATE INDEX IF NOT EXISTS public_file_tokens_owner_id_updated_at_idx ON public_file_tokens (owner_id, updated_at);
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS public_active_file_link_unique_idx ON public_file_tokens (file_id, is_disabled) WHERE is_disabled = FALSE;
|
||||
@@ -10,7 +10,6 @@ import (
|
||||
log "github.com/sirupsen/logrus"
|
||||
|
||||
"github.com/ente-io/museum/ente"
|
||||
"github.com/ente-io/museum/pkg/controller"
|
||||
"github.com/ente-io/museum/pkg/utils/auth"
|
||||
"github.com/ente-io/museum/pkg/utils/handler"
|
||||
"github.com/ente-io/museum/pkg/utils/time"
|
||||
@@ -172,35 +171,6 @@ func (h *CollectionHandler) UpdateShareURL(c *gin.Context) {
|
||||
handler.Error(c, stacktrace.Propagate(err, ""))
|
||||
return
|
||||
}
|
||||
if req.DeviceLimit == nil && req.ValidTill == nil && req.DisablePassword == nil &&
|
||||
req.Nonce == nil && req.PassHash == nil && req.EnableDownload == nil && req.EnableCollect == nil {
|
||||
handler.Error(c, stacktrace.Propagate(ente.ErrBadRequest, "all parameters are missing"))
|
||||
return
|
||||
}
|
||||
|
||||
if req.DeviceLimit != nil && (*req.DeviceLimit < 0 || *req.DeviceLimit > controller.DeviceLimitThreshold) {
|
||||
handler.Error(c, stacktrace.Propagate(ente.ErrBadRequest, fmt.Sprintf("device limit: %d out of range", *req.DeviceLimit)))
|
||||
return
|
||||
}
|
||||
|
||||
if req.ValidTill != nil && *req.ValidTill != 0 && *req.ValidTill < time.Microseconds() {
|
||||
handler.Error(c, stacktrace.Propagate(ente.ErrBadRequest, "valid till should be greater than current timestamp"))
|
||||
return
|
||||
}
|
||||
|
||||
var allPassParamsMissing = req.Nonce == nil && req.PassHash == nil && req.MemLimit == nil && req.OpsLimit == nil
|
||||
var allPassParamsPresent = req.Nonce != nil && req.PassHash != nil && req.MemLimit != nil && req.OpsLimit != nil
|
||||
|
||||
if !(allPassParamsMissing || allPassParamsPresent) {
|
||||
handler.Error(c, stacktrace.Propagate(ente.ErrBadRequest, "all password params should be either present or missing"))
|
||||
return
|
||||
}
|
||||
|
||||
if allPassParamsPresent && req.DisablePassword != nil && *req.DisablePassword {
|
||||
handler.Error(c, stacktrace.Propagate(ente.ErrBadRequest, "can not set and disable password in same request"))
|
||||
return
|
||||
}
|
||||
|
||||
response, err := h.Controller.UpdateShareURL(c, auth.GetUserID(c.Request.Header), req)
|
||||
if err != nil {
|
||||
handler.Error(c, stacktrace.Propagate(err, ""))
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"fmt"
|
||||
"github.com/ente-io/museum/pkg/controller/file_copy"
|
||||
"github.com/ente-io/museum/pkg/controller/filedata"
|
||||
"github.com/ente-io/museum/pkg/controller/public"
|
||||
"net/http"
|
||||
"os"
|
||||
"strconv"
|
||||
@@ -24,6 +25,7 @@ import (
|
||||
// FileHandler exposes request handlers for all encrypted file related requests
|
||||
type FileHandler struct {
|
||||
Controller *controller.FileController
|
||||
FileUrlCtrl *public.FileLinkController
|
||||
FileCopyCtrl *file_copy.FileCopyController
|
||||
FileDataCtrl *filedata.Controller
|
||||
}
|
||||
|
||||
141
server/pkg/api/file_link.go
Normal file
@@ -0,0 +1,141 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"github.com/ente-io/museum/ente"
|
||||
"github.com/ente-io/museum/pkg/utils/auth"
|
||||
"github.com/ente-io/museum/pkg/utils/handler"
|
||||
"github.com/ente-io/stacktrace"
|
||||
"github.com/gin-gonic/gin"
|
||||
"net/http"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
// ShareUrl a sharable url for the file
|
||||
func (h *FileHandler) ShareUrl(c *gin.Context) {
|
||||
var file ente.CreateFileUrl
|
||||
if err := c.ShouldBindJSON(&file); err != nil {
|
||||
handler.Error(c, stacktrace.Propagate(err, ""))
|
||||
return
|
||||
}
|
||||
|
||||
response, err := h.FileUrlCtrl.CreateLink(c, file)
|
||||
if err != nil {
|
||||
handler.Error(c, stacktrace.Propagate(err, ""))
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusOK, response)
|
||||
}
|
||||
|
||||
func (h *FileHandler) LinkInfo(c *gin.Context) {
|
||||
resp, err := h.FileUrlCtrl.Info(c)
|
||||
if err != nil {
|
||||
handler.Error(c, stacktrace.Propagate(err, ""))
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"file": resp,
|
||||
})
|
||||
}
|
||||
|
||||
func (h *FileHandler) PasswordInfo(c *gin.Context) {
|
||||
resp, err := h.FileUrlCtrl.PassInfo(c)
|
||||
if err != nil {
|
||||
handler.Error(c, stacktrace.Propagate(err, ""))
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"nonce": resp.Nonce,
|
||||
"opsLimit": resp.OpsLimit,
|
||||
"memLimit": resp.MemLimit,
|
||||
})
|
||||
}
|
||||
|
||||
func (h *FileHandler) LinkThumbnail(c *gin.Context) {
|
||||
linkCtx := auth.MustGetFileLinkAccessContext(c)
|
||||
url, err := h.Controller.GetThumbnailURL(c, linkCtx.OwnerID, linkCtx.FileID)
|
||||
if err != nil {
|
||||
handler.Error(c, stacktrace.Propagate(err, ""))
|
||||
return
|
||||
}
|
||||
c.Redirect(http.StatusTemporaryRedirect, url)
|
||||
}
|
||||
|
||||
func (h *FileHandler) LinkFile(c *gin.Context) {
|
||||
linkCtx := auth.MustGetFileLinkAccessContext(c)
|
||||
url, err := h.Controller.GetFileURL(c, linkCtx.OwnerID, linkCtx.FileID)
|
||||
if err != nil {
|
||||
handler.Error(c, stacktrace.Propagate(err, ""))
|
||||
return
|
||||
}
|
||||
c.Redirect(http.StatusTemporaryRedirect, url)
|
||||
}
|
||||
|
||||
func (h *FileHandler) DisableUrl(c *gin.Context) {
|
||||
cID, err := strconv.ParseInt(c.Param("fileID"), 10, 64)
|
||||
if err != nil {
|
||||
handler.Error(c, stacktrace.Propagate(ente.ErrBadRequest, ""))
|
||||
return
|
||||
}
|
||||
err = h.FileUrlCtrl.Disable(c, cID)
|
||||
if err != nil {
|
||||
handler.Error(c, stacktrace.Propagate(err, ""))
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusOK, gin.H{})
|
||||
}
|
||||
|
||||
func (h *FileHandler) GetUrls(c *gin.Context) {
|
||||
sinceTime, err := strconv.ParseInt(c.Query("sinceTime"), 10, 64)
|
||||
if err != nil {
|
||||
handler.Error(c, stacktrace.Propagate(ente.ErrBadRequest, "sinceTime parsing failed"))
|
||||
return
|
||||
}
|
||||
limit := 500
|
||||
if c.Query("limit") != "" {
|
||||
limit, err = strconv.Atoi(c.Query("limit"))
|
||||
if err != nil || limit < 1 {
|
||||
handler.Error(c, stacktrace.Propagate(ente.ErrBadRequest, ""))
|
||||
return
|
||||
}
|
||||
}
|
||||
response, err := h.FileUrlCtrl.GetUrls(c, sinceTime, int64(limit))
|
||||
if err != nil {
|
||||
handler.Error(c, stacktrace.Propagate(err, ""))
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"diff": response,
|
||||
})
|
||||
}
|
||||
|
||||
// VerifyPassword verifies the password for given link access token and return signed jwt token if it's valid
|
||||
func (h *FileHandler) VerifyPassword(c *gin.Context) {
|
||||
var req ente.VerifyPasswordRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
handler.Error(c, stacktrace.Propagate(err, ""))
|
||||
return
|
||||
}
|
||||
resp, err := h.FileUrlCtrl.VerifyPassword(c, req)
|
||||
if err != nil {
|
||||
handler.Error(c, stacktrace.Propagate(err, ""))
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusOK, resp)
|
||||
}
|
||||
|
||||
// UpdateFileURL updates the share URL for a file
|
||||
func (h *FileHandler) UpdateFileURL(c *gin.Context) {
|
||||
var req ente.UpdateFileUrl
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
handler.Error(c, stacktrace.Propagate(err, ""))
|
||||
return
|
||||
}
|
||||
response, err := h.FileUrlCtrl.UpdateSharedUrl(c, req)
|
||||
if err != nil {
|
||||
handler.Error(c, stacktrace.Propagate(err, ""))
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"result": response,
|
||||
})
|
||||
}
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
fileData "github.com/ente-io/museum/ente/filedata"
|
||||
"github.com/ente-io/museum/pkg/controller/collections"
|
||||
"github.com/ente-io/museum/pkg/controller/filedata"
|
||||
"github.com/ente-io/museum/pkg/controller/public"
|
||||
"net/http"
|
||||
"strconv"
|
||||
|
||||
@@ -20,7 +21,7 @@ import (
|
||||
|
||||
// PublicCollectionHandler exposes request handlers for publicly accessible collections
|
||||
type PublicCollectionHandler struct {
|
||||
Controller *controller.PublicCollectionController
|
||||
Controller *public.CollectionLinkController
|
||||
FileCtrl *controller.FileController
|
||||
CollectionCtrl *collections.CollectionController
|
||||
FileDataCtrl *filedata.Controller
|
||||
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
"github.com/ente-io/museum/pkg/controller"
|
||||
"github.com/ente-io/museum/pkg/controller/access"
|
||||
"github.com/ente-io/museum/pkg/controller/email"
|
||||
"github.com/ente-io/museum/pkg/controller/public"
|
||||
"github.com/ente-io/museum/pkg/repo/cast"
|
||||
"github.com/ente-io/museum/pkg/utils/array"
|
||||
"github.com/ente-io/museum/pkg/utils/auth"
|
||||
@@ -24,16 +25,16 @@ const (
|
||||
|
||||
// CollectionController encapsulates logic that deals with collections
|
||||
type CollectionController struct {
|
||||
PublicCollectionCtrl *controller.PublicCollectionController
|
||||
EmailCtrl *email.EmailNotificationController
|
||||
AccessCtrl access.Controller
|
||||
BillingCtrl *controller.BillingController
|
||||
CollectionRepo *repo.CollectionRepository
|
||||
UserRepo *repo.UserRepository
|
||||
FileRepo *repo.FileRepository
|
||||
QueueRepo *repo.QueueRepository
|
||||
CastRepo *cast.Repository
|
||||
TaskRepo *repo.TaskLockRepository
|
||||
CollectionLinkCtrl *public.CollectionLinkController
|
||||
EmailCtrl *email.EmailNotificationController
|
||||
AccessCtrl access.Controller
|
||||
BillingCtrl *controller.BillingController
|
||||
CollectionRepo *repo.CollectionRepository
|
||||
UserRepo *repo.UserRepository
|
||||
FileRepo *repo.FileRepository
|
||||
QueueRepo *repo.QueueRepository
|
||||
CastRepo *cast.Repository
|
||||
TaskRepo *repo.TaskLockRepository
|
||||
}
|
||||
|
||||
// Create creates a collection
|
||||
@@ -148,7 +149,7 @@ func (c *CollectionController) TrashV3(ctx *gin.Context, req ente.TrashCollectio
|
||||
}
|
||||
|
||||
}
|
||||
err = c.PublicCollectionCtrl.Disable(ctx, cID)
|
||||
err = c.CollectionLinkCtrl.Disable(ctx, cID)
|
||||
if err != nil {
|
||||
return stacktrace.Propagate(err, "failed to disabled public share url")
|
||||
}
|
||||
@@ -209,7 +210,7 @@ func (c *CollectionController) HandleAccountDeletion(ctx context.Context, userID
|
||||
if err != nil {
|
||||
return stacktrace.Propagate(err, "failed to revoke cast token for user")
|
||||
}
|
||||
err = c.PublicCollectionCtrl.HandleAccountDeletion(ctx, userID, logger)
|
||||
err = c.CollectionLinkCtrl.HandleAccountDeletion(ctx, userID, logger)
|
||||
return stacktrace.Propagate(err, "")
|
||||
}
|
||||
|
||||
|
||||
@@ -70,21 +70,21 @@ func (c *CollectionController) JoinViaLink(ctx *gin.Context, req ente.JoinCollec
|
||||
if !collection.AllowSharing() {
|
||||
return stacktrace.Propagate(ente.ErrBadRequest, fmt.Sprintf("joining %s is not allowed", collection.Type))
|
||||
}
|
||||
publicCollectionToken, err := c.PublicCollectionCtrl.GetActivePublicCollectionToken(ctx, req.CollectionID)
|
||||
collectionLinkToken, err := c.CollectionLinkCtrl.GetActiveCollectionLinkToken(ctx, req.CollectionID)
|
||||
if err != nil {
|
||||
return stacktrace.Propagate(err, "")
|
||||
}
|
||||
|
||||
if canJoin := publicCollectionToken.CanJoin(); canJoin != nil {
|
||||
if canJoin := collectionLinkToken.CanJoin(); canJoin != nil {
|
||||
return stacktrace.Propagate(ente.ErrBadRequest, fmt.Sprintf("can not join collection: %s", canJoin.Error()))
|
||||
}
|
||||
accessToken := auth.GetAccessToken(ctx)
|
||||
if publicCollectionToken.Token != accessToken {
|
||||
if collectionLinkToken.Token != accessToken {
|
||||
return stacktrace.Propagate(ente.ErrPermissionDenied, "token doesn't match collection")
|
||||
}
|
||||
if publicCollectionToken.PassHash != nil && *publicCollectionToken.PassHash != "" {
|
||||
if collectionLinkToken.PassHash != nil && *collectionLinkToken.PassHash != "" {
|
||||
accessTokenJWT := auth.GetAccessTokenJWT(ctx)
|
||||
if passCheckErr := c.PublicCollectionCtrl.ValidateJWTToken(ctx, accessTokenJWT, *publicCollectionToken.PassHash); passCheckErr != nil {
|
||||
if passCheckErr := c.CollectionLinkCtrl.ValidateJWTToken(ctx, accessTokenJWT, *collectionLinkToken.PassHash); passCheckErr != nil {
|
||||
return stacktrace.Propagate(passCheckErr, "")
|
||||
}
|
||||
}
|
||||
@@ -93,7 +93,7 @@ func (c *CollectionController) JoinViaLink(ctx *gin.Context, req ente.JoinCollec
|
||||
return stacktrace.Propagate(err, "")
|
||||
}
|
||||
role := ente.VIEWER
|
||||
if publicCollectionToken.EnableCollect {
|
||||
if collectionLinkToken.EnableCollect {
|
||||
role = ente.COLLABORATOR
|
||||
}
|
||||
joinErr := c.CollectionRepo.Share(req.CollectionID, collection.Owner.ID, userID, req.EncryptedKey, role, time.Microseconds())
|
||||
@@ -197,7 +197,7 @@ func (c *CollectionController) ShareURL(ctx context.Context, userID int64, req e
|
||||
if err != nil {
|
||||
return ente.PublicURL{}, stacktrace.Propagate(err, "")
|
||||
}
|
||||
response, err := c.PublicCollectionCtrl.CreateAccessToken(ctx, req)
|
||||
response, err := c.CollectionLinkCtrl.CreateLink(ctx, req)
|
||||
if err != nil {
|
||||
return ente.PublicURL{}, stacktrace.Propagate(err, "")
|
||||
}
|
||||
@@ -205,20 +205,26 @@ func (c *CollectionController) ShareURL(ctx context.Context, userID int64, req e
|
||||
}
|
||||
|
||||
// UpdateShareURL updates the shared url configuration
|
||||
func (c *CollectionController) UpdateShareURL(ctx context.Context, userID int64, req ente.UpdatePublicAccessTokenRequest) (
|
||||
ente.PublicURL, error) {
|
||||
func (c *CollectionController) UpdateShareURL(
|
||||
ctx context.Context,
|
||||
userID int64,
|
||||
req ente.UpdatePublicAccessTokenRequest,
|
||||
) (*ente.PublicURL, error) {
|
||||
if err := req.Validate(); err != nil {
|
||||
return nil, stacktrace.Propagate(err, "")
|
||||
}
|
||||
if err := c.verifyOwnership(req.CollectionID, userID); err != nil {
|
||||
return ente.PublicURL{}, stacktrace.Propagate(err, "")
|
||||
return nil, stacktrace.Propagate(err, "")
|
||||
}
|
||||
err := c.BillingCtrl.HasActiveSelfOrFamilySubscription(userID, true)
|
||||
if err != nil {
|
||||
return ente.PublicURL{}, stacktrace.Propagate(err, "")
|
||||
return nil, stacktrace.Propagate(err, "")
|
||||
}
|
||||
response, err := c.PublicCollectionCtrl.UpdateSharedUrl(ctx, req)
|
||||
response, err := c.CollectionLinkCtrl.UpdateSharedUrl(ctx, req)
|
||||
if err != nil {
|
||||
return ente.PublicURL{}, stacktrace.Propagate(err, "")
|
||||
return nil, stacktrace.Propagate(err, "")
|
||||
}
|
||||
return response, nil
|
||||
return &response, nil
|
||||
}
|
||||
|
||||
// DisableSharedURL disable a public auth-token for the given collectionID
|
||||
@@ -226,7 +232,7 @@ func (c *CollectionController) DisableSharedURL(ctx context.Context, userID int6
|
||||
if err := c.verifyOwnership(cID, userID); err != nil {
|
||||
return stacktrace.Propagate(err, "")
|
||||
}
|
||||
err := c.PublicCollectionCtrl.Disable(ctx, cID)
|
||||
err := c.CollectionLinkCtrl.Disable(ctx, cID)
|
||||
return stacktrace.Propagate(err, "")
|
||||
}
|
||||
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
package controller
|
||||
package public
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/ente-io/museum/pkg/controller"
|
||||
"github.com/ente-io/museum/pkg/repo/public"
|
||||
|
||||
"github.com/ente-io/museum/ente"
|
||||
enteJWT "github.com/ente-io/museum/ente/jwt"
|
||||
emailCtrl "github.com/ente-io/museum/pkg/controller/email"
|
||||
"github.com/ente-io/museum/pkg/repo"
|
||||
"github.com/ente-io/museum/pkg/utils/auth"
|
||||
@@ -14,7 +15,6 @@ import (
|
||||
"github.com/ente-io/museum/pkg/utils/time"
|
||||
"github.com/ente-io/stacktrace"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/golang-jwt/jwt"
|
||||
"github.com/lithammer/shortuuid/v3"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
@@ -49,23 +49,24 @@ const (
|
||||
AbuseLimitExceededTemplate = "report_limit_exceeded_alert.html"
|
||||
)
|
||||
|
||||
// PublicCollectionController controls share collection operations
|
||||
type PublicCollectionController struct {
|
||||
FileController *FileController
|
||||
// CollectionLinkController controls share collection operations
|
||||
type CollectionLinkController struct {
|
||||
FileController *controller.FileController
|
||||
EmailNotificationCtrl *emailCtrl.EmailNotificationController
|
||||
PublicCollectionRepo *repo.PublicCollectionRepository
|
||||
CollectionLinkRepo *public.CollectionLinkRepo
|
||||
FileLinkRepo *public.FileLinkRepository
|
||||
CollectionRepo *repo.CollectionRepository
|
||||
UserRepo *repo.UserRepository
|
||||
JwtSecret []byte
|
||||
}
|
||||
|
||||
func (c *PublicCollectionController) CreateAccessToken(ctx context.Context, req ente.CreatePublicAccessTokenRequest) (ente.PublicURL, error) {
|
||||
func (c *CollectionLinkController) CreateLink(ctx context.Context, req ente.CreatePublicAccessTokenRequest) (ente.PublicURL, error) {
|
||||
accessToken := shortuuid.New()[0:AccessTokenLength]
|
||||
err := c.PublicCollectionRepo.
|
||||
err := c.CollectionLinkRepo.
|
||||
Insert(ctx, req.CollectionID, accessToken, req.ValidTill, req.DeviceLimit, req.EnableCollect, req.EnableJoin)
|
||||
if err != nil {
|
||||
if errors.Is(err, ente.ErrActiveLinkAlreadyExists) {
|
||||
collectionToPubUrlMap, err2 := c.PublicCollectionRepo.GetCollectionToActivePublicURLMap(ctx, []int64{req.CollectionID})
|
||||
collectionToPubUrlMap, err2 := c.CollectionLinkRepo.GetCollectionToActivePublicURLMap(ctx, []int64{req.CollectionID})
|
||||
if err2 != nil {
|
||||
return ente.PublicURL{}, stacktrace.Propagate(err2, "")
|
||||
}
|
||||
@@ -81,7 +82,7 @@ func (c *PublicCollectionController) CreateAccessToken(ctx context.Context, req
|
||||
}
|
||||
}
|
||||
response := ente.PublicURL{
|
||||
URL: c.PublicCollectionRepo.GetAlbumUrl(accessToken),
|
||||
URL: c.CollectionLinkRepo.GetAlbumUrl(accessToken),
|
||||
ValidTill: req.ValidTill,
|
||||
DeviceLimit: req.DeviceLimit,
|
||||
EnableDownload: true,
|
||||
@@ -91,11 +92,11 @@ func (c *PublicCollectionController) CreateAccessToken(ctx context.Context, req
|
||||
return response, nil
|
||||
}
|
||||
|
||||
func (c *PublicCollectionController) GetActivePublicCollectionToken(ctx context.Context, collectionID int64) (ente.PublicCollectionToken, error) {
|
||||
return c.PublicCollectionRepo.GetActivePublicCollectionToken(ctx, collectionID)
|
||||
func (c *CollectionLinkController) GetActiveCollectionLinkToken(ctx context.Context, collectionID int64) (ente.CollectionLinkRow, error) {
|
||||
return c.CollectionLinkRepo.GetActiveCollectionLinkRow(ctx, collectionID)
|
||||
}
|
||||
|
||||
func (c *PublicCollectionController) CreateFile(ctx *gin.Context, file ente.File, app ente.App) (ente.File, error) {
|
||||
func (c *CollectionLinkController) CreateFile(ctx *gin.Context, file ente.File, app ente.App) (ente.File, error) {
|
||||
collection, err := c.GetPublicCollection(ctx, true)
|
||||
if err != nil {
|
||||
return ente.File{}, stacktrace.Propagate(err, "")
|
||||
@@ -118,13 +119,13 @@ func (c *PublicCollectionController) CreateFile(ctx *gin.Context, file ente.File
|
||||
}
|
||||
|
||||
// Disable all public accessTokens generated for the given cID till date.
|
||||
func (c *PublicCollectionController) Disable(ctx context.Context, cID int64) error {
|
||||
err := c.PublicCollectionRepo.DisableSharing(ctx, cID)
|
||||
func (c *CollectionLinkController) Disable(ctx context.Context, cID int64) error {
|
||||
err := c.CollectionLinkRepo.DisableSharing(ctx, cID)
|
||||
return stacktrace.Propagate(err, "")
|
||||
}
|
||||
|
||||
func (c *PublicCollectionController) UpdateSharedUrl(ctx context.Context, req ente.UpdatePublicAccessTokenRequest) (ente.PublicURL, error) {
|
||||
publicCollectionToken, err := c.PublicCollectionRepo.GetActivePublicCollectionToken(ctx, req.CollectionID)
|
||||
func (c *CollectionLinkController) UpdateSharedUrl(ctx context.Context, req ente.UpdatePublicAccessTokenRequest) (ente.PublicURL, error) {
|
||||
publicCollectionToken, err := c.CollectionLinkRepo.GetActiveCollectionLinkRow(ctx, req.CollectionID)
|
||||
if err != nil {
|
||||
return ente.PublicURL{}, err
|
||||
}
|
||||
@@ -154,12 +155,12 @@ func (c *PublicCollectionController) UpdateSharedUrl(ctx context.Context, req en
|
||||
if req.EnableJoin != nil {
|
||||
publicCollectionToken.EnableJoin = *req.EnableJoin
|
||||
}
|
||||
err = c.PublicCollectionRepo.UpdatePublicCollectionToken(ctx, publicCollectionToken)
|
||||
err = c.CollectionLinkRepo.UpdatePublicCollectionToken(ctx, publicCollectionToken)
|
||||
if err != nil {
|
||||
return ente.PublicURL{}, stacktrace.Propagate(err, "")
|
||||
}
|
||||
return ente.PublicURL{
|
||||
URL: c.PublicCollectionRepo.GetAlbumUrl(publicCollectionToken.Token),
|
||||
URL: c.CollectionLinkRepo.GetAlbumUrl(publicCollectionToken.Token),
|
||||
DeviceLimit: publicCollectionToken.DeviceLimit,
|
||||
ValidTill: publicCollectionToken.ValidTill,
|
||||
EnableDownload: publicCollectionToken.EnableDownload,
|
||||
@@ -176,58 +177,23 @@ func (c *PublicCollectionController) UpdateSharedUrl(ctx context.Context, req en
|
||||
// used by the client to pass in other requests for public collection.
|
||||
// Having a separate endpoint for password validation allows us to easily rate-limit the attempts for brute-force
|
||||
// attack for guessing password.
|
||||
func (c *PublicCollectionController) VerifyPassword(ctx *gin.Context, req ente.VerifyPasswordRequest) (*ente.VerifyPasswordResponse, error) {
|
||||
func (c *CollectionLinkController) VerifyPassword(ctx *gin.Context, req ente.VerifyPasswordRequest) (*ente.VerifyPasswordResponse, error) {
|
||||
accessContext := auth.MustGetPublicAccessContext(ctx)
|
||||
publicCollectionToken, err := c.PublicCollectionRepo.GetActivePublicCollectionToken(ctx, accessContext.CollectionID)
|
||||
collectionLinkRow, err := c.CollectionLinkRepo.GetActiveCollectionLinkRow(ctx, accessContext.CollectionID)
|
||||
if err != nil {
|
||||
return nil, stacktrace.Propagate(err, "failed to get public collection info")
|
||||
}
|
||||
if publicCollectionToken.PassHash == nil || *publicCollectionToken.PassHash == "" {
|
||||
return nil, stacktrace.Propagate(ente.ErrBadRequest, "password is not configured for the link")
|
||||
}
|
||||
if req.PassHash != *publicCollectionToken.PassHash {
|
||||
return nil, stacktrace.Propagate(ente.ErrInvalidPassword, "incorrect password for link")
|
||||
}
|
||||
token := jwt.NewWithClaims(jwt.SigningMethodHS256, &enteJWT.PublicAlbumPasswordClaim{
|
||||
PassHash: req.PassHash,
|
||||
ExpiryTime: time.NDaysFromNow(365),
|
||||
})
|
||||
// Sign and get the complete encoded token as a string using the secret
|
||||
tokenString, err := token.SignedString(c.JwtSecret)
|
||||
|
||||
if err != nil {
|
||||
return nil, stacktrace.Propagate(err, "")
|
||||
}
|
||||
return &ente.VerifyPasswordResponse{
|
||||
JWTToken: tokenString,
|
||||
}, nil
|
||||
return verifyPassword(c.JwtSecret, collectionLinkRow.PassHash, req)
|
||||
}
|
||||
|
||||
func (c *PublicCollectionController) ValidateJWTToken(ctx *gin.Context, jwtToken string, passwordHash string) error {
|
||||
token, err := jwt.ParseWithClaims(jwtToken, &enteJWT.PublicAlbumPasswordClaim{}, func(token *jwt.Token) (interface{}, error) {
|
||||
if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
|
||||
return stacktrace.Propagate(fmt.Errorf("unexpected signing method: %v", token.Header["alg"]), ""), nil
|
||||
}
|
||||
return c.JwtSecret, nil
|
||||
})
|
||||
if err != nil {
|
||||
return stacktrace.Propagate(err, "JWT parsed failed")
|
||||
}
|
||||
claims, ok := token.Claims.(*enteJWT.PublicAlbumPasswordClaim)
|
||||
|
||||
if !ok {
|
||||
return stacktrace.Propagate(errors.New("no claim in jwt token"), "")
|
||||
}
|
||||
if token.Valid && claims.PassHash == passwordHash {
|
||||
return nil
|
||||
}
|
||||
return ente.ErrInvalidPassword
|
||||
func (c *CollectionLinkController) ValidateJWTToken(ctx *gin.Context, jwtToken string, passwordHash string) error {
|
||||
return validateJWTToken(c.JwtSecret, jwtToken, passwordHash)
|
||||
}
|
||||
|
||||
// ReportAbuse captures abuse report for a publicly shared collection.
|
||||
// It will also disable the accessToken for the collection if total abuse reports for the said collection
|
||||
// reaches AutoDisableAbuseThreshold
|
||||
func (c *PublicCollectionController) ReportAbuse(ctx *gin.Context, req ente.AbuseReportRequest) error {
|
||||
func (c *CollectionLinkController) ReportAbuse(ctx *gin.Context, req ente.AbuseReportRequest) error {
|
||||
accessContext := auth.MustGetPublicAccessContext(ctx)
|
||||
readableReason, found := AllowedReasons[req.Reason]
|
||||
if !found {
|
||||
@@ -235,11 +201,11 @@ func (c *PublicCollectionController) ReportAbuse(ctx *gin.Context, req ente.Abus
|
||||
}
|
||||
logrus.WithField("collectionID", accessContext.CollectionID).Error("CRITICAL: received abuse report")
|
||||
|
||||
err := c.PublicCollectionRepo.RecordAbuseReport(ctx, accessContext, req.URL, req.Reason, req.Details)
|
||||
err := c.CollectionLinkRepo.RecordAbuseReport(ctx, accessContext, req.URL, req.Reason, req.Details)
|
||||
if err != nil {
|
||||
return stacktrace.Propagate(err, "")
|
||||
}
|
||||
count, err := c.PublicCollectionRepo.GetAbuseReportCount(ctx, accessContext)
|
||||
count, err := c.CollectionLinkRepo.GetAbuseReportCount(ctx, accessContext)
|
||||
if err != nil {
|
||||
return stacktrace.Propagate(err, "")
|
||||
}
|
||||
@@ -253,7 +219,7 @@ func (c *PublicCollectionController) ReportAbuse(ctx *gin.Context, req ente.Abus
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *PublicCollectionController) onAbuseReportReceived(collectionID int64, report ente.AbuseReportRequest, readableReason string, abuseCount int64) {
|
||||
func (c *CollectionLinkController) onAbuseReportReceived(collectionID int64, report ente.AbuseReportRequest, readableReason string, abuseCount int64) {
|
||||
collection, err := c.CollectionRepo.Get(collectionID)
|
||||
if err != nil {
|
||||
logrus.Error("Could not get collection for abuse report")
|
||||
@@ -292,9 +258,9 @@ func (c *PublicCollectionController) onAbuseReportReceived(collectionID int64, r
|
||||
}
|
||||
}
|
||||
|
||||
func (c *PublicCollectionController) HandleAccountDeletion(ctx context.Context, userID int64, logger *logrus.Entry) error {
|
||||
func (c *CollectionLinkController) HandleAccountDeletion(ctx context.Context, userID int64, logger *logrus.Entry) error {
|
||||
logger.Info("updating public collection on account deletion")
|
||||
collectionIDs, err := c.PublicCollectionRepo.GetActivePublicTokenForUser(ctx, userID)
|
||||
collectionIDs, err := c.CollectionLinkRepo.GetActivePublicTokenForUser(ctx, userID)
|
||||
if err != nil {
|
||||
return stacktrace.Propagate(err, "")
|
||||
}
|
||||
@@ -305,12 +271,12 @@ func (c *PublicCollectionController) HandleAccountDeletion(ctx context.Context,
|
||||
return stacktrace.Propagate(err, "")
|
||||
}
|
||||
}
|
||||
return nil
|
||||
return c.FileLinkRepo.DisableLinksForUser(ctx, userID)
|
||||
}
|
||||
|
||||
// GetPublicCollection will return collection info for a public url.
|
||||
// is mustAllowCollect is set to true but the underlying collection doesn't allow uploading
|
||||
func (c *PublicCollectionController) GetPublicCollection(ctx *gin.Context, mustAllowCollect bool) (ente.Collection, error) {
|
||||
func (c *CollectionLinkController) GetPublicCollection(ctx *gin.Context, mustAllowCollect bool) (ente.Collection, error) {
|
||||
accessContext := auth.MustGetPublicAccessContext(ctx)
|
||||
collection, err := c.CollectionRepo.Get(accessContext.CollectionID)
|
||||
if err != nil {
|
||||
162
server/pkg/controller/public/file_link.go
Normal file
@@ -0,0 +1,162 @@
|
||||
package public
|
||||
|
||||
import (
|
||||
"github.com/ente-io/museum/ente"
|
||||
"github.com/ente-io/museum/pkg/controller"
|
||||
"github.com/ente-io/museum/pkg/repo"
|
||||
"github.com/ente-io/museum/pkg/repo/public"
|
||||
"github.com/ente-io/museum/pkg/utils/auth"
|
||||
"github.com/ente-io/stacktrace"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/lithammer/shortuuid/v3"
|
||||
)
|
||||
|
||||
// FileLinkController controls share collection operations
|
||||
type FileLinkController struct {
|
||||
FileController *controller.FileController
|
||||
FileLinkRepo *public.FileLinkRepository
|
||||
FileRepo *repo.FileRepository
|
||||
JwtSecret []byte
|
||||
}
|
||||
|
||||
func (c *FileLinkController) CreateLink(ctx *gin.Context, req ente.CreateFileUrl) (*ente.FileUrl, error) {
|
||||
actorUserID := auth.GetUserID(ctx.Request.Header)
|
||||
app := auth.GetApp(ctx)
|
||||
if req.App != app {
|
||||
return nil, stacktrace.Propagate(ente.NewBadRequestWithMessage("app mismatch"), "app mismatch")
|
||||
}
|
||||
file, err := c.FileRepo.GetFileAttributes(req.FileID)
|
||||
if err != nil {
|
||||
return nil, stacktrace.Propagate(err, "failed to get file attributes")
|
||||
}
|
||||
if actorUserID != file.OwnerID {
|
||||
return nil, stacktrace.Propagate(ente.NewPermissionDeniedError("not file owner"), "")
|
||||
}
|
||||
accessToken := shortuuid.New()[0:AccessTokenLength]
|
||||
_, err = c.FileLinkRepo.Insert(ctx, req.FileID, actorUserID, accessToken, app)
|
||||
if err == nil || err == ente.ErrActiveLinkAlreadyExists {
|
||||
row, rowErr := c.FileLinkRepo.GetFileUrlRowByFileID(ctx, req.FileID)
|
||||
if rowErr != nil {
|
||||
return nil, stacktrace.Propagate(rowErr, "failed to get active file url token")
|
||||
}
|
||||
return c.mapRowToFileUrl(ctx, row), nil
|
||||
}
|
||||
return nil, stacktrace.Propagate(err, "failed to create public file link")
|
||||
}
|
||||
|
||||
// Disable all public accessTokens generated for the given fileID till date.
|
||||
func (c *FileLinkController) Disable(ctx *gin.Context, fileID int64) error {
|
||||
userID := auth.GetUserID(ctx.Request.Header)
|
||||
file, err := c.FileRepo.GetFileAttributes(fileID)
|
||||
if err != nil {
|
||||
return stacktrace.Propagate(err, "failed to get file attributes")
|
||||
}
|
||||
if userID != file.OwnerID {
|
||||
return stacktrace.Propagate(ente.NewPermissionDeniedError("not file owner"), "")
|
||||
}
|
||||
return c.FileLinkRepo.DisableLinkForFiles(ctx, []int64{fileID})
|
||||
}
|
||||
|
||||
func (c *FileLinkController) GetUrls(ctx *gin.Context, sinceTime int64, limit int64) ([]*ente.FileUrl, error) {
|
||||
userID := auth.GetUserID(ctx.Request.Header)
|
||||
app := auth.GetApp(ctx)
|
||||
fileLinks, err := c.FileLinkRepo.GetFileUrls(ctx, userID, sinceTime, limit, app)
|
||||
if err != nil {
|
||||
return nil, stacktrace.Propagate(err, "failed to get file urls")
|
||||
}
|
||||
var fileUrls []*ente.FileUrl
|
||||
for _, row := range fileLinks {
|
||||
fileUrls = append(fileUrls, c.mapRowToFileUrl(ctx, row))
|
||||
}
|
||||
return fileUrls, nil
|
||||
}
|
||||
|
||||
func (c *FileLinkController) UpdateSharedUrl(ctx *gin.Context, req ente.UpdateFileUrl) (*ente.FileUrl, error) {
|
||||
if err := req.Validate(); err != nil {
|
||||
return nil, stacktrace.Propagate(err, "invalid request")
|
||||
}
|
||||
fileLinkRow, err := c.FileLinkRepo.GetActiveFileUrlToken(ctx, req.FileID)
|
||||
if err != nil {
|
||||
return nil, stacktrace.Propagate(err, "failed to get file link info")
|
||||
}
|
||||
if fileLinkRow.OwnerID != auth.GetUserID(ctx.Request.Header) {
|
||||
return nil, stacktrace.Propagate(ente.NewPermissionDeniedError("not file owner"), "")
|
||||
}
|
||||
if req.ValidTill != nil {
|
||||
fileLinkRow.ValidTill = *req.ValidTill
|
||||
}
|
||||
if req.DeviceLimit != nil {
|
||||
fileLinkRow.DeviceLimit = *req.DeviceLimit
|
||||
}
|
||||
if req.PassHash != nil && req.Nonce != nil && req.OpsLimit != nil && req.MemLimit != nil {
|
||||
fileLinkRow.PassHash = req.PassHash
|
||||
fileLinkRow.Nonce = req.Nonce
|
||||
fileLinkRow.OpsLimit = req.OpsLimit
|
||||
fileLinkRow.MemLimit = req.MemLimit
|
||||
} else if req.DisablePassword != nil && *req.DisablePassword {
|
||||
fileLinkRow.PassHash = nil
|
||||
fileLinkRow.Nonce = nil
|
||||
fileLinkRow.OpsLimit = nil
|
||||
fileLinkRow.MemLimit = nil
|
||||
}
|
||||
if req.EnableDownload != nil {
|
||||
fileLinkRow.EnableDownload = *req.EnableDownload
|
||||
}
|
||||
|
||||
err = c.FileLinkRepo.UpdateLink(ctx, *fileLinkRow)
|
||||
if err != nil {
|
||||
return nil, stacktrace.Propagate(err, "")
|
||||
}
|
||||
return c.mapRowToFileUrl(ctx, fileLinkRow), nil
|
||||
}
|
||||
|
||||
func (c *FileLinkController) Info(ctx *gin.Context) (*ente.File, error) {
|
||||
accessContext := auth.MustGetFileLinkAccessContext(ctx)
|
||||
return c.FileRepo.GetFileAttributes(accessContext.FileID)
|
||||
}
|
||||
|
||||
func (c *FileLinkController) PassInfo(ctx *gin.Context) (*ente.FileLinkRow, error) {
|
||||
accessContext := auth.MustGetFileLinkAccessContext(ctx)
|
||||
return c.FileLinkRepo.GetFileUrlRowByFileID(ctx, accessContext.FileID)
|
||||
}
|
||||
|
||||
// VerifyPassword verifies if the user has provided correct pw hash. If yes, it returns a signed jwt token which can be
|
||||
// used by the client to pass in other requests for public collection.
|
||||
// Having a separate endpoint for password validation allows us to easily rate-limit the attempts for brute-force
|
||||
// attack for guessing password.
|
||||
func (c *FileLinkController) VerifyPassword(ctx *gin.Context, req ente.VerifyPasswordRequest) (*ente.VerifyPasswordResponse, error) {
|
||||
accessContext := auth.MustGetFileLinkAccessContext(ctx)
|
||||
collectionLinkRow, err := c.FileLinkRepo.GetActiveFileUrlToken(ctx, accessContext.FileID)
|
||||
if err != nil {
|
||||
return nil, stacktrace.Propagate(err, "failed to get public collection info")
|
||||
}
|
||||
return verifyPassword(c.JwtSecret, collectionLinkRow.PassHash, req)
|
||||
}
|
||||
|
||||
func (c *FileLinkController) ValidateJWTToken(ctx *gin.Context, jwtToken string, passwordHash string) error {
|
||||
return validateJWTToken(c.JwtSecret, jwtToken, passwordHash)
|
||||
}
|
||||
|
||||
func (c *FileLinkController) mapRowToFileUrl(ctx *gin.Context, row *ente.FileLinkRow) *ente.FileUrl {
|
||||
app := auth.GetApp(ctx)
|
||||
var url string
|
||||
if app == ente.Locker {
|
||||
url = c.FileLinkRepo.LockerFileLink(row.Token)
|
||||
} else {
|
||||
url = c.FileLinkRepo.PhotoLink(row.Token)
|
||||
}
|
||||
return &ente.FileUrl{
|
||||
LinkID: row.LinkID,
|
||||
FileID: row.FileID,
|
||||
URL: url,
|
||||
OwnerID: row.OwnerID,
|
||||
ValidTill: row.ValidTill,
|
||||
DeviceLimit: row.DeviceLimit,
|
||||
PasswordEnabled: row.PassHash != nil,
|
||||
Nonce: row.Nonce,
|
||||
OpsLimit: row.OpsLimit,
|
||||
MemLimit: row.MemLimit,
|
||||
EnableDownload: row.EnableDownload,
|
||||
CreatedAt: row.CreatedAt,
|
||||
}
|
||||
}
|
||||
54
server/pkg/controller/public/link_common.go
Normal file
@@ -0,0 +1,54 @@
|
||||
package public
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/ente-io/museum/ente"
|
||||
enteJWT "github.com/ente-io/museum/ente/jwt"
|
||||
"github.com/ente-io/museum/pkg/utils/time"
|
||||
"github.com/ente-io/stacktrace"
|
||||
"github.com/golang-jwt/jwt"
|
||||
)
|
||||
|
||||
func validateJWTToken(secret []byte, jwtToken string, passwordHash string) error {
|
||||
token, err := jwt.ParseWithClaims(jwtToken, &enteJWT.LinkPasswordClaim{}, func(token *jwt.Token) (interface{}, error) {
|
||||
if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
|
||||
return stacktrace.Propagate(fmt.Errorf("unexpected signing method: %v", token.Header["alg"]), ""), nil
|
||||
}
|
||||
return secret, nil
|
||||
})
|
||||
if err != nil {
|
||||
return stacktrace.Propagate(err, "JWT parsed failed")
|
||||
}
|
||||
claims, ok := token.Claims.(*enteJWT.LinkPasswordClaim)
|
||||
|
||||
if !ok {
|
||||
return stacktrace.Propagate(errors.New("no claim in jwt token"), "")
|
||||
}
|
||||
if token.Valid && claims.PassHash == passwordHash {
|
||||
return nil
|
||||
}
|
||||
return ente.ErrInvalidPassword
|
||||
}
|
||||
|
||||
func verifyPassword(secret []byte, expectedPassHash *string, req ente.VerifyPasswordRequest) (*ente.VerifyPasswordResponse, error) {
|
||||
if expectedPassHash == nil || *expectedPassHash == "" {
|
||||
return nil, stacktrace.Propagate(ente.ErrBadRequest, "password is not configured for the link")
|
||||
}
|
||||
if req.PassHash != *expectedPassHash {
|
||||
return nil, stacktrace.Propagate(ente.ErrInvalidPassword, "incorrect password for link")
|
||||
}
|
||||
token := jwt.NewWithClaims(jwt.SigningMethodHS256, &enteJWT.LinkPasswordClaim{
|
||||
PassHash: req.PassHash,
|
||||
ExpiryTime: time.NDaysFromNow(365),
|
||||
})
|
||||
// Sign and get the complete encoded token as a string using the secret
|
||||
tokenString, err := token.SignedString(secret)
|
||||
|
||||
if err != nil {
|
||||
return nil, stacktrace.Propagate(err, "")
|
||||
}
|
||||
return &ente.VerifyPasswordResponse{
|
||||
JWTToken: tokenString,
|
||||
}, nil
|
||||
}
|
||||
@@ -5,6 +5,8 @@ import (
|
||||
"context"
|
||||
"crypto/sha256"
|
||||
"fmt"
|
||||
public2 "github.com/ente-io/museum/pkg/controller/public"
|
||||
"github.com/ente-io/museum/pkg/repo/public"
|
||||
"net/http"
|
||||
|
||||
"github.com/ente-io/museum/ente"
|
||||
@@ -24,20 +26,20 @@ import (
|
||||
var passwordWhiteListedURLs = []string{"/public-collection/info", "/public-collection/report-abuse", "/public-collection/verify-password"}
|
||||
var whitelistedCollectionShareIDs = []int64{111}
|
||||
|
||||
// AccessTokenMiddleware intercepts and authenticates incoming requests
|
||||
type AccessTokenMiddleware struct {
|
||||
PublicCollectionRepo *repo.PublicCollectionRepository
|
||||
PublicCollectionCtrl *controller.PublicCollectionController
|
||||
// CollectionLinkMiddleware intercepts and authenticates incoming requests
|
||||
type CollectionLinkMiddleware struct {
|
||||
CollectionLinkRepo *public.CollectionLinkRepo
|
||||
PublicCollectionCtrl *public2.CollectionLinkController
|
||||
CollectionRepo *repo.CollectionRepository
|
||||
Cache *cache.Cache
|
||||
BillingCtrl *controller.BillingController
|
||||
DiscordController *discord.DiscordController
|
||||
}
|
||||
|
||||
// AccessTokenAuthMiddleware returns a middle ware that extracts the `X-Auth-Access-Token`
|
||||
// Authenticate returns a middle ware that extracts the `X-Auth-Access-Token`
|
||||
// within the header of a request and uses it to validate the access token and set the
|
||||
// ente.PublicAccessContext with auth.PublicAccessKey as key
|
||||
func (m *AccessTokenMiddleware) AccessTokenAuthMiddleware(urlSanitizer func(_ *gin.Context) string) gin.HandlerFunc {
|
||||
func (m *CollectionLinkMiddleware) Authenticate(urlSanitizer func(_ *gin.Context) string) gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
accessToken := auth.GetAccessToken(c)
|
||||
if accessToken == "" {
|
||||
@@ -52,7 +54,7 @@ func (m *AccessTokenMiddleware) AccessTokenAuthMiddleware(urlSanitizer func(_ *g
|
||||
cacheKey := computeHashKeyForList([]string{accessToken, clientIP, userAgent}, ":")
|
||||
cachedValue, cacheHit := m.Cache.Get(cacheKey)
|
||||
if !cacheHit {
|
||||
publicCollectionSummary, err = m.PublicCollectionRepo.GetCollectionSummaryByToken(c, accessToken)
|
||||
publicCollectionSummary, err = m.CollectionLinkRepo.GetCollectionSummaryByToken(c, accessToken)
|
||||
if err != nil {
|
||||
c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"error": "invalid token"})
|
||||
return
|
||||
@@ -112,7 +114,7 @@ func (m *AccessTokenMiddleware) AccessTokenAuthMiddleware(urlSanitizer func(_ *g
|
||||
c.Next()
|
||||
}
|
||||
}
|
||||
func (m *AccessTokenMiddleware) validateOwnersSubscription(cID int64) error {
|
||||
func (m *CollectionLinkMiddleware) validateOwnersSubscription(cID int64) error {
|
||||
userID, err := m.CollectionRepo.GetOwnerID(cID)
|
||||
if err != nil {
|
||||
return stacktrace.Propagate(err, "")
|
||||
@@ -120,7 +122,7 @@ func (m *AccessTokenMiddleware) validateOwnersSubscription(cID int64) error {
|
||||
return m.BillingCtrl.HasActiveSelfOrFamilySubscription(userID, false)
|
||||
}
|
||||
|
||||
func (m *AccessTokenMiddleware) isDeviceLimitReached(ctx context.Context,
|
||||
func (m *CollectionLinkMiddleware) isDeviceLimitReached(ctx context.Context,
|
||||
collectionSummary ente.PublicCollectionSummary, ip string, ua string) (bool, error) {
|
||||
// skip deviceLimit check & record keeping for requests via CF worker
|
||||
if network.IsCFWorkerIP(ip) {
|
||||
@@ -128,7 +130,7 @@ func (m *AccessTokenMiddleware) isDeviceLimitReached(ctx context.Context,
|
||||
}
|
||||
|
||||
sharedID := collectionSummary.ID
|
||||
hasAccessedInPast, err := m.PublicCollectionRepo.AccessedInPast(ctx, sharedID, ip, ua)
|
||||
hasAccessedInPast, err := m.CollectionLinkRepo.AccessedInPast(ctx, sharedID, ip, ua)
|
||||
if err != nil {
|
||||
return false, stacktrace.Propagate(err, "")
|
||||
}
|
||||
@@ -136,17 +138,17 @@ func (m *AccessTokenMiddleware) isDeviceLimitReached(ctx context.Context,
|
||||
if hasAccessedInPast {
|
||||
return false, nil
|
||||
}
|
||||
count, err := m.PublicCollectionRepo.GetUniqueAccessCount(ctx, sharedID)
|
||||
count, err := m.CollectionLinkRepo.GetUniqueAccessCount(ctx, sharedID)
|
||||
if err != nil {
|
||||
return false, stacktrace.Propagate(err, "failed to get unique access count")
|
||||
}
|
||||
|
||||
deviceLimit := int64(collectionSummary.DeviceLimit)
|
||||
if deviceLimit == controller.DeviceLimitThreshold {
|
||||
deviceLimit = controller.DeviceLimitThresholdMultiplier * controller.DeviceLimitThreshold
|
||||
if deviceLimit == public2.DeviceLimitThreshold {
|
||||
deviceLimit = public2.DeviceLimitThresholdMultiplier * public2.DeviceLimitThreshold
|
||||
}
|
||||
|
||||
if count >= controller.DeviceLimitWarningThreshold {
|
||||
if count >= public2.DeviceLimitWarningThreshold {
|
||||
if !array.Int64InList(sharedID, whitelistedCollectionShareIDs) {
|
||||
m.DiscordController.NotifyPotentialAbuse(
|
||||
fmt.Sprintf("Album exceeds warning threshold: {CollectionID: %d, ShareID: %d}",
|
||||
@@ -157,12 +159,12 @@ func (m *AccessTokenMiddleware) isDeviceLimitReached(ctx context.Context,
|
||||
if deviceLimit > 0 && count >= deviceLimit {
|
||||
return true, nil
|
||||
}
|
||||
err = m.PublicCollectionRepo.RecordAccessHistory(ctx, sharedID, ip, ua)
|
||||
err = m.CollectionLinkRepo.RecordAccessHistory(ctx, sharedID, ip, ua)
|
||||
return false, stacktrace.Propagate(err, "failed to record access history")
|
||||
}
|
||||
|
||||
// validatePassword will verify if the user is provided correct password for the public album
|
||||
func (m *AccessTokenMiddleware) validatePassword(c *gin.Context, reqPath string,
|
||||
func (m *CollectionLinkMiddleware) validatePassword(c *gin.Context, reqPath string,
|
||||
collectionSummary ente.PublicCollectionSummary) error {
|
||||
if array.StringInList(reqPath, passwordWhiteListedURLs) {
|
||||
return nil
|
||||
168
server/pkg/middleware/file_link.go
Normal file
@@ -0,0 +1,168 @@
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
publicCtrl "github.com/ente-io/museum/pkg/controller/public"
|
||||
"github.com/ente-io/museum/pkg/repo/public"
|
||||
"github.com/ente-io/museum/pkg/utils/array"
|
||||
"net/http"
|
||||
|
||||
"github.com/ente-io/museum/ente"
|
||||
"github.com/ente-io/museum/pkg/controller"
|
||||
"github.com/ente-io/museum/pkg/controller/discord"
|
||||
"github.com/ente-io/museum/pkg/utils/auth"
|
||||
"github.com/ente-io/museum/pkg/utils/network"
|
||||
"github.com/ente-io/museum/pkg/utils/time"
|
||||
"github.com/ente-io/stacktrace"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/patrickmn/go-cache"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
var filePasswordWhiteListedURLs = []string{"/file-link/pass-info", "/file-link/verify-password"}
|
||||
|
||||
// FileLinkMiddleware intercepts and authenticates incoming requests
|
||||
type FileLinkMiddleware struct {
|
||||
FileLinkRepo *public.FileLinkRepository
|
||||
FileLinkCtrl *publicCtrl.FileLinkController
|
||||
Cache *cache.Cache
|
||||
BillingCtrl *controller.BillingController
|
||||
DiscordController *discord.DiscordController
|
||||
}
|
||||
|
||||
// Authenticate returns a middle ware that extracts the `X-Auth-Access-Token`
|
||||
// within the header of a request and uses it to validate the access token and set the
|
||||
// ente.PublicAccessContext with auth.PublicAccessKey as key
|
||||
func (m *FileLinkMiddleware) Authenticate(urlSanitizer func(_ *gin.Context) string) gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
accessToken := auth.GetAccessToken(c)
|
||||
if accessToken == "" {
|
||||
c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"error": "missing accessToken"})
|
||||
return
|
||||
}
|
||||
clientIP := network.GetClientIP(c)
|
||||
userAgent := c.GetHeader("User-Agent")
|
||||
|
||||
cacheKey := computeHashKeyForList([]string{accessToken, clientIP, userAgent}, ":")
|
||||
cachedValue, cacheHit := m.Cache.Get(cacheKey)
|
||||
var fileLinkRow *ente.FileLinkRow
|
||||
var err error
|
||||
if !cacheHit {
|
||||
fileLinkRow, err = m.FileLinkRepo.GetFileUrlRowByToken(c, accessToken)
|
||||
if err != nil {
|
||||
logrus.WithError(err).Info("failed to get file link row by token")
|
||||
c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"error": "invalid token"})
|
||||
return
|
||||
}
|
||||
if fileLinkRow.IsDisabled {
|
||||
c.AbortWithStatusJSON(http.StatusGone, gin.H{"error": "disabled token"})
|
||||
return
|
||||
}
|
||||
// validate if user still has active paid subscription
|
||||
if err = m.BillingCtrl.HasActiveSelfOrFamilySubscription(fileLinkRow.OwnerID, true); err != nil {
|
||||
logrus.WithError(err).Info("failed to verify active paid subscription")
|
||||
c.AbortWithStatusJSON(http.StatusGone, gin.H{"error": "no active subscription"})
|
||||
return
|
||||
}
|
||||
|
||||
// validate device limit
|
||||
reached, limitErr := m.isDeviceLimitReached(c, fileLinkRow, clientIP, userAgent)
|
||||
if limitErr != nil {
|
||||
logrus.WithError(limitErr).Error("failed to check device limit")
|
||||
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"error": "something went wrong"})
|
||||
return
|
||||
}
|
||||
if reached {
|
||||
c.AbortWithStatusJSON(http.StatusTooManyRequests, gin.H{"error": "reached device limit"})
|
||||
return
|
||||
}
|
||||
} else {
|
||||
fileLinkRow = cachedValue.(*ente.FileLinkRow)
|
||||
}
|
||||
|
||||
if fileLinkRow.ValidTill > 0 && // expiry time is defined, 0 indicates no expiry
|
||||
fileLinkRow.ValidTill < time.Microseconds() {
|
||||
c.AbortWithStatusJSON(http.StatusGone, gin.H{"error": "expired token"})
|
||||
return
|
||||
}
|
||||
|
||||
// checks password protected public collection
|
||||
if fileLinkRow.PassHash != nil && *fileLinkRow.PassHash != "" {
|
||||
reqPath := urlSanitizer(c)
|
||||
if err = m.validatePassword(c, reqPath, fileLinkRow); err != nil {
|
||||
logrus.WithError(err).Warn("password validation failed")
|
||||
c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"error": err})
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if !cacheHit {
|
||||
m.Cache.Set(cacheKey, fileLinkRow, cache.DefaultExpiration)
|
||||
}
|
||||
|
||||
c.Set(auth.FileLinkAccessKey, &ente.FileLinkAccessContext{
|
||||
LinkID: fileLinkRow.LinkID,
|
||||
IP: clientIP,
|
||||
UserAgent: userAgent,
|
||||
FileID: fileLinkRow.FileID,
|
||||
OwnerID: fileLinkRow.OwnerID,
|
||||
})
|
||||
c.Next()
|
||||
}
|
||||
}
|
||||
|
||||
func (m *FileLinkMiddleware) isDeviceLimitReached(ctx context.Context,
|
||||
collectionSummary *ente.FileLinkRow, ip string, ua string) (bool, error) {
|
||||
// skip deviceLimit check & record keeping for requests via CF worker
|
||||
if network.IsCFWorkerIP(ip) {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
sharedID := collectionSummary.LinkID
|
||||
hasAccessedInPast, err := m.FileLinkRepo.AccessedInPast(ctx, sharedID, ip, ua)
|
||||
if err != nil {
|
||||
return false, stacktrace.Propagate(err, "")
|
||||
}
|
||||
// if the device has accessed the url in the past, let it access it now as well, irrespective of device limit.
|
||||
if hasAccessedInPast {
|
||||
return false, nil
|
||||
}
|
||||
count, err := m.FileLinkRepo.GetUniqueAccessCount(ctx, sharedID)
|
||||
if err != nil {
|
||||
return false, stacktrace.Propagate(err, "failed to get unique access count")
|
||||
}
|
||||
|
||||
deviceLimit := int64(collectionSummary.DeviceLimit)
|
||||
if deviceLimit == publicCtrl.DeviceLimitThreshold {
|
||||
deviceLimit = publicCtrl.DeviceLimitThresholdMultiplier * publicCtrl.DeviceLimitThreshold
|
||||
}
|
||||
|
||||
if count >= publicCtrl.DeviceLimitWarningThreshold {
|
||||
m.DiscordController.NotifyPotentialAbuse(
|
||||
fmt.Sprintf("FileLink exceeds warning threshold: {FileID: %d, ShareID: %s}",
|
||||
collectionSummary.FileID, collectionSummary.LinkID))
|
||||
}
|
||||
|
||||
if deviceLimit > 0 && count >= deviceLimit {
|
||||
return true, nil
|
||||
}
|
||||
err = m.FileLinkRepo.RecordAccessHistory(ctx, sharedID, ip, ua)
|
||||
return false, stacktrace.Propagate(err, "failed to record access history")
|
||||
}
|
||||
|
||||
// validatePassword will verify if the user is provided correct password for the public album
|
||||
func (m *FileLinkMiddleware) validatePassword(
|
||||
c *gin.Context,
|
||||
reqPath string,
|
||||
fileLinkRow *ente.FileLinkRow,
|
||||
) error {
|
||||
accessTokenJWT := auth.GetAccessTokenJWT(c)
|
||||
if accessTokenJWT == "" {
|
||||
if array.StringInList(reqPath, filePasswordWhiteListedURLs) {
|
||||
return nil
|
||||
}
|
||||
return &ente.ErrPassProtectedResource
|
||||
}
|
||||
return m.FileLinkCtrl.ValidateJWTToken(c, accessTokenJWT, *fileLinkRow.PassHash)
|
||||
}
|
||||
@@ -140,6 +140,7 @@ func (r *RateLimitMiddleware) getLimiter(reqPath string, reqMethod string) *limi
|
||||
reqPath == "/users/verify-email" ||
|
||||
reqPath == "/user/change-email" ||
|
||||
reqPath == "/public-collection/verify-password" ||
|
||||
reqPath == "/file-link/verify-password" ||
|
||||
reqPath == "/family/accept-invite" ||
|
||||
reqPath == "/users/srp/attributes" ||
|
||||
(reqPath == "/cast/device-info" && reqMethod == "POST") ||
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"github.com/ente-io/museum/pkg/repo/public"
|
||||
"strconv"
|
||||
t "time"
|
||||
|
||||
@@ -22,13 +23,13 @@ import (
|
||||
// CollectionRepository defines the methods for inserting, updating and
|
||||
// retrieving collection entities from the underlying repository
|
||||
type CollectionRepository struct {
|
||||
DB *sql.DB
|
||||
FileRepo *FileRepository
|
||||
PublicCollectionRepo *PublicCollectionRepository
|
||||
TrashRepo *TrashRepository
|
||||
SecretEncryptionKey []byte
|
||||
QueueRepo *QueueRepository
|
||||
LatencyLogger *prometheus.HistogramVec
|
||||
DB *sql.DB
|
||||
FileRepo *FileRepository
|
||||
CollectionLinkRepo *public.CollectionLinkRepo
|
||||
TrashRepo *TrashRepository
|
||||
SecretEncryptionKey []byte
|
||||
QueueRepo *QueueRepository
|
||||
LatencyLogger *prometheus.HistogramVec
|
||||
}
|
||||
|
||||
type SharedCollection struct {
|
||||
@@ -74,7 +75,7 @@ func (repo *CollectionRepository) Get(collectionID int64) (ente.Collection, erro
|
||||
c.EncryptedName = encryptedName.String
|
||||
c.NameDecryptionNonce = nameDecryptionNonce.String
|
||||
}
|
||||
urlMap, err := repo.PublicCollectionRepo.GetCollectionToActivePublicURLMap(context.Background(), []int64{collectionID})
|
||||
urlMap, err := repo.CollectionLinkRepo.GetCollectionToActivePublicURLMap(context.Background(), []int64{collectionID})
|
||||
if err != nil {
|
||||
return ente.Collection{}, stacktrace.Propagate(err, "failed to get publicURL info")
|
||||
}
|
||||
@@ -174,7 +175,7 @@ pct.access_token, pct.valid_till, pct.device_limit, pct.created_at, pct.updated_
|
||||
if _, ok := addPublicUrlMap[pctToken.String]; !ok {
|
||||
addPublicUrlMap[pctToken.String] = true
|
||||
url := ente.PublicURL{
|
||||
URL: repo.PublicCollectionRepo.GetAlbumUrl(pctToken.String),
|
||||
URL: repo.CollectionLinkRepo.GetAlbumUrl(pctToken.String),
|
||||
DeviceLimit: int(pctDeviceLimit.Int32),
|
||||
ValidTill: pctValidTill.Int64,
|
||||
EnableDownload: pctEnableDownload.Bool,
|
||||
|
||||
@@ -638,6 +638,16 @@ func (repo *FileRepository) GetFileAttributesForCopy(fileIDs []int64) ([]ente.Fi
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (repo *FileRepository) GetFileAttributes(fileID int64) (*ente.File, error) {
|
||||
rows := repo.DB.QueryRow(`SELECT file_id, owner_id, file_decryption_header, thumbnail_decryption_header, metadata_decryption_header, encrypted_metadata, pub_magic_metadata FROM files WHERE file_id = $1`, fileID)
|
||||
var file ente.File
|
||||
err := rows.Scan(&file.ID, &file.OwnerID, &file.File.DecryptionHeader, &file.Thumbnail.DecryptionHeader, &file.Metadata.DecryptionHeader, &file.Metadata.EncryptedData, &file.PubicMagicMetadata)
|
||||
if err != nil {
|
||||
return nil, stacktrace.Propagate(err, "")
|
||||
}
|
||||
return &file, nil
|
||||
}
|
||||
|
||||
// GetUsage gets the Storage usage of a user
|
||||
// Deprecated: GetUsage is deprecated, use UsageRepository.GetUsage
|
||||
func (repo *FileRepository) GetUsage(userID int64) (int64, error) {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package repo
|
||||
package public
|
||||
|
||||
import (
|
||||
"context"
|
||||
@@ -13,29 +13,29 @@ import (
|
||||
|
||||
const BaseShareURL = "https://albums.ente.io/?t=%s"
|
||||
|
||||
// PublicCollectionRepository defines the methods for inserting, updating and
|
||||
// CollectionLinkRepo defines the methods for inserting, updating and
|
||||
// retrieving entities related to public collections
|
||||
type PublicCollectionRepository struct {
|
||||
type CollectionLinkRepo struct {
|
||||
DB *sql.DB
|
||||
albumHost string
|
||||
}
|
||||
|
||||
// NewPublicCollectionRepository ..
|
||||
func NewPublicCollectionRepository(db *sql.DB, albumHost string) *PublicCollectionRepository {
|
||||
// NewCollectionLinkRepository ..
|
||||
func NewCollectionLinkRepository(db *sql.DB, albumHost string) *CollectionLinkRepo {
|
||||
if albumHost == "" {
|
||||
albumHost = "https://albums.ente.io"
|
||||
}
|
||||
return &PublicCollectionRepository{
|
||||
return &CollectionLinkRepo{
|
||||
DB: db,
|
||||
albumHost: albumHost,
|
||||
}
|
||||
}
|
||||
|
||||
func (pcr *PublicCollectionRepository) GetAlbumUrl(token string) string {
|
||||
func (pcr *CollectionLinkRepo) GetAlbumUrl(token string) string {
|
||||
return fmt.Sprintf("%s/?t=%s", pcr.albumHost, token)
|
||||
}
|
||||
|
||||
func (pcr *PublicCollectionRepository) Insert(ctx context.Context,
|
||||
func (pcr *CollectionLinkRepo) Insert(ctx context.Context,
|
||||
cID int64, token string, validTill int64, deviceLimit int, enableCollect bool, enableJoin *bool) error {
|
||||
// default value for enableJoin is true
|
||||
join := true
|
||||
@@ -51,7 +51,7 @@ func (pcr *PublicCollectionRepository) Insert(ctx context.Context,
|
||||
return stacktrace.Propagate(err, "failed to insert")
|
||||
}
|
||||
|
||||
func (pcr *PublicCollectionRepository) DisableSharing(ctx context.Context, cID int64) error {
|
||||
func (pcr *CollectionLinkRepo) DisableSharing(ctx context.Context, cID int64) error {
|
||||
_, err := pcr.DB.ExecContext(ctx, `UPDATE public_collection_tokens SET is_disabled = true where
|
||||
collection_id = $1 and is_disabled = false`, cID)
|
||||
return stacktrace.Propagate(err, "failed to disable sharing")
|
||||
@@ -59,7 +59,7 @@ func (pcr *PublicCollectionRepository) DisableSharing(ctx context.Context, cID i
|
||||
|
||||
// GetCollectionToActivePublicURLMap will return map of collectionID to PublicURLs which are not disabled yet.
|
||||
// Note: The url could be expired or deviceLimit is already reached
|
||||
func (pcr *PublicCollectionRepository) GetCollectionToActivePublicURLMap(ctx context.Context, collectionIDs []int64) (map[int64][]ente.PublicURL, error) {
|
||||
func (pcr *CollectionLinkRepo) GetCollectionToActivePublicURLMap(ctx context.Context, collectionIDs []int64) (map[int64][]ente.PublicURL, error) {
|
||||
rows, err := pcr.DB.QueryContext(ctx, `SELECT collection_id, access_token, valid_till, device_limit, enable_download, enable_collect, enable_join, pw_nonce, mem_limit, ops_limit FROM
|
||||
public_collection_tokens WHERE collection_id = ANY($1) and is_disabled = FALSE`,
|
||||
pq.Array(collectionIDs))
|
||||
@@ -92,26 +92,26 @@ func (pcr *PublicCollectionRepository) GetCollectionToActivePublicURLMap(ctx con
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// GetActivePublicCollectionToken will return ente.PublicCollectionToken for given collection ID
|
||||
// GetActiveCollectionLinkRow will return ente.CollectionLinkRow for given collection ID
|
||||
// Note: The token could be expired or deviceLimit is already reached
|
||||
func (pcr *PublicCollectionRepository) GetActivePublicCollectionToken(ctx context.Context, collectionID int64) (ente.PublicCollectionToken, error) {
|
||||
func (pcr *CollectionLinkRepo) GetActiveCollectionLinkRow(ctx context.Context, collectionID int64) (ente.CollectionLinkRow, error) {
|
||||
row := pcr.DB.QueryRowContext(ctx, `SELECT id, collection_id, access_token, valid_till, device_limit,
|
||||
is_disabled, pw_hash, pw_nonce, mem_limit, ops_limit, enable_download, enable_collect, enable_join FROM
|
||||
public_collection_tokens WHERE collection_id = $1 and is_disabled = FALSE`,
|
||||
collectionID)
|
||||
|
||||
//defer rows.Close()
|
||||
ret := ente.PublicCollectionToken{}
|
||||
ret := ente.CollectionLinkRow{}
|
||||
err := row.Scan(&ret.ID, &ret.CollectionID, &ret.Token, &ret.ValidTill, &ret.DeviceLimit,
|
||||
&ret.IsDisabled, &ret.PassHash, &ret.Nonce, &ret.MemLimit, &ret.OpsLimit, &ret.EnableDownload, &ret.EnableCollect, &ret.EnableJoin)
|
||||
if err != nil {
|
||||
return ente.PublicCollectionToken{}, stacktrace.Propagate(err, "")
|
||||
return ente.CollectionLinkRow{}, stacktrace.Propagate(err, "")
|
||||
}
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
// UpdatePublicCollectionToken will update the row for corresponding public collection token
|
||||
func (pcr *PublicCollectionRepository) UpdatePublicCollectionToken(ctx context.Context, pct ente.PublicCollectionToken) error {
|
||||
func (pcr *CollectionLinkRepo) UpdatePublicCollectionToken(ctx context.Context, pct ente.CollectionLinkRow) error {
|
||||
_, err := pcr.DB.ExecContext(ctx, `UPDATE public_collection_tokens SET valid_till = $1, device_limit = $2,
|
||||
pw_hash = $3, pw_nonce = $4, mem_limit = $5, ops_limit = $6, enable_download = $7, enable_collect = $8, enable_join = $9
|
||||
where id = $10`,
|
||||
@@ -119,7 +119,7 @@ func (pcr *PublicCollectionRepository) UpdatePublicCollectionToken(ctx context.C
|
||||
return stacktrace.Propagate(err, "failed to update public collection token")
|
||||
}
|
||||
|
||||
func (pcr *PublicCollectionRepository) RecordAbuseReport(ctx context.Context, accessCtx ente.PublicAccessContext,
|
||||
func (pcr *CollectionLinkRepo) RecordAbuseReport(ctx context.Context, accessCtx ente.PublicAccessContext,
|
||||
url string, reason string, details ente.AbuseReportDetails) error {
|
||||
_, err := pcr.DB.ExecContext(ctx, `INSERT INTO public_abuse_report
|
||||
(share_id, ip, user_agent, url, reason, details) VALUES ($1, $2, $3, $4, $5, $6)
|
||||
@@ -128,7 +128,7 @@ func (pcr *PublicCollectionRepository) RecordAbuseReport(ctx context.Context, ac
|
||||
return stacktrace.Propagate(err, "failed to record abuse report")
|
||||
}
|
||||
|
||||
func (pcr *PublicCollectionRepository) GetAbuseReportCount(ctx context.Context, accessCtx ente.PublicAccessContext) (int64, error) {
|
||||
func (pcr *CollectionLinkRepo) GetAbuseReportCount(ctx context.Context, accessCtx ente.PublicAccessContext) (int64, error) {
|
||||
row := pcr.DB.QueryRowContext(ctx, `SELECT count(*) FROM public_abuse_report WHERE share_id = $1`, accessCtx.ID)
|
||||
var count int64 = 0
|
||||
err := row.Scan(&count)
|
||||
@@ -138,7 +138,7 @@ func (pcr *PublicCollectionRepository) GetAbuseReportCount(ctx context.Context,
|
||||
return count, nil
|
||||
}
|
||||
|
||||
func (pcr *PublicCollectionRepository) GetUniqueAccessCount(ctx context.Context, shareId int64) (int64, error) {
|
||||
func (pcr *CollectionLinkRepo) GetUniqueAccessCount(ctx context.Context, shareId int64) (int64, error) {
|
||||
row := pcr.DB.QueryRowContext(ctx, `SELECT count(*) FROM public_collection_access_history WHERE share_id = $1`, shareId)
|
||||
var count int64 = 0
|
||||
err := row.Scan(&count)
|
||||
@@ -148,7 +148,7 @@ func (pcr *PublicCollectionRepository) GetUniqueAccessCount(ctx context.Context,
|
||||
return count, nil
|
||||
}
|
||||
|
||||
func (pcr *PublicCollectionRepository) RecordAccessHistory(ctx context.Context, shareID int64, ip string, ua string) error {
|
||||
func (pcr *CollectionLinkRepo) RecordAccessHistory(ctx context.Context, shareID int64, ip string, ua string) error {
|
||||
_, err := pcr.DB.ExecContext(ctx, `INSERT INTO public_collection_access_history
|
||||
(share_id, ip, user_agent) VALUES ($1, $2, $3)
|
||||
ON CONFLICT ON CONSTRAINT unique_access_sid_ip_ua DO NOTHING;`,
|
||||
@@ -157,7 +157,7 @@ func (pcr *PublicCollectionRepository) RecordAccessHistory(ctx context.Context,
|
||||
}
|
||||
|
||||
// AccessedInPast returns true if the given ip, ua agent combination has accessed the url in the past
|
||||
func (pcr *PublicCollectionRepository) AccessedInPast(ctx context.Context, shareID int64, ip string, ua string) (bool, error) {
|
||||
func (pcr *CollectionLinkRepo) AccessedInPast(ctx context.Context, shareID int64, ip string, ua string) (bool, error) {
|
||||
row := pcr.DB.QueryRowContext(ctx, `select share_id from public_collection_access_history where share_id =$1 and ip = $2 and user_agent = $3`,
|
||||
shareID, ip, ua)
|
||||
var tempID int64
|
||||
@@ -168,7 +168,7 @@ func (pcr *PublicCollectionRepository) AccessedInPast(ctx context.Context, share
|
||||
return true, stacktrace.Propagate(err, "failed to record access history")
|
||||
}
|
||||
|
||||
func (pcr *PublicCollectionRepository) GetCollectionSummaryByToken(ctx context.Context, accessToken string) (ente.PublicCollectionSummary, error) {
|
||||
func (pcr *CollectionLinkRepo) GetCollectionSummaryByToken(ctx context.Context, accessToken string) (ente.PublicCollectionSummary, error) {
|
||||
row := pcr.DB.QueryRowContext(ctx,
|
||||
`SELECT sct.id, sct.collection_id, sct.is_disabled, sct.valid_till, sct.device_limit, sct.pw_hash,
|
||||
sct.created_at, sct.updated_at, count(ah.share_id)
|
||||
@@ -185,7 +185,7 @@ func (pcr *PublicCollectionRepository) GetCollectionSummaryByToken(ctx context.C
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (pcr *PublicCollectionRepository) GetActivePublicTokenForUser(ctx context.Context, userID int64) ([]int64, error) {
|
||||
func (pcr *CollectionLinkRepo) GetActivePublicTokenForUser(ctx context.Context, userID int64) ([]int64, error) {
|
||||
rows, err := pcr.DB.QueryContext(ctx, `select pt.collection_id from public_collection_tokens pt left join collections c on pt.collection_id = c.collection_id where pt.is_disabled = FALSE and c.owner_id= $1;`, userID)
|
||||
if err != nil {
|
||||
return nil, stacktrace.Propagate(err, "")
|
||||
@@ -204,7 +204,7 @@ func (pcr *PublicCollectionRepository) GetActivePublicTokenForUser(ctx context.C
|
||||
}
|
||||
|
||||
// CleanupAccessHistory public_collection_access_history where public_collection_tokens is disabled and the last updated time is older than 30 days
|
||||
func (pcr *PublicCollectionRepository) CleanupAccessHistory(ctx context.Context) error {
|
||||
func (pcr *CollectionLinkRepo) CleanupAccessHistory(ctx context.Context) error {
|
||||
_, err := pcr.DB.ExecContext(ctx, `DELETE FROM public_collection_access_history WHERE share_id IN (SELECT id FROM public_collection_tokens WHERE is_disabled = TRUE AND updated_at < (now_utc_micro_seconds() - (24::BIGINT * 30 * 60 * 60 * 1000 * 1000)))`)
|
||||
if err != nil {
|
||||
return stacktrace.Propagate(err, "failed to clean up public collection access history")
|
||||
218
server/pkg/repo/public/file_link.go
Normal file
@@ -0,0 +1,218 @@
|
||||
package public
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/ente-io/museum/ente/base"
|
||||
"github.com/lib/pq"
|
||||
"github.com/spf13/viper"
|
||||
|
||||
"github.com/ente-io/museum/ente"
|
||||
"github.com/ente-io/stacktrace"
|
||||
)
|
||||
|
||||
// FileLinkRepository defines the methods for inserting, updating and
|
||||
// retrieving entities related to public file
|
||||
type FileLinkRepository struct {
|
||||
DB *sql.DB
|
||||
photoHost string
|
||||
lockerHost string
|
||||
}
|
||||
|
||||
// NewFileLinkRepo ..
|
||||
func NewFileLinkRepo(db *sql.DB) *FileLinkRepository {
|
||||
albumHost := viper.GetString("apps.public-albums")
|
||||
if albumHost == "" {
|
||||
albumHost = "https://albums.ente.io"
|
||||
}
|
||||
lockerHost := viper.GetString("apps.public-locker")
|
||||
if lockerHost == "" {
|
||||
lockerHost = "https://locker.ente.io"
|
||||
}
|
||||
return &FileLinkRepository{
|
||||
DB: db,
|
||||
photoHost: albumHost,
|
||||
lockerHost: lockerHost,
|
||||
}
|
||||
}
|
||||
|
||||
func (pcr *FileLinkRepository) PhotoLink(token string) string {
|
||||
return fmt.Sprintf("%s/?t=%s", pcr.photoHost, token)
|
||||
}
|
||||
|
||||
func (pcr *FileLinkRepository) LockerFileLink(token string) string {
|
||||
return fmt.Sprintf("%s/?t=%s", pcr.lockerHost, token)
|
||||
}
|
||||
|
||||
func (pcr *FileLinkRepository) Insert(
|
||||
ctx context.Context,
|
||||
fileID int64,
|
||||
ownerID int64,
|
||||
token string,
|
||||
app ente.App,
|
||||
) (*string, error) {
|
||||
id, err := base.NewID("pft")
|
||||
if err != nil {
|
||||
return nil, stacktrace.Propagate(err, "failed to generate new ID for public file token")
|
||||
}
|
||||
_, err = pcr.DB.ExecContext(ctx, `INSERT INTO public_file_tokens
|
||||
(id, file_id, owner_id, access_token, app) VALUES ($1, $2, $3, $4, $5)`,
|
||||
id, fileID, ownerID, token, string(app))
|
||||
if err != nil {
|
||||
if err.Error() == "pq: duplicate key value violates unique constraint \"public_active_file_link_unique_idx\"" {
|
||||
return nil, ente.ErrActiveLinkAlreadyExists
|
||||
}
|
||||
return nil, stacktrace.Propagate(err, "failed to insert")
|
||||
}
|
||||
return id, nil
|
||||
}
|
||||
|
||||
// GetActiveFileUrlToken will return ente.CollectionLinkRow for given collection ID
|
||||
// Note: The token could be expired or deviceLimit is already reached
|
||||
func (pcr *FileLinkRepository) GetActiveFileUrlToken(ctx context.Context, fileID int64) (*ente.FileLinkRow, error) {
|
||||
row := pcr.DB.QueryRowContext(ctx, `SELECT id, file_id, owner_id, access_token, valid_till, device_limit,
|
||||
is_disabled, pw_hash, pw_nonce, mem_limit, ops_limit, enable_download FROM
|
||||
public_file_tokens WHERE file_id = $1 and is_disabled = FALSE`,
|
||||
fileID)
|
||||
|
||||
ret := ente.FileLinkRow{}
|
||||
err := row.Scan(&ret.LinkID, &ret.FileID, ret.OwnerID, &ret.Token, &ret.ValidTill, &ret.DeviceLimit,
|
||||
&ret.IsDisabled, &ret.PassHash, &ret.Nonce, &ret.MemLimit, &ret.OpsLimit, &ret.EnableDownload)
|
||||
if err != nil {
|
||||
return nil, stacktrace.Propagate(err, "")
|
||||
}
|
||||
return &ret, nil
|
||||
}
|
||||
func (pcr *FileLinkRepository) GetFileUrls(ctx context.Context, userID int64, sinceTime int64, limit int64, app ente.App) ([]*ente.FileLinkRow, error) {
|
||||
if limit <= 0 {
|
||||
limit = 500
|
||||
}
|
||||
query := `SELECT id, file_id, owner_id, is_disabled, valid_till, device_limit, enable_download, pw_hash, pw_nonce, mem_limit, ops_limit,
|
||||
created_at, updated_at FROM public_file_tokens
|
||||
WHERE owner_id = $1 AND created_at > $2 AND app = $3 ORDER BY updated_at DESC LIMIT $4`
|
||||
rows, err := pcr.DB.QueryContext(ctx, query, userID, sinceTime, string(app), limit)
|
||||
if err != nil {
|
||||
return nil, stacktrace.Propagate(err, "failed to get public file urls")
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
var result []*ente.FileLinkRow
|
||||
for rows.Next() {
|
||||
var row ente.FileLinkRow
|
||||
err = rows.Scan(&row.LinkID, &row.FileID, &row.OwnerID, &row.IsDisabled,
|
||||
&row.ValidTill, &row.DeviceLimit, &row.EnableDownload,
|
||||
&row.PassHash, &row.Nonce, &row.MemLimit,
|
||||
&row.OpsLimit, &row.CreatedAt, &row.UpdatedAt)
|
||||
if err != nil {
|
||||
return nil, stacktrace.Propagate(err, "failed to scan public file url row")
|
||||
}
|
||||
result = append(result, &row)
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (pcr *FileLinkRepository) DisableLinkForFiles(ctx context.Context, fileIDs []int64) error {
|
||||
if len(fileIDs) == 0 {
|
||||
return nil
|
||||
}
|
||||
query := `UPDATE public_file_tokens SET is_disabled = TRUE WHERE file_id = ANY($1)`
|
||||
_, err := pcr.DB.ExecContext(ctx, query, pq.Array(fileIDs))
|
||||
if err != nil {
|
||||
return stacktrace.Propagate(err, "failed to disable public file links")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// DisableLinksForUser will disable all public file links for the given user
|
||||
func (pcr *FileLinkRepository) DisableLinksForUser(ctx context.Context, userID int64) error {
|
||||
_, err := pcr.DB.ExecContext(ctx, `UPDATE public_file_tokens SET is_disabled = TRUE WHERE owner_id = $1`, userID)
|
||||
if err != nil {
|
||||
return stacktrace.Propagate(err, "failed to disable public file link")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (pcr *FileLinkRepository) GetFileUrlRowByToken(ctx context.Context, accessToken string) (*ente.FileLinkRow, error) {
|
||||
row := pcr.DB.QueryRowContext(ctx,
|
||||
`SELECT id, file_id, owner_id, is_disabled, valid_till, device_limit, enable_download, pw_hash, pw_nonce, mem_limit, ops_limit
|
||||
created_at, updated_at
|
||||
from public_file_tokens
|
||||
where access_token = $1
|
||||
`, accessToken)
|
||||
var result = ente.FileLinkRow{}
|
||||
err := row.Scan(&result.LinkID, &result.FileID, &result.OwnerID, &result.IsDisabled, &result.EnableDownload, &result.ValidTill, &result.DeviceLimit, &result.PassHash, &result.Nonce, &result.MemLimit, &result.OpsLimit, &result.CreatedAt, &result.UpdatedAt)
|
||||
if err != nil {
|
||||
if err == sql.ErrNoRows {
|
||||
return nil, ente.ErrNotFound
|
||||
}
|
||||
return nil, stacktrace.Propagate(err, "failed to get public file url summary by token")
|
||||
}
|
||||
return &result, nil
|
||||
}
|
||||
|
||||
func (pcr *FileLinkRepository) GetFileUrlRowByFileID(ctx context.Context, fileID int64) (*ente.FileLinkRow, error) {
|
||||
row := pcr.DB.QueryRowContext(ctx,
|
||||
`SELECT id, file_id, access_token, owner_id, is_disabled, enable_download, valid_till, device_limit, pw_hash, pw_nonce, mem_limit, ops_limit,
|
||||
created_at, updated_at
|
||||
from public_file_tokens
|
||||
where file_id = $1 and is_disabled = FALSE`, fileID)
|
||||
var result = ente.FileLinkRow{}
|
||||
err := row.Scan(&result.LinkID, &result.FileID, &result.Token, &result.OwnerID, &result.IsDisabled, &result.EnableDownload, &result.ValidTill, &result.DeviceLimit, &result.PassHash, &result.Nonce, &result.MemLimit, &result.OpsLimit, &result.CreatedAt, &result.UpdatedAt)
|
||||
if err != nil {
|
||||
if err == sql.ErrNoRows {
|
||||
return nil, ente.ErrNotFound
|
||||
}
|
||||
return nil, stacktrace.Propagate(err, "failed to get public file url summary by file ID")
|
||||
}
|
||||
return &result, nil
|
||||
}
|
||||
|
||||
// UpdateLink will update the row for corresponding public file token
|
||||
func (pcr *FileLinkRepository) UpdateLink(ctx context.Context, pct ente.FileLinkRow) error {
|
||||
_, err := pcr.DB.ExecContext(ctx, `UPDATE public_file_tokens SET valid_till = $1, device_limit = $2,
|
||||
pw_hash = $3, pw_nonce = $4, mem_limit = $5, ops_limit = $6, enable_download = $7
|
||||
where id = $8`,
|
||||
pct.ValidTill, pct.DeviceLimit, pct.PassHash, pct.Nonce, pct.MemLimit, pct.OpsLimit, pct.EnableDownload, pct.LinkID)
|
||||
return stacktrace.Propagate(err, "failed to update public file token")
|
||||
}
|
||||
|
||||
func (pcr *FileLinkRepository) GetUniqueAccessCount(ctx context.Context, linkId string) (int64, error) {
|
||||
row := pcr.DB.QueryRowContext(ctx, `SELECT count(*) FROM public_file_tokens_access_history WHERE id = $1`, linkId)
|
||||
var count int64 = 0
|
||||
err := row.Scan(&count)
|
||||
if err != nil {
|
||||
return -1, stacktrace.Propagate(err, "")
|
||||
}
|
||||
return count, nil
|
||||
}
|
||||
|
||||
func (pcr *FileLinkRepository) RecordAccessHistory(ctx context.Context, shareID string, ip string, ua string) error {
|
||||
_, err := pcr.DB.ExecContext(ctx, `INSERT INTO public_file_tokens_access_history
|
||||
(id, ip, user_agent) VALUES ($1, $2, $3)
|
||||
ON CONFLICT ON CONSTRAINT unique_access_id_ip_ua DO NOTHING;`,
|
||||
shareID, ip, ua)
|
||||
return stacktrace.Propagate(err, "failed to record access history")
|
||||
}
|
||||
|
||||
// AccessedInPast returns true if the given ip, ua agent combination has accessed the url in the past
|
||||
func (pcr *FileLinkRepository) AccessedInPast(ctx context.Context, shareID string, ip string, ua string) (bool, error) {
|
||||
row := pcr.DB.QueryRowContext(ctx, `select id from public_file_tokens_access_history where id =$1 and ip = $2 and user_agent = $3`,
|
||||
shareID, ip, ua)
|
||||
var tempID int64
|
||||
err := row.Scan(&tempID)
|
||||
if errors.Is(err, sql.ErrNoRows) {
|
||||
return false, nil
|
||||
}
|
||||
return true, stacktrace.Propagate(err, "failed to record access history")
|
||||
}
|
||||
|
||||
// CleanupAccessHistory public_file_tokens_access_history where public_collection_tokens is disabled and the last updated time is older than 30 days
|
||||
func (pcr *FileLinkRepository) CleanupAccessHistory(ctx context.Context) error {
|
||||
_, err := pcr.DB.ExecContext(ctx, `DELETE FROM public_file_tokens_access_history WHERE id IN (SELECT id FROM public_file_tokens WHERE is_disabled = TRUE AND updated_at < (now_utc_micro_seconds() - (24::BIGINT * 30 * 60 * 60 * 1000 * 1000)))`)
|
||||
if err != nil {
|
||||
return stacktrace.Propagate(err, "failed to clean up public file access history")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"database/sql"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/ente-io/museum/pkg/repo/public"
|
||||
"strings"
|
||||
|
||||
"github.com/ente-io/museum/ente"
|
||||
@@ -32,10 +33,11 @@ type FileWithUpdatedAt struct {
|
||||
}
|
||||
|
||||
type TrashRepository struct {
|
||||
DB *sql.DB
|
||||
ObjectRepo *ObjectRepository
|
||||
FileRepo *FileRepository
|
||||
QueueRepo *QueueRepository
|
||||
DB *sql.DB
|
||||
ObjectRepo *ObjectRepository
|
||||
FileRepo *FileRepository
|
||||
QueueRepo *QueueRepository
|
||||
FileLinkRepo *public.FileLinkRepository
|
||||
}
|
||||
|
||||
func (t *TrashRepository) InsertItems(ctx context.Context, tx *sql.Tx, userID int64, items []ente.TrashItemRequest) error {
|
||||
@@ -156,6 +158,13 @@ func (t *TrashRepository) TrashFiles(fileIDs []int64, userID int64, trash ente.T
|
||||
return stacktrace.Propagate(err, "")
|
||||
}
|
||||
err = tx.Commit()
|
||||
|
||||
if err == nil {
|
||||
removeLinkErr := t.FileLinkRepo.DisableLinkForFiles(ctx, fileIDs)
|
||||
if removeLinkErr != nil {
|
||||
return stacktrace.Propagate(removeLinkErr, "failed to disable file links for files being trashed")
|
||||
}
|
||||
}
|
||||
return stacktrace.Propagate(err, "")
|
||||
}
|
||||
|
||||
|
||||
@@ -17,8 +17,9 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
PublicAccessKey = "X-Public-Access-ID"
|
||||
CastContext = "X-Cast-Context"
|
||||
PublicAccessKey = "X-Public-Access-ID"
|
||||
FileLinkAccessKey = "X-Public-FileLink-Access-ID"
|
||||
CastContext = "X-Cast-Context"
|
||||
)
|
||||
|
||||
// GenerateRandomBytes returns securely generated random bytes.
|
||||
@@ -120,6 +121,8 @@ func GetCastToken(c *gin.Context) string {
|
||||
return token
|
||||
}
|
||||
|
||||
// GetAccessTokenJWT fetches the JWT access token from the request header or query parameters.
|
||||
// This token is issued by server on password verification of links that are protected by password.
|
||||
func GetAccessTokenJWT(c *gin.Context) string {
|
||||
token := c.GetHeader("X-Auth-Access-Token-JWT")
|
||||
if token == "" {
|
||||
@@ -132,6 +135,10 @@ func MustGetPublicAccessContext(c *gin.Context) ente.PublicAccessContext {
|
||||
return c.MustGet(PublicAccessKey).(ente.PublicAccessContext)
|
||||
}
|
||||
|
||||
func MustGetFileLinkAccessContext(c *gin.Context) *ente.FileLinkAccessContext {
|
||||
return c.MustGet(FileLinkAccessKey).(*ente.FileLinkAccessContext)
|
||||
}
|
||||
|
||||
func GetCastCtx(c *gin.Context) cast.AuthContext {
|
||||
return c.MustGet(CastContext).(cast.AuthContext)
|
||||
}
|
||||
|
||||
@@ -705,56 +705,54 @@ const splitMetadataAndMediaItems = (
|
||||
* single live photo when appropriate.
|
||||
*/
|
||||
const clusterLivePhotos = async (
|
||||
items: UploadItemWithCollectionIDAndName[],
|
||||
_items: UploadItemWithCollectionIDAndName[],
|
||||
parsedMetadataJSONMap: Map<string, ParsedMetadataJSON>,
|
||||
) => {
|
||||
const result: ClusteredUploadItem[] = [];
|
||||
type ItemAsset = PotentialLivePhotoAsset & {
|
||||
localID: number;
|
||||
isLivePhoto?: boolean;
|
||||
};
|
||||
const items: ItemAsset[] = _items.map((item) => ({
|
||||
localID: item.localID,
|
||||
isLivePhoto: item.isLivePhoto,
|
||||
fileName: item.fileName,
|
||||
fileType: potentialFileTypeFromExtension(item.fileName) ?? -1,
|
||||
collectionID: item.collectionID,
|
||||
uploadItem: item.uploadItem!,
|
||||
pathPrefix: item.pathPrefix,
|
||||
}));
|
||||
items
|
||||
.sort((f, g) =>
|
||||
nameAndExtension(f.fileName)[0].localeCompare(
|
||||
.sort((f, g) => {
|
||||
const cmp = nameAndExtension(f.fileName)[0].localeCompare(
|
||||
nameAndExtension(g.fileName)[0],
|
||||
),
|
||||
)
|
||||
);
|
||||
return cmp == 0 ? f.fileType - g.fileType : cmp;
|
||||
})
|
||||
.sort((f, g) => f.collectionID - g.collectionID);
|
||||
let index = 0;
|
||||
while (index < items.length - 1) {
|
||||
const f = items[index]!;
|
||||
const g = items[index + 1]!;
|
||||
const fFileType = potentialFileTypeFromExtension(f.fileName)!;
|
||||
const gFileType = potentialFileTypeFromExtension(g.fileName)!;
|
||||
const fa: PotentialLivePhotoAsset = {
|
||||
fileName: f.fileName,
|
||||
fileType: fFileType,
|
||||
collectionID: f.collectionID,
|
||||
uploadItem: f.uploadItem!,
|
||||
pathPrefix: f.pathPrefix,
|
||||
};
|
||||
const ga: PotentialLivePhotoAsset = {
|
||||
fileName: g.fileName,
|
||||
fileType: gFileType,
|
||||
collectionID: g.collectionID,
|
||||
uploadItem: g.uploadItem!,
|
||||
pathPrefix: g.pathPrefix,
|
||||
};
|
||||
const fa = items[index]!;
|
||||
const ga = items[index + 1]!;
|
||||
if (await areLivePhotoAssets(fa, ga, parsedMetadataJSONMap)) {
|
||||
const [image, video] =
|
||||
fFileType == FileType.image ? [f, g] : [g, f];
|
||||
fa.fileType == FileType.image ? [fa, ga] : [ga, fa];
|
||||
result.push({
|
||||
localID: f.localID,
|
||||
collectionID: f.collectionID,
|
||||
localID: fa.localID,
|
||||
collectionID: fa.collectionID,
|
||||
fileName: image.fileName,
|
||||
isLivePhoto: true,
|
||||
pathPrefix: image.pathPrefix,
|
||||
livePhotoAssets: {
|
||||
image: image.uploadItem!,
|
||||
video: video.uploadItem!,
|
||||
image: image.uploadItem,
|
||||
video: video.uploadItem,
|
||||
},
|
||||
});
|
||||
index += 2;
|
||||
} else {
|
||||
// They may already be a live photo (we might be retrying a
|
||||
// previously failed upload).
|
||||
result.push({ ...f, isLivePhoto: f.isLivePhoto ?? false });
|
||||
result.push({ ...fa, isLivePhoto: fa.isLivePhoto ?? false });
|
||||
index += 1;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -327,7 +327,7 @@ interface EncryptedFilePieces {
|
||||
|
||||
export interface PotentialLivePhotoAsset {
|
||||
fileName: string;
|
||||
fileType: number /* FileType */;
|
||||
fileType: number /* FileType | -1 */;
|
||||
collectionID: number;
|
||||
uploadItem: UploadItem;
|
||||
pathPrefix: UploadPathPrefix | undefined;
|
||||
|
||||